Received: (at 56046) by debbugs.gnu.org; 12 Jul 2022 15:45:51 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Tue Jul 12 11:45:51 2022 Received: from localhost ([127.0.0.1]:43557 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1oBI58-0005Th-JA for submit <at> debbugs.gnu.org; Tue, 12 Jul 2022 11:45:50 -0400 Received: from mx1.dismail.de ([78.46.223.134]:37764) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <jbranso@HIDDEN>) id 1oBI56-0005JZ-5q for 56046 <at> debbugs.gnu.org; Tue, 12 Jul 2022 11:45:49 -0400 Received: from mx1.dismail.de (localhost [127.0.0.1]) by mx1.dismail.de (OpenSMTPD) with ESMTP id 50e45c6e; Tue, 12 Jul 2022 17:45:40 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed; d=dismail.de; h=from:to:cc :subject:references:date:in-reply-to:message-id:mime-version :content-type; s=20190914; bh=hsCs40vFpYWEMvHXoCXFP6Ii22t6j6yP5e aEeV9kOko=; b=mLY7TLu7AMZvEw+r3rRARhOu30cQJFNk37Th35PDADobYqCCW2 PjfZCY1sS1nYRc8XYOnkf6h3vt1ugy/gGqQoo0ROzwjyXNanZ4CAEuSACHOCEa4u xPUraprhYicByariWk0sxbI9RZudMMUOHebugq+0I6TwGz2qr91BHcgZG7u0eVm/ sJ2sWSfT+u2QYmoC8ddW0/zF5r8+hvJxjlwwsMU+cvhAK0OFjOWGKzhETNHux3zN BoByHlGqfVBDq4gpKV+iyjzxU83Nk5T1/MX9rVIvq9XKd1iB8ZSctN8cJNH0kgd9 ht6mxrbkK8mdZXL6TnwwLa4SpL89W4DXjmPQ== Received: from smtp1.dismail.de (<unknown> [10.240.26.11]) by mx1.dismail.de (OpenSMTPD) with ESMTP id 33099331; Tue, 12 Jul 2022 17:45:40 +0200 (CEST) Received: from smtp1.dismail.de (localhost [127.0.0.1]) by smtp1.dismail.de (OpenSMTPD) with ESMTP id be2bee02; Tue, 12 Jul 2022 17:45:40 +0200 (CEST) Received: by dismail.de (OpenSMTPD) with ESMTPSA id e5236485 (TLSv1.3:TLS_AES_256_GCM_SHA384:256:NO); Tue, 12 Jul 2022 17:45:39 +0200 (CEST) From: Joshua Branson <jbranso@HIDDEN> To: Liliana Marie Prikler <liliana.prikler@HIDDEN> Subject: Re: bug#56046: [PATCH] services: mail: add opensmtpd records to enhance opensmtpd-configuration. References: <ce7ebbcfa7585592f2ba276b3d416172f9678f97.camel@HIDDEN> <756a905107d7783bab238091d668fddbc1e712ab.camel@HIDDEN> <20220704211759.8314-1-jbranso@HIDDEN> <6a272ff438ca4e2efc8e196c9160f857@HIDDEN> <338cfc2ca96492218838280d8961bef7@HIDDEN> <8abb0b095fd83baa81b6dc14364ae07da4036a17.camel@HIDDEN> <87mtdkfgl6.fsf_-_@HIDDEN> Date: Tue, 12 Jul 2022 11:45:37 -0400 In-Reply-To: <87mtdkfgl6.fsf_-_@HIDDEN> (Joshua Branson's message of "Thu, 07 Jul 2022 23:06:45 -0400") Message-ID: <87a69etjvi.fsf_-_@HIDDEN> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/28.1 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain X-Spam-Score: -0.7 (/) X-Debbugs-Envelope-To: 56046 Cc: 56046 <at> debbugs.gnu.org X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -1.7 (-) Joshua Branson <jbranso@HIDDEN> writes: Just an update on progress. The main thing to report is that the best place to find the task list is here: https://notabug.org/jbranso/linode-guix-system-configuration/src/master/opensmtpd.org The best place to find a good example configuration will always be found here: https://notabug.org/jbranso/linode-guix-system-configuration/src/master/opensmtpd.org#example-configuration The main thing that I am focusing on now is how to make it easy for users of this service to use opensmtpd filters: search for "enable to do dkimsigning or bogofilter" is the file and you'll see where it is. for some reason the link is REALLY long when I try to paste it in this email. Thanks, Joshua
guix-patches@HIDDEN
:bug#56046
; Package guix-patches
.
Full text available.Received: (at 56046) by debbugs.gnu.org; 8 Jul 2022 03:06:58 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Thu Jul 07 23:06:58 2022 Received: from localhost ([127.0.0.1]:58440 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1o9eKY-000322-JU for submit <at> debbugs.gnu.org; Thu, 07 Jul 2022 23:06:58 -0400 Received: from mx1.dismail.de ([78.46.223.134]:37679) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <jbranso@HIDDEN>) id 1o9eKW-00031l-FL for 56046 <at> debbugs.gnu.org; Thu, 07 Jul 2022 23:06:57 -0400 Received: from mx1.dismail.de (localhost [127.0.0.1]) by mx1.dismail.de (OpenSMTPD) with ESMTP id 39b1e452; Fri, 8 Jul 2022 05:06:49 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed; d=dismail.de; h=from:to:cc :subject:references:date:in-reply-to:message-id:mime-version :content-type:content-transfer-encoding; s=20190914; bh=x9u2KLSj ePEcG29GiEboMzFFQ3QzUD3+J5PQFBtwaJ0=; b=A27niz0MFAzcMXdXuEk3Rgh3 Dem3PXQBqyRDsc4QPEmdLcp71OIjrUMx7GPADgoWH2ai3uluEOs8GyDGC5vMabpC 7IeWB5JB6o6bDwgrpKz462roXl4Cc+kFeAcZ1vlu60NFSQ7yrH8Xgr2Vpm/mxj5+ nEsG4mzRqbcvFdQ7dbZwGkA+Bnq2xkd9dqdhjplTBSR5TdCFgEoTU3XeIFqvRGqY KjtEhDPWW3UG+BC5hseZ7JDMb+6AefxnKFhdLi+Vsu8lJJGHsEIWCbUCi5JNk16f BmwYrNk0qOKJhTgaPdHD+JHF8hCucIMkbVa/lbPLyXGaeVjH6DXCDui22X7wdA== Received: from smtp2.dismail.de (<unknown> [10.240.26.12]) by mx1.dismail.de (OpenSMTPD) with ESMTP id 056f4131; Fri, 8 Jul 2022 05:06:48 +0200 (CEST) Received: from smtp2.dismail.de (localhost [127.0.0.1]) by smtp2.dismail.de (OpenSMTPD) with ESMTP id c1f61dc7; Fri, 8 Jul 2022 05:06:48 +0200 (CEST) Received: by dismail.de (OpenSMTPD) with ESMTPSA id 30955af7 (TLSv1.3:TLS_AES_256_GCM_SHA384:256:NO); Fri, 8 Jul 2022 05:06:47 +0200 (CEST) From: Joshua Branson <jbranso@HIDDEN> To: Liliana Marie Prikler <liliana.prikler@HIDDEN> Subject: Re: bug#56046: [PATCH] services: mail: add opensmtpd records to enhance opensmtpd-configuration. References: <ce7ebbcfa7585592f2ba276b3d416172f9678f97.camel@HIDDEN> <756a905107d7783bab238091d668fddbc1e712ab.camel@HIDDEN> <20220704211759.8314-1-jbranso@HIDDEN> <6a272ff438ca4e2efc8e196c9160f857@HIDDEN> <338cfc2ca96492218838280d8961bef7@HIDDEN> <8abb0b095fd83baa81b6dc14364ae07da4036a17.camel@HIDDEN> Date: Thu, 07 Jul 2022 23:06:45 -0400 In-Reply-To: <8abb0b095fd83baa81b6dc14364ae07da4036a17.camel@HIDDEN> (Liliana Marie Prikler's message of "Thu, 07 Jul 2022 20:20:39 +0200") Message-ID: <87mtdkfgl6.fsf_-_@HIDDEN> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/28.1 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Score: -0.7 (/) X-Debbugs-Envelope-To: 56046 Cc: 56046 <at> debbugs.gnu.org X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -1.7 (-) Liliana Marie Prikler <liliana.prikler@HIDDEN> writes: > Am Donnerstag, dem 07.07.2022 um 17:27 +0000 schrieb > jbranso@HIDDEN: >> Also does guix have a style guide for writing services?=C2=A0 Do we >> usually only put "-configuration" for the top level configuration?=C2=A0 >> Is that the current style recommendations? > Style guide #0 is "look at what already exists". For example, the > jami-service also defines jami-account via define-configuration. Hope > that helps. > I suppose that I was following the nginx style. It seems like most of their records end in "-configuration". Thanks again for reviewing this. I should have an updated patch soon. > > Cheers
guix-patches@HIDDEN
:bug#56046
; Package guix-patches
.
Full text available.Received: (at 56046) by debbugs.gnu.org; 7 Jul 2022 18:20:49 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Thu Jul 07 14:20:49 2022 Received: from localhost ([127.0.0.1]:57860 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1o9W7N-0000Vg-9v for submit <at> debbugs.gnu.org; Thu, 07 Jul 2022 14:20:49 -0400 Received: from mail-ed1-f65.google.com ([209.85.208.65]:42970) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <liliana.prikler@HIDDEN>) id 1o9W7L-0000VQ-D9 for 56046 <at> debbugs.gnu.org; Thu, 07 Jul 2022 14:20:47 -0400 Received: by mail-ed1-f65.google.com with SMTP id r18so24173610edb.9 for <56046 <at> debbugs.gnu.org>; Thu, 07 Jul 2022 11:20:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=message-id:subject:from:to:date:in-reply-to:references:user-agent :mime-version:content-transfer-encoding; bh=P9av8p6DIgA/4vd8NZtKc/7xvKKAHUbNrGEWeW+FL1Q=; b=ffruA5JqaiaNINHaPBL7pfi8Q4mEbJIbvznwEYxGBiU7HGh8X91T73emnGrhASZ7S/ 2wF4Xmq6tnAcEgB3XIhLCE8/Ixosywf+7ARomGq0XUc/KZq/9714B4vqW6xVUrH6xPlF hlcoGi8fmtsdJzIsHp8Ac+mlYiPz6kCP1bI721Af20tdrbS1OrruXk8gShrA30qLhjIc lKK7lN3fx80IPDecTXZXveCM+5es+AC3PWill1yF16Q6o1R20Cz7SNy5a+HYjOZ19jII DK1GGWU3k7ZyVdaScv+3L4bDosf1rQxNAWogmj9F1ARhe95mfE9hwKcMi/g3abWiwld6 2LHw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:message-id:subject:from:to:date:in-reply-to :references:user-agent:mime-version:content-transfer-encoding; bh=P9av8p6DIgA/4vd8NZtKc/7xvKKAHUbNrGEWeW+FL1Q=; b=d/3amkz98KvVysWaahrO0llvSI6DdNDkXIuZJQhpSqCEiQepAZY8cMa893Vkl7l6ek NuAn0vnqirqHiIdpwM5NHacCYwpqy+oH+tJMxZDTnA+RPSjun6q1M+RI5x5wRpSxyKJL Wk72O1LX8Rgn+ouHA6+G79O+A1YJsAllefbKJ40OdITVBDXNT50OQ1MSQNTwfCFRM2rT wk7xnSSGHIcJN6x2bnlfRtNGov+7hVv25kt3luEyc1N4vh3VSvW5Q2kz0cObVHnpsM6b YQKnNu6RoIq1RYag3vAU0eRzwJwb9/ISb9cL4b7rtCcuwGV7c7B+HiZolvyf5rac4iVQ V1lQ== X-Gm-Message-State: AJIora+2SOkWlpZQUKKJhrkL/57V1BG30UNeznlCtmL0hN7+gHVezGbR Mu/UNKnlk4sW1n3HT9bgEI4= X-Google-Smtp-Source: AGRyM1s+HEcpqf9tD511xvwBHjiIP4cy623cTEDoUgiqNUQjPTiJlQqGadqsF+RHnW8xc8Tb69rkfg== X-Received: by 2002:a05:6402:2548:b0:437:62de:668 with SMTP id l8-20020a056402254800b0043762de0668mr63740277edb.143.1657218040933; Thu, 07 Jul 2022 11:20:40 -0700 (PDT) Received: from nijino.fritz.box (85-127-52-93.dsl.dynamic.surfer.at. [85.127.52.93]) by smtp.gmail.com with ESMTPSA id ee34-20020a056402292200b0043a554818afsm10175621edb.42.2022.07.07.11.20.40 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 07 Jul 2022 11:20:40 -0700 (PDT) Message-ID: <8abb0b095fd83baa81b6dc14364ae07da4036a17.camel@HIDDEN> Subject: Re: [PATCH] services: mail: add opensmtpd records to enhance opensmtpd-configuration. Version 2 From: Liliana Marie Prikler <liliana.prikler@HIDDEN> To: jbranso@HIDDEN, 56046 <at> debbugs.gnu.org Date: Thu, 07 Jul 2022 20:20:39 +0200 In-Reply-To: <338cfc2ca96492218838280d8961bef7@HIDDEN> References: <ce7ebbcfa7585592f2ba276b3d416172f9678f97.camel@HIDDEN> <756a905107d7783bab238091d668fddbc1e712ab.camel@HIDDEN> <20220704211759.8314-1-jbranso@HIDDEN> <6a272ff438ca4e2efc8e196c9160f857@HIDDEN> <338cfc2ca96492218838280d8961bef7@HIDDEN> Content-Type: text/plain; charset="UTF-8" User-Agent: Evolution 3.42.1 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Spam-Score: -0.0 (/) X-Debbugs-Envelope-To: 56046 X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -1.0 (-) Am Donnerstag, dem 07.07.2022 um 17:27 +0000 schrieb jbranso@HIDDEN: > Also does guix have a style guide for writing services? Do we > usually only put "-configuration" for the top level configuration? > Is that the current style recommendations? Style guide #0 is "look at what already exists". For example, the jami-service also defines jami-account via define-configuration. Hope that helps. Cheers
guix-patches@HIDDEN
:bug#56046
; Package guix-patches
.
Full text available.Received: (at 56046) by debbugs.gnu.org; 7 Jul 2022 17:27:18 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Thu Jul 07 13:27:18 2022 Received: from localhost ([127.0.0.1]:57770 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1o9VHa-0003Cm-9h for submit <at> debbugs.gnu.org; Thu, 07 Jul 2022 13:27:18 -0400 Received: from mx1.dismail.de ([78.46.223.134]:19716) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <jbranso@HIDDEN>) id 1o9VHX-0003CX-Ky for 56046 <at> debbugs.gnu.org; Thu, 07 Jul 2022 13:27:16 -0400 Received: from mx1.dismail.de (localhost [127.0.0.1]) by mx1.dismail.de (OpenSMTPD) with ESMTP id 8a5a8c96; Thu, 7 Jul 2022 19:27:08 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed; d=dismail.de; h= mime-version:date:content-type:content-transfer-encoding:from :message-id:subject:to:in-reply-to:references; s=20190914; bh=KK FthKd3VPVD4OaTYVTeHhHA4n5uu4SnNIdgq3XKyj4=; b=aFOMhEax1erZyLmV57 cxp9JQNWsd43eB4Mh4Ei2xvGILWSa3lOoe1vOG2wkq3OOH7RoS8uSkvnFq3va1hW mdsSOI2OG8dmuKUg80cEORyJCpCG4viFdvw7R+UnGRBjqTtw35CYJ2gKX4xrEXvb tYuWszs3m6n+cjxRykiB9oU9PEvUASBS6F5spU51HGMGUGGP9jF8b1ezjmzkYn4j +lsUtOD2cqMjFHFCVU1dICkaISlbw/xxKUEsufmLo49qzehdI2oWzQvTeaubMHAa 09nmYwiX/s1jtWFS3RxWmBqvjojdEXYmMLziGtf74sgAN3NMPr2Mi+lKk/mnGJQz tCgw== Received: from smtp2.dismail.de (<unknown> [10.240.26.12]) by mx1.dismail.de (OpenSMTPD) with ESMTP id 122b0778; Thu, 7 Jul 2022 19:27:07 +0200 (CEST) Received: from smtp2.dismail.de (localhost [127.0.0.1]) by smtp2.dismail.de (OpenSMTPD) with ESMTP id 7d3c90f9; Thu, 7 Jul 2022 19:27:07 +0200 (CEST) Received: by dismail.de (OpenSMTPD) with ESMTPSA id 0992026e (TLSv1.3:TLS_AES_256_GCM_SHA384:256:NO); Thu, 7 Jul 2022 19:27:07 +0200 (CEST) MIME-Version: 1.0 Date: Thu, 07 Jul 2022 17:27:06 +0000 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-Mailer: RainLoop/1.16.0a From: jbranso@HIDDEN Message-ID: <338cfc2ca96492218838280d8961bef7@HIDDEN> Subject: Re: [PATCH] services: mail: add opensmtpd records to enhance opensmtpd-configuration. Version 2 To: "Liliana Marie Prikler" <liliana.prikler@HIDDEN>, 56046 <at> debbugs.gnu.org In-Reply-To: <ce7ebbcfa7585592f2ba276b3d416172f9678f97.camel@HIDDEN> References: <ce7ebbcfa7585592f2ba276b3d416172f9678f97.camel@HIDDEN> <756a905107d7783bab238091d668fddbc1e712ab.camel@HIDDEN> <20220704211759.8314-1-jbranso@HIDDEN> <6a272ff438ca4e2efc8e196c9160f857@HIDDEN> X-Spam-Score: -0.7 (/) X-Debbugs-Envelope-To: 56046 X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -1.7 (-) July 7, 2022 2:48 AM, "Liliana Marie Prikler" <liliana.prikler@HIDDEN>= wrote: > Am Mittwoch, dem 06.07.2022 um 21:51 +0000 schrieb jbranso@HIDDEN: >=20 >>=20I do not believe that guile has a file-exists? thunk.=20 >>=20I could use (access? file F_OK) every time, but I think >> file-exists? is easier to use. My two cents. >=20 >=20scheme@(guile-user)> file-exists? > $1 =3D #<procedure 7fa4d7da6828 at ice-9/boot-9.scm:1971:6 (str)> ok. Good to know! >=20 >>=20What is a "listen-on"? >>=20 >>=20"listen-on" refers to the "listen on" in smtpd.conf: >> https://man.openbsd.org/smtpd.conf >>=20 >>=20lan_addr =3D "192.168.0.1" >> listen on $lan_addr >> listen on $lan_addr tls auth >=20 >=20Okay, but what would you call that? An address maybe? >=20 >>>=20+(define-record-type* <opensmtpd-listen-on-socket-configuration- >>> configuration> >>=20 >>=20Again, could this just be <opensmtpd-socket-configuration>? >>=20 >>=20I would prefer to have two data types for "listen on" and >> "listen on socket". "listen on socket" only supports 3 options, >> where "listen on" supports 19. >=20 >=20I am not questioning whether it makes sense to add a configuration > record =E2=80=93 it probably does =E2=80=93 but whether you're using th= e best name for > that record. We are not Java programmers here, a little abstraction > goes a long way. >=20 >>=20From the documentation: >>=20 >>=20listen on interface [family] [options] >> Listen on the interface for incoming connections, using the same >> syntax as ifconfig(8). The interface parameter may also be an >> interface group, an IP address, or a domain name. Listening can >> optionally be restricted to a specific address family, which can be >> either inet4 or inet6. >>=20 >>=20listen on socket [options] >> Listen for incoming SMTP connections on the Unix domain socket >> /var/run/smtpd.sock. This is done by default, even if the directive >> is absent. >=20 >=20So you can either have an opensmtp-interface (with family and a bunch > of options) or an opensmtp-socket (with a bunch of options). Sounds > like a much nicer ontology, doesn't it? That does make much more sense! I'll do that. >=20 >>>=20[...] >>=20 >>=20Too much to check, too little time. Maybe return later. >>=20 To=20summarize the updated tasks that you have given me are: =20 =201) Write a proper changelog. 2) define "string-in-list?" with member? Are there other procedures that could use this? 3) DONE replace [] with () 4) Shorten the sanitize procedure for opensmtpd-option-configuration 5) Review your class names. I also don't think it makes too much sense to add -configuration for anything but the top-level configuration record, it just requires you to type much more configuration than you probably want. Also does guix have a style guide for writing services? Do we usually only put "-configuration" for the top level configuration? Is that the current style recommendations? =20 eg:=20rename opensmtpd-listen-on-configuration and opensmtpd-listen-on-socket-configuration to opensmtpd-interface and opensmtpd-socket.
guix-patches@HIDDEN
:bug#56046
; Package guix-patches
.
Full text available.Received: (at 56046) by debbugs.gnu.org; 7 Jul 2022 04:25:47 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Thu Jul 07 00:25:47 2022 Received: from localhost ([127.0.0.1]:55465 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1o9J5F-0003Fp-CY for submit <at> debbugs.gnu.org; Thu, 07 Jul 2022 00:25:47 -0400 Received: from mail-ej1-f68.google.com ([209.85.218.68]:41598) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <liliana.prikler@HIDDEN>) id 1o9J5A-0003FY-Kd for 56046 <at> debbugs.gnu.org; Thu, 07 Jul 2022 00:25:44 -0400 Received: by mail-ej1-f68.google.com with SMTP id u12so30289872eja.8 for <56046 <at> debbugs.gnu.org>; Wed, 06 Jul 2022 21:25:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=message-id:subject:from:to:date:in-reply-to:references:user-agent :mime-version:content-transfer-encoding; bh=mER72DH/rkHh76cSAamAG9HOQPGoN8SNKo3lzZdOLg4=; b=pdY8Ab7igE4LZnoFwKg2/TdzV5uMXtDe9ZrCgIp6t1COzTqhXriE6MTpNR251yOWWs UscPm2nQsNqgvCsFrnlSUogF440rGq21APas+0IayBSv5SfpQ6y1lwH+K3ezKUPq1JAb ueKenYwqlRBBEYl7lWzsq7L26dUQduRHQx8Tv42gmVv/FVbVRTYGIPJCmcq+y5BK6pkz 8dF9i1IPSZrLTrPHyo3Tcw6umMGLuKkKK/wqPuldQJ8HvgAmRvXGtT7rle+hupjsMhiT +wP1oKXqchCgEWxVjpOTpd/LIbMJQM2EYtLpCFrnLjJ2NsWkh/TLxNuU5S02T/28CkwF eL7A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:message-id:subject:from:to:date:in-reply-to :references:user-agent:mime-version:content-transfer-encoding; bh=mER72DH/rkHh76cSAamAG9HOQPGoN8SNKo3lzZdOLg4=; b=Fhs1HRPIeX+bs2rtYUrIPf8JnVgB7gQZX7UeZoEFdQn8vhxVzPIcal/GkDp5na9ash sNKkpIgjWOTtVYSD1J/2jIuprdesBRrHQ67BJ6Hml6giu9/YZiQd0pP7N/r4dyZEjaeS 2NCvUXs5ypTEepPB8NYniSEKHTzDVC4v/FMUo69NL03I+lh4yMiySgReefk9hkTax8EP 3ouvfBf/WOhPvj0c2F26amZNOPs82YALkwWIZOYrPmcvudFHvEd3aFA+HBGZ/5ehl1qQ pgePw50TfkCB6P+eGYPWDrFzZdu80Jn4dgvUWfZVySh2LWDC1uPXZ6pbHw651IrBIbEF 6GJA== X-Gm-Message-State: AJIora/Hg1V3wkbige057gS9ExpwMw6xgtnoxsvH9wKHpBTdSSEcfKAF r/Jzwy0pVGctA6s2BtWusy8= X-Google-Smtp-Source: AGRyM1ukYaPxrduJXVUPMT0Eo16mVSf1Br8n/pHqkPJHsn1o2U82i9YlGDLpGnrf/3cejpAY425dKw== X-Received: by 2002:a17:907:1693:b0:726:4322:c330 with SMTP id hc19-20020a170907169300b007264322c330mr43209664ejc.9.1657167934559; Wed, 06 Jul 2022 21:25:34 -0700 (PDT) Received: from nijino.fritz.box (85-127-52-93.dsl.dynamic.surfer.at. [85.127.52.93]) by smtp.gmail.com with ESMTPSA id jp1-20020a170906f74100b006fe0abb00f0sm18184620ejb.209.2022.07.06.21.25.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 06 Jul 2022 21:25:34 -0700 (PDT) Message-ID: <ce7ebbcfa7585592f2ba276b3d416172f9678f97.camel@HIDDEN> Subject: Re: [PATCH] services: mail: add opensmtpd records to enhance opensmtpd-configuration. Version 2 From: Liliana Marie Prikler <liliana.prikler@HIDDEN> To: jbranso@HIDDEN, 56046 <at> debbugs.gnu.org Date: Thu, 07 Jul 2022 06:25:32 +0200 In-Reply-To: <6a272ff438ca4e2efc8e196c9160f857@HIDDEN> References: <756a905107d7783bab238091d668fddbc1e712ab.camel@HIDDEN> <20220704211759.8314-1-jbranso@HIDDEN> <6a272ff438ca4e2efc8e196c9160f857@HIDDEN> Content-Type: text/plain; charset="UTF-8" User-Agent: Evolution 3.42.1 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Spam-Score: -0.0 (/) X-Debbugs-Envelope-To: 56046 X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -1.0 (-) Am Mittwoch, dem 06.07.2022 um 21:51 +0000 schrieb jbranso@HIDDEN: > I do not believe that guile has a file-exists? thunk. > I could use (access? file F_OK) every time, but I think > file-exists? is easier to use. My two cents. scheme@(guile-user)> file-exists? $1 = #<procedure 7fa4d7da6828 at ice-9/boot-9.scm:1971:6 (str)> > > > What is a "listen-on"? > > "listen-on" refers to the "listen on" in smtpd.conf: > https://man.openbsd.org/smtpd.conf > > lan_addr = "192.168.0.1" > listen on $lan_addr > listen on $lan_addr tls auth Okay, but what would you call that? An address maybe? > > > +(define-record-type* <opensmtpd-listen-on-socket-configuration- > > > configuration> > > > > Again, could this just be <opensmtpd-socket-configuration>? > > I would prefer to have two data types for "listen on" and > "listen on socket". "listen on socket" only supports 3 options, > where "listen on" supports 19. I am not questioning whether it makes sense to add a configuration record – it probably does – but whether you're using the best name for that record. We are not Java programmers here, a little abstraction goes a long way. > From the documentation: > > listen on interface [family] [options] > Listen on the interface for incoming connections, using the same > syntax as ifconfig(8). The interface parameter may also be an > interface group, an IP address, or a domain name. Listening can > optionally be restricted to a specific address family, which can be > either inet4 or inet6. > > listen on socket [options] > Listen for incoming SMTP connections on the Unix domain socket > /var/run/smtpd.sock. This is done by default, even if the directive > is absent. So you can either have an opensmtp-interface (with family and a bunch of options) or an opensmtp-socket (with a bunch of options). Sounds like a much nicer ontology, doesn't it? > > > [...] > > > > Too much to check, too little time. Maybe return later. > > To summarize the tasks that you have given me are: > > 1) Write a proper changelog. > 2) define "string-in-list?" with member? > Are there other procedures that could use this? > 3) replace [] with () > 4) Shorten the sanitize procedure for opensmtpd-option-configuration 5) Review your class names. I also don't think it makes too much sense to add -configuration for anything but the top-level configuration record, it just requires you to type much more configuration than you probably want. Cheers
guix-patches@HIDDEN
:bug#56046
; Package guix-patches
.
Full text available.Received: (at 56046) by debbugs.gnu.org; 6 Jul 2022 21:51:45 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Wed Jul 06 17:51:45 2022 Received: from localhost ([127.0.0.1]:55313 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1o9Cvp-00089w-5V for submit <at> debbugs.gnu.org; Wed, 06 Jul 2022 17:51:45 -0400 Received: from mx1.dismail.de ([78.46.223.134]:41044) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <jbranso@HIDDEN>) id 1o9Cvk-00089Z-91 for 56046 <at> debbugs.gnu.org; Wed, 06 Jul 2022 17:51:35 -0400 Received: from mx1.dismail.de (localhost [127.0.0.1]) by mx1.dismail.de (OpenSMTPD) with ESMTP id 32b3ce11; Wed, 6 Jul 2022 23:51:21 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed; d=dismail.de; h= mime-version:date:content-type:content-transfer-encoding:from :message-id:subject:to:in-reply-to:references; s=20190914; bh=M3 lAaEhkW86YkTagxi7uVUSv51d6g7TerXXrvKBSwCI=; b=iclNkuBRM/HOGMQfE/ icAjrYe+AI6t7hKmghf4R+cC1npyLHk84fC8cSS7uPGOP0LfDowl3Co+mcsVucye mq0VPYPUANl8NBVlJF8DlolCwzEJ7y/jKQgORYnxe6mE1SLvodTuZtlrg7g9XnXn sXiocHBxvLbrkXaB7ggAa8gdpQvnHGdc/STxGescxAP9rmNM9noH64Xqv+fwprty +H/GhryNdpKjm63XjMI93s+egY4QSbZM6OuwJnj0L6qrtkbX94uqYZtPkIR4ltuB N2B1gQHmPfHBGBOk9QGSiu2bp5xZ7JEB822fxsrRw+/TEpWhgmNLk7pPn7eZDzf8 CCgw== Received: from smtp1.dismail.de (<unknown> [10.240.26.11]) by mx1.dismail.de (OpenSMTPD) with ESMTP id 655320fa; Wed, 6 Jul 2022 23:51:21 +0200 (CEST) Received: from smtp1.dismail.de (localhost [127.0.0.1]) by smtp1.dismail.de (OpenSMTPD) with ESMTP id e411c72a; Wed, 6 Jul 2022 23:51:21 +0200 (CEST) Received: by dismail.de (OpenSMTPD) with ESMTPSA id 097f58c9 (TLSv1.3:TLS_AES_256_GCM_SHA384:256:NO); Wed, 6 Jul 2022 23:51:20 +0200 (CEST) MIME-Version: 1.0 Date: Wed, 06 Jul 2022 21:51:19 +0000 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-Mailer: RainLoop/1.16.0a From: jbranso@HIDDEN Message-ID: <6a272ff438ca4e2efc8e196c9160f857@HIDDEN> Subject: Re: [PATCH] services: mail: add opensmtpd records to enhance opensmtpd-configuration. Version 2 To: "Liliana Marie Prikler" <liliana.prikler@HIDDEN>, 56046 <at> debbugs.gnu.org In-Reply-To: <756a905107d7783bab238091d668fddbc1e712ab.camel@HIDDEN> References: <756a905107d7783bab238091d668fddbc1e712ab.camel@HIDDEN> <20220704211759.8314-1-jbranso@HIDDEN> X-Spam-Score: -0.7 (/) X-Debbugs-Envelope-To: 56046 X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -1.0 (-) July 6, 2022 12:27 AM, "Liliana Marie Prikler" <liliana.prikler@HIDDEN= > wrote: > Am Montag, dem 04.07.2022 um 17:17 -0400 schrieb Joshua Branson: >=20 >>=20Openmstpd-configuration may only be configured by a config-file.=20 >>=20This >> patch, enables one to configure opensmtpd by using some guile record >> types (defined via define-record-type*). >>=20 >>=20* gnu/services/mail.scm: New records (opensmtpd-table- >> configuration), >> (opensmtpd-ca-configuration), >> (opensmtpd-pki-configuration), >> (opensmtpd-action-local-delivery-configuration), >> (opensmtpd-maildir-configuration), >> (opensmtpd-mda-configuration), >> (opensmtpd-action-relay-configuration), >> (opensmtpd-option-configuration), >> (opensmtpd-filter-phase-configuration), >> (opensmtpd-filter-configuration), >> (opensmtpd-listen-on-configuration), >> (opensmtpd-listen-on-socket-configuration), >> (opensmtpd-match-configuration), >> (opensmtpd-smtp-configuration), >> (opensmtpd-srs-configuration), >> (opensmtpd-queue-configuration), and >> (opensmtpd-configuration). >=20 >=20Not a ChangeLog. I'll actually go and learn how to do that thanks. >=20 >>=20New procedures: false?, is-value-right-type, add-comma-or-string, >> file-exists?, list-of-procedures->string, string-in-list?, my- >> sanitize, >> opensmtpd-filter-chain?, throw-error-duplicate-option, >> sanitize-list-of-options-for-match-configuration, sanitize-filters, >> list-has-duplicates-or-non-filters?, >> filter-phase-has-message-and-value?, >> filter-phase-decision-lacks-proper-message?, >> filter-phase-lacks-proper-value?, >> filter-phase-has-incorrect-junk-or-bypass?, >> filter-phase-junks-after-commit?, >> list-of-unique-filter-or-filter-phase?, throw-error, >> contains-duplicate?, list-of-type?, list-of-strings?, >> list-of-unique-opensmtpd-option-configuration?, >> list-of-opensmtpd-ca-configuration?, >> list-of-opensmtpd-pki-configuration?, >> list-of-opensmtpd-listen-on-configuration?, >> list-of-unique-opensmtpd-match-configuration?, list-of-strings- >> string, >> assoc-list? assoc-list, variable->string, >> table-whose-data-are-assoc-list?, >> table-whose-data-are-a-list-of-strings?, assoc-list->string, >> opensmtpd-table-configuration->string, >> opensmtpd-listen-on-configuration->string, >> opensmtpd-listen-on-socket-configuration->string, >> opensmtpd-action-relay-configuration->string, >> opensmtpd-lmtp-configuration->string, >> opensmtpd-mda-configuration->string, >> opensmtpd-maildir-configuration->string, >> opensmtpd-action-local-delivery-configuration->string, >> opensmtpd-action->string, opensmtpd-option-configuration->string, >> opensmtpd-match-configuration->string, >> opensmtpd-ca-configuration->string, opensmtpd-pki-configuration- >> string, >> generate-filter-chain-name, opensmtpd-filter-chain->string, >> opensmtpd-filter-phase-configuration->string, opensmtpd-filters- >> string, >> opensmtpd-configuration-listen->string, >> opensmtpd-configuration-srs->string, >> opensmtpd-smtp-configuration->string, >> opensmtpd-configuration-queue->string, get-opensmtpd-actions, >> get-opensmtpd-pki-configurations, get-opensmtpd-filters, flatten, >> get-opensmtpd-tables, opensmtpd-configuration-fieldname->string, >> list-of-records->string, opensmtpd-configuration->mixed-text-file. >=20 >=20Neither is this. >> * doc/guix.texi added documentation for the new records for >> opensmtpd. >=20 >=20Or this. >=20 >>=20--- >> doc/guix.texi | 1051 ++++++++++++++++++++- >> gnu/services/mail.scm | 2016 >> ++++++++++++++++++++++++++++++++++++++++- >> 2 files changed, 3056 insertions(+), 11 deletions(-) >>=20 >>=20diff --git a/doc/guix.texi b/doc/guix.texi >> index eda0956260..e8564240d1 100644 >> --- a/doc/guix.texi >> +++ b/doc/guix.texi >> @@ -24849,14 +24849,59 @@ could instantiate a dovecot service like >> this: >> @subsubheading OpenSMTPD Service >>=20 >>=20@deffn {Scheme Variable} opensmtpd-service-type >> -This is the type of the @uref{https://www.opensmtpd.org, OpenSMTPD} >> -service, whose value should be an @code{opensmtpd-configuration} >> object >> -as in this example: >> - >> -@lisp >> -(service opensmtpd-service-type >> - (opensmtpd-configuration >> - (config-file (local-file "./my-smtpd.conf")))) >> +OpenSMTPD is an easy-to-use mail transfer agent (MTA). Its >> configuration file is >> +throughly documented in @code{man 5 smtpd.conf}. OpenSMTPD >> @strong{listens} for incoming >> +mail and @strong{matches} the mail to @strong{actions}. The >> following records represent those >> +stages: >> + >> +@multitable {aaaaaaaaa} >> {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} >=20 >=20I suggest using fractions here. I'll take a look at the texinfo fractions bit. This was all generated=20 from=20an org-mode document. >=20 >>=20[...] >> +This is a string of one of these options: >> + >> +@multitable {aaaaaaaaaaaaaaaaaaaa} >> {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} >=20 >=20Same here. Btw. I did not actually check all the doc in between, so I > might be missing something. >> +@multitable {aaaaaaaaaaaaaaaaaaaaaaaaa} >> {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} >=20 >=20Likewise. >> +@multitable {aaaaaaaaaa} >> {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} >=20 >=20You get the drill. >=20 >>=20[more doc with strange multitables] >> diff --git a/gnu/services/mail.scm b/gnu/services/mail.scm >> index d99743ac31..2a344e303e 100644 >> --- a/gnu/services/mail.scm >> +++ b/gnu/services/mail.scm >> @@ -57,8 +57,143 @@ (define-module (gnu services mail) >> mailbox-configuration >> namespace-configuration >>=20 >>=20+ opensmtpd-table-configuration >> + opensmtpd-table-configuration? >> + opensmtpd-table-configuration-name >> + opensmtpd-table-configuration-file-db >> + opensmtpd-table-configuration-data >> + >> + opensmtpd-ca-configuration >> + opensmtpd-ca-configuration? >> + opensmtpd-ca-configuration-name >> + opensmtpd-ca-configuration-file >> + >> + opensmtpd-pki-configuration >> + opensmtpd-pki-configuration? >> + opensmtpd-pki-configuration-domain >> + opensmtpd-pki-configuration-cert >> + opensmtpd-pki-configuration-key >> + opensmtpd-pki-configuration-dhe >> + >> + opensmtpd-action-local-delivery-configuration >> + opensmtpd-action-local-delivery-configuration? >> + opensmtpd-action-local-delivery-configuration-method >> + opensmtpd-action-local-delivery-configuration-alias >> + opensmtpd-action-local-delivery-configuration-ttl >> + opensmtpd-action-local-delivery-configuration-user >> + opensmtpd-action-local-delivery-configuration-userbase >> + opensmtpd-action-local-delivery-configuration-virtual >> + opensmtpd-action-local-delivery-configuration-wrapper >> + >> + opensmtpd-maildir-configuration >> + opensmtpd-maildir-configuration? >> + opensmtpd-maildir-configuration-pathname >> + opensmtpd-maildir-configuration-junk >> + >> + opensmtpd-mda-configuration >> + opensmtpd-mda-configuration-name >> + opensmtpd-mda-configuration-command >> + >> + opensmtpd-action-relay-configuration >> + opensmtpd-action-relay-configuration? >> + opensmtpd-action-relay-configuration-backup >> + opensmtpd-action-relay-configuration-backup-mx >> + opensmtpd-action-relay-configuration-helo >> + opensmtpd-action-relay-configuration-domain >> + opensmtpd-action-relay-configuration-host >> + opensmtpd-action-relay-configuration-pki >> + opensmtpd-action-relay-configuration-srs >> + opensmtpd-action-relay-configuration-tls >> + opensmtpd-action-relay-configuration-auth >> + opensmtpd-action-relay-configuration-mail-from >> + opensmtpd-action-relay-configuration-src >> + >> +=20=20=20=20=20 opensmtpd-option-configuration >> + opensmtpd-option-configuration? >> + opensmtpd-option-configuration-option >> + opensmtpd-option-configuration-not >> + opensmtpd-option-configuration-regex >> + opensmtpd-option-configuration-data >> + >> + opensmtpd-filter-phase-configuration >> + opensmtpd-filter-phase-configuration? >> + opensmtpd-filter-phase-configuration-name >> + opensmtpd-filter-phase-configuration-phase-name >> + opensmtpd-filter-phase-configuration-options >> + opensmtpd-filter-phase-configuration-decision >> + opensmtpd-filter-phase-configuration-message >> + opensmtpd-filter-phase-configuration-value >> + >> + opensmtpd-filter-configuration >> + opensmtpd-filter-configuration? >> + opensmtpd-filter-configuration-name >> + opensmtpd-filter-configuration-proc >> + >> + opensmtpd-listen-on-configuration >> + opensmtpd-listen-on-configuration? >> + opensmtpd-listen-on-configuration-interface >> + opensmtpd-listen-on-configuration-family >> + opensmtpd-listen-on-configuration-auth >> + opensmtpd-listen-on-configuration-auth-optional >> + opensmtpd-listen-on-configuration-filters >> + opensmtpd-listen-on-configuration-hostname >> + opensmtpd-listen-on-configuration-hostnames >> + opensmtpd-listen-on-configuration-mask-src >> + opensmtpd-listen-on-configuration-disable-dsn >> + opensmtpd-listen-on-configuration-pki >> + opensmtpd-listen-on-configuration-port >> + opensmtpd-listen-on-configuration-proxy-v2 >> + opensmtpd-listen-on-configuration-received-auth >> + opensmtpd-listen-on-configuration-senders >> + opensmtpd-listen-on-configuration-secure-connection >> + opensmtpd-listen-on-configuration-tag >> + >> + opensmtpd-listen-on-socket-configuration >> + opensmtpd-listen-on-socket-configuration? >> + opensmtpd-listen-on-socket-configuration-filters >> + opensmtpd-listen-on-socket-configuration-mask-src >> + opensmtpd-listen-on-socket-configuration-tag >> + >> + opensmtpd-match-configuration >> + opensmtpd-match-configuration? >> + opensmtpd-match-configuration-action >> + opensmtpd-match-configuration-options >> + >> + opensmtpd-smtp-configuration >> + opensmtpd-smtp-configuration? >> + opensmtpd-smtp-configuration-ciphers >> + opensmtpd-smtp-configuration-limit-max-mails >> + opensmtpd-smtp-configuration-limit-max-rcpt >> + opensmtpd-smtp-configuration-max-message-size >> + opensmtpd-smtp-configuration-sub-addr-delim character >> + >> + opensmtpd-srs-configuration >> + opensmtpd-srs-configuration? >> + opensmtpd-srs-configuration-key >> + opensmtpd-srs-configuration-backup-key >> + opensmtpd-srs-configuration-ttl-delay >> + >> + opensmtpd-queue-configuration >> + opensmtpd-queue-configuration? >> + opensmtpd-queue-configuration-compression >> + opensmtpd-queue-configuration-encryption >> + opensmtpd-queue-configuration-ttl-delay >> + >> opensmtpd-configuration >> opensmtpd-configuration? >> + opensmtpd-package >> + opensmtpd-config-file >> + opensmtpd-configuration-bounce >> + opensmtpd-configuration-listen-ons >> + opensmtpd-configuration-listen-on-socket >> + opensmtpd-configuration-includes >> + opensmtpd-configuration-matches >> + opensmtpd-configuration-mda-wrappers >> + opensmtpd-configuration-mta-max-deferred >> + opensmtpd-configuration-srs >> + opensmtpd-configuration-smtp >> + opensmtpd-configuration-queue >> + >> opensmtpd-service-type >> %default-opensmtpd-config-file >>=20 >>=20@@ -1651,13 +1786,1888 @@ (define (generate-dovecot-documentation) >> ;;; OpenSMTPD. >> ;;; >>=20 >>=20+;; some fieldnames have a default value of #f, which is ok. They >> cannot have a value of #t. >> +;; for example opensmtpd-table-configuration-data can be #f, BUT NOT >> true. >> +;; my/sanitize procedure tests values to see if they are of the >> right kind. >> +;; procedure false? is needed to allow fields like 'values' to be >> blank, (empty), or #f BUT also >> +;; have a value like a list of strings. >> +(define (false? var) >> + (eq? #f var)) >=20 >=20I'm pretty sure it'd be fine to use not in lieu of false?, even at th= e > risk of matching nil. Sounds ok. I may need to double check that there are no places that have nil values. >=20 >>=20+;; this procedure takes in a var and a list of procedures. It loop= s >> through list of procedures passing in var to each. >> +;; if one procedure returns #t, the function returns true.=20 >>=20Otherwise #f. >> +;; TODO for fun rewrite this using map >> +;; If I rewrote it in map, then it may help with sanitizing. >> +;; eg: I could then potentially easily sanitize vars with lambda >> procedures. >> +(define (is-value-right-type? var list-of-procedures record >> fieldname) >> + (if (null? list-of-procedures) >> + #f >> + (cond [(procedure? (car list-of-procedures)) >> + (if ((car list-of-procedures) var) >> + #t >> + (is-value-right-type? var (cdr list-of- >> procedures) record fieldname))] >> + [(and (sanitize-configuration? (car list-of- >> procedures)) >> + (sanitize-configuration-error-if-proc-fails (car >> list-of-procedures)) >> + (if ((sanitize-configuration-proc (car list-of- >> procedures)) var) >> + #t >> + (begin >> + (apply string-append >> + (sanitize-configuration-error- >> message (car list-of-procedures))) >> + (throw 'bad! var))))] >> + [else (if ((sanitize-configuration-proc (car list-of- >> procedures)) var) >> + #t >> + (is-value-right-type? var (cdr list-of- >> procedures) record fieldname))]))) >=20 >=20Don't we have field sanitizers already that make this obsolete? >=20 >>=20+;; converts strings like this: >> +;; "apple, ham, cherry" -> "apple, ham, or cherry" >> +;; "pineapple" -> "pinneapple". >> +;; "cheese, grapefruit, or jam" -> "cheese, grapefruit, or jam" >> +(define (add-comma-or string) >> + (define last-comma-location (string-rindex string #\,)) >> + (if last-comma-location >> + (if (string-contains string ", or" last-comma-location) >> + string >> + (string-replace string ", or" last-comma-location >> + (+ 1 last-comma-location))) >> + string)) >> + >> +;; I could test for read-ability of a file, but then I would have to >> +;; test the program as root everytime instead of as a normal user... >> +(define (file-exists? file) >> +(if (string? file) >> + (access? file F_OK) >> + #f)) >=20 >=20Is this not part of the Guile standard library? I do not believe that guile has a file-exists? thunk.=20=20 I=20could use (access? file F_OK) every time, but I think file-exists? is easier to use. My two cents. >=20 >>=20+(define (list-of-procedures->string procedures) >> + (define string >> + (let loop ([procedures procedures]) >> + (if (null? procedures) >> + "" >> + (begin >> + (string-append >> + (cond [(eq? false? (car procedures)) >> + "#f , "] >> + [(eq? boolean? (car procedures)) >> + "boolean, "] >> + [(eq? string? (car procedures)) >> +=20=20=20=20=20=20=20=20=20=20 "string, "] >> + [(eq? integer? (car procedures)) >> + "integer, "] >> + [(eq? list-of-strings? (car procedures)) >> + "list of strings, "] >> + [(eq? assoc-list? (car procedures)) >> + "an association list, "] >> + [(eq? opensmtpd-pki-configuration? (car >> procedures)) >> + "an <opensmtpd-pki-configuration> record, "] >> + [(eq? opensmtpd-table-configuration? (car >> procedures)) >> + "an <opensmtpd-table-configuration> record, "] >> + [(eq? list-of-unique-opensmtpd-match- >> configuration? (car procedures)) >> + "a list of unique <opensmtpd-match- >> configuration> records, "] >> + [(eq? table-whose-data-are-assoc-list? (car >> procedures)) >> + (string-append >> + "an <opensmtpd-table-configuration> record whose >> fieldname 'values' are an assoc-list \n" >> + "(eg: (opensmtpd-table-configuration (name >> \"table\") (data '(\"joshua\" . \"$encrypted$password\")))), ")] >> + [(eq? file-exists? (car procedures)) >> + "file, "] >> + [else "has an incorrect value, "]) >> + (loop (cdr procedures))))))) >> + (add-comma-or (string-append (string-drop-right string 2) ".\n"))) >=20 >=20Using a table, map and string-join might be wiser. If this is the onl= y > place add-comma-or is used, you can replace it by=20 >=20(string-append > (string-join (butlast strings) ",") > ", or " (last strings)) > where you only need to define butlast. I'll take a look. >=20 >>=20+;; TODO can I M-x raise-sexp (string=3D? string var) in this >> procedure? and get rid of checking >> +;; if the var is a string? The previous string-in-list? had that >> check. >> +;; (string-in-list? '("hello" 5 "cat")) currently works. If I M-x >> raise-sexp (string=3D? string var) >> +;; then it will no longer work. >> +(define (string-in-list? string list) >> + (primitive-eval (cons 'or (map (lambda (var) (and (string? var) >> (string=3D? string var))) list)))) >=20 >=20Ever heard of member? Will use it. >=20 >>=20+(define (my/sanitize var record fieldname list-of-procedures) >> + (if (is-value-right-type? var list-of-procedures record fieldname) >> + var >> + (begin >> + (display (string-append "<" record "> fieldname: '" >> fieldname "' is of type " >> + (list-of-procedures->string list-of- >> procedures) "\n")) >> + (throw 'bad! var)))) >> + >> +;; Some example opensmtpd-table-configurations: >> +;; >> +;; (opensmtpd-table-configuration (name "root accounts") (data >> '(("joshua" . "root@HIDDEN") ("joshua" . >> "postmaster@HIDDEN")))) >> +;; (opensmtpd-table-configuration (name "root accounts") (data >> (list "mysite.me" "your-site.com"))) >> +;; TODO should <opensmtpd-table-configuration> support have a >> fieldname 'file'? >> +;; Or should I change name to name-or-file ? >> +(define-record-type* <opensmtpd-table-configuration> >> + opensmtpd-table-configuration make-opensmtpd-table-configuration >> + opensmtpd-table-configuration? >> + this-record >> + (name opensmtpd-table-configuration-name ;; string >> + (default #f) >> + (sanitize (lambda (var) >> + (my/sanitize var "opensmtpd-table-configuration" >> "name" (list string?))))) >> + (file-db opensmtpd-table-configuration-file-db >> + (default #f) >> + (sanitize (lambda (var) >> + (my/sanitize var "opensmtpd-table- >> configuration" "file-db" >> + (list boolean?))))) >> + ;; FIXME support an aliasing table as described here: >> + ;; https://man.openbsd.org/table.5 >> + ;; One may have to use the record file for this. I don't think >> tables support a table like this: >> + ;; table "name" { joshua =3D >> joshua@HIDDEN,joshua@HIDDEN,joshua@HIDDEN, root = =3D >> root@HIDDEN } >> + ;; If values is an absolute filename, then it will use said >> filename to house the table info. >> + ;; filename must be an absolute filename. >> + (data opensmtpd-table-configuration-data >> + (default #f) >> + (sanitize (lambda (var) >> + (my/sanitize var "opensmtpd-table- >> configuration" "values" >> + (list file-exists? list-of- >> strings? assoc-list?))))) >> + ;; is a list of values or key values >> + ;; eg: (list "mysite.me" "your-site.com") >> + ;; eg: (list ("joshua" . "joshua@HIDDEN") ("james" . >> "james@HIDDEN")) >> + ;; I am currently making these values be as assocation list of >> strings only. >> + ;; FIXME should I allow a var like this? >> + ;; (list (cons "gnucode.me" 234.949.392.23)) >> + ;; can be of type: (quote list-of-strings) or (quote assoc-list) >> + ;; (opensmtpd-table-configuration-type record) returns the values' >> type. The user SHOULD NEVER set the type. >> + ;; TODO jpoiret: on irc reccomends that I just use an outside >> function to determine fieldname 'values', type. >> + ;; it would be "simpler" and possibly easier for the next person >> working on this code to understand what is happening. >> + (type opensmtpd-table-configuration-type >> + (default #f) >> + (thunked) >> + (sanitize (lambda (var) >> + (cond [(opensmtpd-table-configuration-data this- >> record) >> + (if (list-of-strings? (opensmtpd-table- >> configuration-data this-record)) >> + (quote list-of-strings) >> + (quote assoc-list))] >=20 >=20Just a quick side note, we don't usually intermix [ and (. It's all (= . Yeah I saw that in the coding style. I'll fix it. >=20 >>=20[skipping a bit of stuff, may check later...] >> +(define-record-type* <opensmtpd-option-configuration> >> + opensmtpd-option-configuration make-opensmtpd-option-configuration >> + opensmtpd-option-configuration? >> + (option opensmtpd-option-configuration-option >> + (default #f) >> + (sanitize (lambda (var) >> + (if (and (string? var) >> + (or (string-in-list? var (list >> "fcrdns" "rdns" >> + "src" >> "helo" >> + "auth" >> "mail-from" >> + "rcpt-to" >> + "for" >> + "for any" >> "for local" >> + "for >> domain" "for rcpt-to" >> + "from any" >> "from auth" >> + "from >> local" "from mail-from" >> + "from >> rdns" "from socket" >> + "from src" >> "auth" >> + "helo" >> "mail-from" >> + "rcpt-to" >> "tag" "tls" >> + )))) >> + var >> + (begin >> + (display (string-append "<opensmtpd- >> option-configuration> fieldname: 'option' is of type \n" >> + "string. The >> string can be either 'fcrdns', \n" >> + " 'rdns', 'src', >> 'helo', 'auth', 'mail-from', or 'rcpt-to', \n" >> + "'for', 'for >> any', 'for local', 'for domain', 'for rcpt-to', \n" >> + "'from any', >> 'from auth', 'from local', 'from mail-from', 'from rdns', 'from >> socket', \n" >> + "'from src', >> 'auth helo', 'mail-from', 'rcpt-to', 'tag', or 'tls' \n" >> + )) >> + (throw 'bad! var)))))) >=20 >=20This is a little verbose for what it does. Fair I suppose. I'll see if I can shorten it. >=20 >>=20+(define-record-type* <opensmtpd-listen-on-configuration> >=20 >=20What is a "listen-on"? "listen-on" refers to the "listen on" in smtpd.conf: https://man.openbsd.org/smtpd.conf lan_addr =3D "192.168.0.1" listen on $lan_addr listen on $lan_addr tls auth >> +(define-record-type* <opensmtpd-listen-on-socket-configuration- >> configuration> >=20 >=20Again, could this just be <opensmtpd-socket-configuration>? I would prefer to have two data types for "listen on" and "listen on socket". "listen on socket" only supports 3 options, where "listen on" supports 19.=20=20 https://man.openbsd.org/smtpd.conf ;;valid=20record (openstmpd-listen-on-configuration (tag "port-48") (port 48)) ;;invalid record and will result in an error as it should. (openstmpd-listen-on-socket-configuration (tag "port-48") (port 48)) >=20 >>=20(define-record-type* <opensmtpd-configuration> >> opensmtpd-configuration make-opensmtpd-configuration >> opensmtpd-configuration? >> - (package opensmtpd-configuration-package >> - (default opensmtpd)) >> + (package opensmtpd-configuration-package >> + (default opensmtpd)) >> (config-file opensmtpd-configuration-config-file >> - (default %default-opensmtpd-config-file))) >> + (default #f)) >> + ;; FIXME/TODO should I include a admd authservid entry? >> + >> + ;; TODO sanitize this properly with perhaps a <sanitize- >> configuration>. >> + (bounce opensmtpd-configuration-bounce >> + (default #f) >> + (sanitize (lambda (var) >> + (my/sanitize var "opensmtpd-configuration" >> "bounce" >> + (list false? list?))))) >> + (cas opensmtpd-configuration-cas >> + (default #f) >> + (sanitize (lambda (var) >> + (my/sanitize var "opensmtpd-configuration" "cas" >> + (list false? list-of-opensmtpd-ca- >> configuration?))))) >> + ;; list of many records of type opensmtpd-listen-on-configuration >> + (listen-ons opensmtpd-configuration-listen-ons >=20 >=20What does opensmtpd acutally listen on? From the documentation: listen on interface [family] [options] Listen on the interface for incoming connections, using the same synt= ax as ifconfig(8). The interface parameter may also be an interface group= , an IP address, or a domain name. Listening can optionally be restricted= to a specific address family, which can be either inet4 or inet6.=20 listen=20on socket [options] Listen for incoming SMTP connections on the Unix domain socket /var/r= un/smtpd.sock. This is done by default, even if the directive is absent.= =20 >=20 >> [...] >=20 >=20Too much to check, too little time. Maybe return later. To summarize the tasks that you have given me are: 1) Write a proper changelog. 2) define "string-in-list?" with member?=20 =20 Are there other procedures that could use this? 3) replace [] with () 4) Shorten the sanitize procedure for opensmtpd-option-configuration Thanks for reviewing! Joshua
guix-patches@HIDDEN
:bug#56046
; Package guix-patches
.
Full text available.Received: (at 56046) by debbugs.gnu.org; 6 Jul 2022 04:28:06 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Wed Jul 06 00:28:05 2022 Received: from localhost ([127.0.0.1]:52027 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1o8wdr-00059i-JP for submit <at> debbugs.gnu.org; Wed, 06 Jul 2022 00:28:05 -0400 Received: from mail-ej1-f67.google.com ([209.85.218.67]:45709) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <liliana.prikler@HIDDEN>) id 1o8wdk-00059N-9k for 56046 <at> debbugs.gnu.org; Wed, 06 Jul 2022 00:27:57 -0400 Received: by mail-ej1-f67.google.com with SMTP id h23so24894321ejj.12 for <56046 <at> debbugs.gnu.org>; Tue, 05 Jul 2022 21:27:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=message-id:subject:from:to:date:in-reply-to:references:user-agent :mime-version:content-transfer-encoding; bh=gduwxLChwEnV1zfeROMWD2TTCf0U4GW0foAab/DvmkU=; b=oxmmwhTY0a52Gu7tfJuEwO5mSJtnLJ4fipIuDcoOBfOCZ/n6WGuxeyvh9toEhlEWuc fB8FLK6MOZkNVz/tRWpaAJf3/Z6GVOuX3PXCTmGuxXMvFaZdXRRoyPILnYLjG3eY1zgr DtAnRLzVfKCl5JRNNPz5+C72Bkh0xPxsiad/2r4RMTehN7I8ndKdHLNgoFCCRsf6ZFYP c31GoqHB6AwFVc2EbYSiAG/eCdyxjmuOqLJixoReNQktwl1gGxqfPk9xlX+czEibkLP1 ivXgGloWWsphUecQE1f7zJfHzfXMfPot8KByDl0waalylog3UL7S3ohlBxHUMPCK5OQr eZug== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:message-id:subject:from:to:date:in-reply-to :references:user-agent:mime-version:content-transfer-encoding; bh=gduwxLChwEnV1zfeROMWD2TTCf0U4GW0foAab/DvmkU=; b=sIIPPHdE/M+ksaUzSZ2tIFVu2bCGJHejBV/Z7omXpUqvmyIzRC7IZKeRqo6Kzl8KsT wqjchszdzRcfWpDpfEHc7muV4W4vH47MVGQFljhPJjfPUNZjzg6GrPbAAS65WqlMYxmk 1b+FjxUEAZxjRORBcIH/jEi3Q6SrMlp7BZyWaQSqFcn9ofyjndo89Dsz0WIkCFaIpTVo kRv85l8krtCBxI3OQgF+KrttCvdqxNUREDSmIPhdrNSPTnppQ+SmMC7OQ9/nQ3eU4KTw 8RwZaIrkolk4CeU1tgDjR8Mb0aroFLB7qZG5ohImwP6pksZKZ+gVIe9jtvm78D7LG5UD PPBw== X-Gm-Message-State: AJIora/kzBdTYFvEMNw7SSbV5H3akwnHOFAUZYYwJdNS2v0wQZ7YK5r6 H69eckl1egv4hJBUJ0nOjeE= X-Google-Smtp-Source: AGRyM1vL3MyLJNnnhI1sURiM+IfbomW3JmT0piu+RCjNOwtDjLGAw7xqvoiimcCn4wHvNiXDfnMGCg== X-Received: by 2002:a17:906:2da:b0:712:14b:62da with SMTP id 26-20020a17090602da00b00712014b62damr36743944ejk.351.1657081665857; Tue, 05 Jul 2022 21:27:45 -0700 (PDT) Received: from nijino.fritz.box (85-127-52-93.dsl.dynamic.surfer.at. [85.127.52.93]) by smtp.gmail.com with ESMTPSA id la21-20020a170907781500b0072aeda86ac3sm1358592ejc.149.2022.07.05.21.27.45 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 05 Jul 2022 21:27:45 -0700 (PDT) Message-ID: <756a905107d7783bab238091d668fddbc1e712ab.camel@HIDDEN> Subject: Re: [PATCH] services: mail: add opensmtpd records to enhance opensmtpd-configuration. Version 2 From: Liliana Marie Prikler <liliana.prikler@HIDDEN> To: Joshua Branson <jbranso@HIDDEN>, 56046 <at> debbugs.gnu.org Date: Wed, 06 Jul 2022 06:27:44 +0200 In-Reply-To: <20220704211759.8314-1-jbranso@HIDDEN> References: <20220704211759.8314-1-jbranso@HIDDEN> Content-Type: text/plain; charset="UTF-8" User-Agent: Evolution 3.42.1 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Spam-Score: 0.0 (/) X-Debbugs-Envelope-To: 56046 X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -1.0 (-) Am Montag, dem 04.07.2022 um 17:17 -0400 schrieb Joshua Branson: > Openmstpd-configuration may only be configured by a config-file. > This > patch, enables one to configure opensmtpd by using some guile record > types (defined via define-record-type*). > > * gnu/services/mail.scm: New records (opensmtpd-table- > configuration), > (opensmtpd-ca-configuration), > (opensmtpd-pki-configuration), > (opensmtpd-action-local-delivery-configuration), > (opensmtpd-maildir-configuration), > (opensmtpd-mda-configuration), > (opensmtpd-action-relay-configuration), > (opensmtpd-option-configuration), > (opensmtpd-filter-phase-configuration), > (opensmtpd-filter-configuration), > (opensmtpd-listen-on-configuration), > (opensmtpd-listen-on-socket-configuration), > (opensmtpd-match-configuration), > (opensmtpd-smtp-configuration), > (opensmtpd-srs-configuration), > (opensmtpd-queue-configuration), and > (opensmtpd-configuration). Not a ChangeLog. > New procedures: false?, is-value-right-type, add-comma-or-string, > file-exists?, list-of-procedures->string, string-in-list?, my- > sanitize, > opensmtpd-filter-chain?, throw-error-duplicate-option, > sanitize-list-of-options-for-match-configuration, sanitize-filters, > list-has-duplicates-or-non-filters?, > filter-phase-has-message-and-value?, > filter-phase-decision-lacks-proper-message?, > filter-phase-lacks-proper-value?, > filter-phase-has-incorrect-junk-or-bypass?, > filter-phase-junks-after-commit?, > list-of-unique-filter-or-filter-phase?, throw-error, > contains-duplicate?, list-of-type?, list-of-strings?, > list-of-unique-opensmtpd-option-configuration?, > list-of-opensmtpd-ca-configuration?, > list-of-opensmtpd-pki-configuration?, > list-of-opensmtpd-listen-on-configuration?, > list-of-unique-opensmtpd-match-configuration?, list-of-strings- > >string, > assoc-list? assoc-list, variable->string, > table-whose-data-are-assoc-list?, > table-whose-data-are-a-list-of-strings?, assoc-list->string, > opensmtpd-table-configuration->string, > opensmtpd-listen-on-configuration->string, > opensmtpd-listen-on-socket-configuration->string, > opensmtpd-action-relay-configuration->string, > opensmtpd-lmtp-configuration->string, > opensmtpd-mda-configuration->string, > opensmtpd-maildir-configuration->string, > opensmtpd-action-local-delivery-configuration->string, > opensmtpd-action->string, opensmtpd-option-configuration->string, > opensmtpd-match-configuration->string, > opensmtpd-ca-configuration->string, opensmtpd-pki-configuration- > >string, > generate-filter-chain-name, opensmtpd-filter-chain->string, > opensmtpd-filter-phase-configuration->string, opensmtpd-filters- > >string, > opensmtpd-configuration-listen->string, > opensmtpd-configuration-srs->string, > opensmtpd-smtp-configuration->string, > opensmtpd-configuration-queue->string, get-opensmtpd-actions, > get-opensmtpd-pki-configurations, get-opensmtpd-filters, flatten, > get-opensmtpd-tables, opensmtpd-configuration-fieldname->string, > list-of-records->string, opensmtpd-configuration->mixed-text-file. Neither is this. > * doc/guix.texi added documentation for the new records for > opensmtpd. Or this. > --- > doc/guix.texi | 1051 ++++++++++++++++++++- > gnu/services/mail.scm | 2016 > ++++++++++++++++++++++++++++++++++++++++- > 2 files changed, 3056 insertions(+), 11 deletions(-) > > diff --git a/doc/guix.texi b/doc/guix.texi > index eda0956260..e8564240d1 100644 > --- a/doc/guix.texi > +++ b/doc/guix.texi > @@ -24849,14 +24849,59 @@ could instantiate a dovecot service like > this: > @subsubheading OpenSMTPD Service > > @deffn {Scheme Variable} opensmtpd-service-type > -This is the type of the @uref{https://www.opensmtpd.org, OpenSMTPD} > -service, whose value should be an @code{opensmtpd-configuration} > object > -as in this example: > - > -@lisp > -(service opensmtpd-service-type > - (opensmtpd-configuration > - (config-file (local-file "./my-smtpd.conf")))) > +OpenSMTPD is an easy-to-use mail transfer agent (MTA). Its > configuration file is > +throughly documented in @code{man 5 smtpd.conf}. OpenSMTPD > @strong{listens} for incoming > +mail and @strong{matches} the mail to @strong{actions}. The > following records represent those > +stages: > + > +@multitable {aaaaaaaaa} > {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} I suggest using fractions here. > [...] > +This is a string of one of these options: > + > +@multitable {aaaaaaaaaaaaaaaaaaaa} > {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} Same here. Btw. I did not actually check all the doc in between, so I might be missing something. > +@multitable {aaaaaaaaaaaaaaaaaaaaaaaaa} > {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} Likewise. > +@multitable {aaaaaaaaaa} > {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} You get the drill. > [more doc with strange multitables] > diff --git a/gnu/services/mail.scm b/gnu/services/mail.scm > index d99743ac31..2a344e303e 100644 > --- a/gnu/services/mail.scm > +++ b/gnu/services/mail.scm > @@ -57,8 +57,143 @@ (define-module (gnu services mail) > mailbox-configuration > namespace-configuration > > + opensmtpd-table-configuration > + opensmtpd-table-configuration? > + opensmtpd-table-configuration-name > + opensmtpd-table-configuration-file-db > + opensmtpd-table-configuration-data > + > + opensmtpd-ca-configuration > + opensmtpd-ca-configuration? > + opensmtpd-ca-configuration-name > + opensmtpd-ca-configuration-file > + > + opensmtpd-pki-configuration > + opensmtpd-pki-configuration? > + opensmtpd-pki-configuration-domain > + opensmtpd-pki-configuration-cert > + opensmtpd-pki-configuration-key > + opensmtpd-pki-configuration-dhe > + > + opensmtpd-action-local-delivery-configuration > + opensmtpd-action-local-delivery-configuration? > + opensmtpd-action-local-delivery-configuration-method > + opensmtpd-action-local-delivery-configuration-alias > + opensmtpd-action-local-delivery-configuration-ttl > + opensmtpd-action-local-delivery-configuration-user > + opensmtpd-action-local-delivery-configuration-userbase > + opensmtpd-action-local-delivery-configuration-virtual > + opensmtpd-action-local-delivery-configuration-wrapper > + > + opensmtpd-maildir-configuration > + opensmtpd-maildir-configuration? > + opensmtpd-maildir-configuration-pathname > + opensmtpd-maildir-configuration-junk > + > + opensmtpd-mda-configuration > + opensmtpd-mda-configuration-name > + opensmtpd-mda-configuration-command > + > + opensmtpd-action-relay-configuration > + opensmtpd-action-relay-configuration? > + opensmtpd-action-relay-configuration-backup > + opensmtpd-action-relay-configuration-backup-mx > + opensmtpd-action-relay-configuration-helo > + opensmtpd-action-relay-configuration-domain > + opensmtpd-action-relay-configuration-host > + opensmtpd-action-relay-configuration-pki > + opensmtpd-action-relay-configuration-srs > + opensmtpd-action-relay-configuration-tls > + opensmtpd-action-relay-configuration-auth > + opensmtpd-action-relay-configuration-mail-from > + opensmtpd-action-relay-configuration-src > + > + opensmtpd-option-configuration > + opensmtpd-option-configuration? > + opensmtpd-option-configuration-option > + opensmtpd-option-configuration-not > + opensmtpd-option-configuration-regex > + opensmtpd-option-configuration-data > + > + opensmtpd-filter-phase-configuration > + opensmtpd-filter-phase-configuration? > + opensmtpd-filter-phase-configuration-name > + opensmtpd-filter-phase-configuration-phase-name > + opensmtpd-filter-phase-configuration-options > + opensmtpd-filter-phase-configuration-decision > + opensmtpd-filter-phase-configuration-message > + opensmtpd-filter-phase-configuration-value > + > + opensmtpd-filter-configuration > + opensmtpd-filter-configuration? > + opensmtpd-filter-configuration-name > + opensmtpd-filter-configuration-proc > + > + opensmtpd-listen-on-configuration > + opensmtpd-listen-on-configuration? > + opensmtpd-listen-on-configuration-interface > + opensmtpd-listen-on-configuration-family > + opensmtpd-listen-on-configuration-auth > + opensmtpd-listen-on-configuration-auth-optional > + opensmtpd-listen-on-configuration-filters > + opensmtpd-listen-on-configuration-hostname > + opensmtpd-listen-on-configuration-hostnames > + opensmtpd-listen-on-configuration-mask-src > + opensmtpd-listen-on-configuration-disable-dsn > + opensmtpd-listen-on-configuration-pki > + opensmtpd-listen-on-configuration-port > + opensmtpd-listen-on-configuration-proxy-v2 > + opensmtpd-listen-on-configuration-received-auth > + opensmtpd-listen-on-configuration-senders > + opensmtpd-listen-on-configuration-secure-connection > + opensmtpd-listen-on-configuration-tag > + > + opensmtpd-listen-on-socket-configuration > + opensmtpd-listen-on-socket-configuration? > + opensmtpd-listen-on-socket-configuration-filters > + opensmtpd-listen-on-socket-configuration-mask-src > + opensmtpd-listen-on-socket-configuration-tag > + > + opensmtpd-match-configuration > + opensmtpd-match-configuration? > + opensmtpd-match-configuration-action > + opensmtpd-match-configuration-options > + > + opensmtpd-smtp-configuration > + opensmtpd-smtp-configuration? > + opensmtpd-smtp-configuration-ciphers > + opensmtpd-smtp-configuration-limit-max-mails > + opensmtpd-smtp-configuration-limit-max-rcpt > + opensmtpd-smtp-configuration-max-message-size > + opensmtpd-smtp-configuration-sub-addr-delim character > + > + opensmtpd-srs-configuration > + opensmtpd-srs-configuration? > + opensmtpd-srs-configuration-key > + opensmtpd-srs-configuration-backup-key > + opensmtpd-srs-configuration-ttl-delay > + > + opensmtpd-queue-configuration > + opensmtpd-queue-configuration? > + opensmtpd-queue-configuration-compression > + opensmtpd-queue-configuration-encryption > + opensmtpd-queue-configuration-ttl-delay > + > opensmtpd-configuration > opensmtpd-configuration? > + opensmtpd-package > + opensmtpd-config-file > + opensmtpd-configuration-bounce > + opensmtpd-configuration-listen-ons > + opensmtpd-configuration-listen-on-socket > + opensmtpd-configuration-includes > + opensmtpd-configuration-matches > + opensmtpd-configuration-mda-wrappers > + opensmtpd-configuration-mta-max-deferred > + opensmtpd-configuration-srs > + opensmtpd-configuration-smtp > + opensmtpd-configuration-queue > + > opensmtpd-service-type > %default-opensmtpd-config-file > > @@ -1651,13 +1786,1888 @@ (define (generate-dovecot-documentation) > ;;; OpenSMTPD. > ;;; > > +;; some fieldnames have a default value of #f, which is ok. They > cannot have a value of #t. > +;; for example opensmtpd-table-configuration-data can be #f, BUT NOT > true. > +;; my/sanitize procedure tests values to see if they are of the > right kind. > +;; procedure false? is needed to allow fields like 'values' to be > blank, (empty), or #f BUT also > +;; have a value like a list of strings. > +(define (false? var) > + (eq? #f var)) I'm pretty sure it'd be fine to use not in lieu of false?, even at the risk of matching nil. > +;; this procedure takes in a var and a list of procedures. It loops > through list of procedures passing in var to each. > +;; if one procedure returns #t, the function returns true. > Otherwise #f. > +;; TODO for fun rewrite this using map > +;; If I rewrote it in map, then it may help with sanitizing. > +;; eg: I could then potentially easily sanitize vars with lambda > procedures. > +(define (is-value-right-type? var list-of-procedures record > fieldname) > + (if (null? list-of-procedures) > + #f > + (cond [(procedure? (car list-of-procedures)) > + (if ((car list-of-procedures) var) > + #t > + (is-value-right-type? var (cdr list-of- > procedures) record fieldname))] > + [(and (sanitize-configuration? (car list-of- > procedures)) > + (sanitize-configuration-error-if-proc-fails (car > list-of-procedures)) > + (if ((sanitize-configuration-proc (car list-of- > procedures)) var) > + #t > + (begin > + (apply string-append > + (sanitize-configuration-error- > message (car list-of-procedures))) > + (throw 'bad! var))))] > + [else (if ((sanitize-configuration-proc (car list-of- > procedures)) var) > + #t > + (is-value-right-type? var (cdr list-of- > procedures) record fieldname))]))) Don't we have field sanitizers already that make this obsolete? > +;; converts strings like this: > +;; "apple, ham, cherry" -> "apple, ham, or cherry" > +;; "pineapple" -> "pinneapple". > +;; "cheese, grapefruit, or jam" -> "cheese, grapefruit, or jam" > +(define (add-comma-or string) > + (define last-comma-location (string-rindex string #\,)) > + (if last-comma-location > + (if (string-contains string ", or" last-comma-location) > + string > + (string-replace string ", or" last-comma-location > + (+ 1 last-comma-location))) > + string)) > + > +;; I could test for read-ability of a file, but then I would have to > +;; test the program as root everytime instead of as a normal user... > +(define (file-exists? file) > +(if (string? file) > + (access? file F_OK) > + #f)) Is this not part of the Guile standard library? > +(define (list-of-procedures->string procedures) > + (define string > + (let loop ([procedures procedures]) > + (if (null? procedures) > + "" > + (begin > + (string-append > + (cond [(eq? false? (car procedures)) > + "#f , "] > + [(eq? boolean? (car procedures)) > + "boolean, "] > + [(eq? string? (car procedures)) > + "string, "] > + [(eq? integer? (car procedures)) > + "integer, "] > + [(eq? list-of-strings? (car procedures)) > + "list of strings, "] > + [(eq? assoc-list? (car procedures)) > + "an association list, "] > + [(eq? opensmtpd-pki-configuration? (car > procedures)) > + "an <opensmtpd-pki-configuration> record, "] > + [(eq? opensmtpd-table-configuration? (car > procedures)) > + "an <opensmtpd-table-configuration> record, "] > + [(eq? list-of-unique-opensmtpd-match- > configuration? (car procedures)) > + "a list of unique <opensmtpd-match- > configuration> records, "] > + [(eq? table-whose-data-are-assoc-list? (car > procedures)) > + (string-append > + "an <opensmtpd-table-configuration> record whose > fieldname 'values' are an assoc-list \n" > + "(eg: (opensmtpd-table-configuration (name > \"table\") (data '(\"joshua\" . \"$encrypted$password\")))), ")] > + [(eq? file-exists? (car procedures)) > + "file, "] > + [else "has an incorrect value, "]) > + (loop (cdr procedures))))))) > + (add-comma-or (string-append (string-drop-right string 2) ".\n"))) Using a table, map and string-join might be wiser. If this is the only place add-comma-or is used, you can replace it by (string-append (string-join (butlast strings) ",") ", or " (last strings)) where you only need to define butlast. > +;; TODO can I M-x raise-sexp (string=? string var) in this > procedure? and get rid of checking > +;; if the var is a string? The previous string-in-list? had that > check. > +;; (string-in-list? '("hello" 5 "cat")) currently works. If I M-x > raise-sexp (string=? string var) > +;; then it will no longer work. > +(define (string-in-list? string list) > + (primitive-eval (cons 'or (map (lambda (var) (and (string? var) > (string=? string var))) list)))) Ever heard of member? > +(define (my/sanitize var record fieldname list-of-procedures) > + (if (is-value-right-type? var list-of-procedures record fieldname) > + var > + (begin > + (display (string-append "<" record "> fieldname: '" > fieldname "' is of type " > + (list-of-procedures->string list-of- > procedures) "\n")) > + (throw 'bad! var)))) > + > +;; Some example opensmtpd-table-configurations: > +;; > +;; (opensmtpd-table-configuration (name "root accounts") (data > '(("joshua" . "root@HIDDEN") ("joshua" . > "postmaster@HIDDEN")))) > +;; (opensmtpd-table-configuration (name "root accounts") (data > (list "mysite.me" "your-site.com"))) > +;; TODO should <opensmtpd-table-configuration> support have a > fieldname 'file'? > +;; Or should I change name to name-or-file ? > +(define-record-type* <opensmtpd-table-configuration> > + opensmtpd-table-configuration make-opensmtpd-table-configuration > + opensmtpd-table-configuration? > + this-record > + (name opensmtpd-table-configuration-name ;; string > + (default #f) > + (sanitize (lambda (var) > + (my/sanitize var "opensmtpd-table-configuration" > "name" (list string?))))) > + (file-db opensmtpd-table-configuration-file-db > + (default #f) > + (sanitize (lambda (var) > + (my/sanitize var "opensmtpd-table- > configuration" "file-db" > + (list boolean?))))) > + ;; FIXME support an aliasing table as described here: > + ;; https://man.openbsd.org/table.5 > + ;; One may have to use the record file for this. I don't think > tables support a table like this: > + ;; table "name" { joshua = > joshua@HIDDEN,joshua@HIDDEN,joshua@HIDDEN, root = > root@HIDDEN } > + ;; If values is an absolute filename, then it will use said > filename to house the table info. > + ;; filename must be an absolute filename. > + (data opensmtpd-table-configuration-data > + (default #f) > + (sanitize (lambda (var) > + (my/sanitize var "opensmtpd-table- > configuration" "values" > + (list file-exists? list-of- > strings? assoc-list?))))) > + ;; is a list of values or key values > + ;; eg: (list "mysite.me" "your-site.com") > + ;; eg: (list ("joshua" . "joshua@HIDDEN") ("james" . > "james@HIDDEN")) > + ;; I am currently making these values be as assocation list of > strings only. > + ;; FIXME should I allow a var like this? > + ;; (list (cons "gnucode.me" 234.949.392.23)) > + ;; can be of type: (quote list-of-strings) or (quote assoc-list) > + ;; (opensmtpd-table-configuration-type record) returns the values' > type. The user SHOULD NEVER set the type. > + ;; TODO jpoiret: on irc reccomends that I just use an outside > function to determine fieldname 'values', type. > + ;; it would be "simpler" and possibly easier for the next person > working on this code to understand what is happening. > + (type opensmtpd-table-configuration-type > + (default #f) > + (thunked) > + (sanitize (lambda (var) > + (cond [(opensmtpd-table-configuration-data this- > record) > + (if (list-of-strings? (opensmtpd-table- > configuration-data this-record)) > + (quote list-of-strings) > + (quote assoc-list))] Just a quick side note, we don't usually intermix [ and (. It's all (. > [skipping a bit of stuff, may check later...] > +(define-record-type* <opensmtpd-option-configuration> > + opensmtpd-option-configuration make-opensmtpd-option-configuration > + opensmtpd-option-configuration? > + (option opensmtpd-option-configuration-option > + (default #f) > + (sanitize (lambda (var) > + (if (and (string? var) > + (or (string-in-list? var (list > "fcrdns" "rdns" > + "src" > "helo" > + "auth" > "mail-from" > + "rcpt-to" > + "for" > + "for any" > "for local" > + "for > domain" "for rcpt-to" > + "from any" > "from auth" > + "from > local" "from mail-from" > + "from > rdns" "from socket" > + "from src" > "auth" > + "helo" > "mail-from" > + "rcpt-to" > "tag" "tls" > + )))) > + var > + (begin > + (display (string-append "<opensmtpd- > option-configuration> fieldname: 'option' is of type \n" > + "string. The > string can be either 'fcrdns', \n" > + " 'rdns', 'src', > 'helo', 'auth', 'mail-from', or 'rcpt-to', \n" > + "'for', 'for > any', 'for local', 'for domain', 'for rcpt-to', \n" > + "'from any', > 'from auth', 'from local', 'from mail-from', 'from rdns', 'from > socket', \n" > + "'from src', > 'auth helo', 'mail-from', 'rcpt-to', 'tag', or 'tls' \n" > + )) > + (throw 'bad! var)))))) This is a little verbose for what it does. > +(define-record-type* <opensmtpd-listen-on-configuration> What is a "listen-on"? > +(define-record-type* <opensmtpd-listen-on-socket-configuration- > configuration> Again, could this just be <opensmtpd-socket-configuration>? > (define-record-type* <opensmtpd-configuration> > opensmtpd-configuration make-opensmtpd-configuration > opensmtpd-configuration? > - (package opensmtpd-configuration-package > - (default opensmtpd)) > + (package opensmtpd-configuration-package > + (default opensmtpd)) > (config-file opensmtpd-configuration-config-file > - (default %default-opensmtpd-config-file))) > + (default #f)) > + ;; FIXME/TODO should I include a admd authservid entry? > + > + ;; TODO sanitize this properly with perhaps a <sanitize- > configuration>. > + (bounce opensmtpd-configuration-bounce > + (default #f) > + (sanitize (lambda (var) > + (my/sanitize var "opensmtpd-configuration" > "bounce" > + (list false? list?))))) > + (cas opensmtpd-configuration-cas > + (default #f) > + (sanitize (lambda (var) > + (my/sanitize var "opensmtpd-configuration" "cas" > + (list false? list-of-opensmtpd-ca- > configuration?))))) > + ;; list of many records of type opensmtpd-listen-on-configuration > + (listen-ons opensmtpd-configuration-listen-ons What does opensmtpd acutally listen on? > [...] Too much to check, too little time. Maybe return later.
guix-patches@HIDDEN
:bug#56046
; Package guix-patches
.
Full text available.Received: (at 56046) by debbugs.gnu.org; 5 Jul 2022 21:36:34 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Tue Jul 05 17:36:34 2022 Received: from localhost ([127.0.0.1]:51870 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1o8qDh-0003Wd-TT for submit <at> debbugs.gnu.org; Tue, 05 Jul 2022 17:36:34 -0400 Received: from mx1.dismail.de ([78.46.223.134]:6762) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <jbranso@HIDDEN>) id 1o8qDf-0003WN-Fp for 56046 <at> debbugs.gnu.org; Tue, 05 Jul 2022 17:36:32 -0400 Received: from mx1.dismail.de (localhost [127.0.0.1]) by mx1.dismail.de (OpenSMTPD) with ESMTP id 1a3b5552 for <56046 <at> debbugs.gnu.org>; Tue, 5 Jul 2022 23:36:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed; d=dismail.de; h= mime-version:date:content-type:from:message-id:subject:to; s= 20190914; bh=lRgguFIapdZWSDVvYsk7Dh7yc9nI0PBXXUf7kIkvqNg=; b=Nqo rlLo3+6H+rEinvWOEylJWlCMAJNfxPJFxDW0gvz60proHtY70R8EVXYGA2S9vIXI 0sG+9TqQpp3AHgLR/ozT2jUE1O/+EXRodoxHbZu/St23pjGm/YRyW0DuS9qmxTc3 02FC5SQE16/zdtgNTBAcS7dzuZHaAf6k6TNiKxPAgxSpkAhB0Uj9gr3u2O23TxQe dKdWDEehKmZez5Pg79oO56GvNSKcfD/oAhJbKtXQz/aLPRkfTWco8puZ1gD2kfkX UsFYI874AuLIIytDgiKy5OKgfNYkWHCYWMKONmZz5K/ymISuijQYk6F4Rk1YxjoO 7keleRb1dL36YFX/NEQ== Received: from smtp1.dismail.de (<unknown> [10.240.26.11]) by mx1.dismail.de (OpenSMTPD) with ESMTP id 809c3a31 for <56046 <at> debbugs.gnu.org>; Tue, 5 Jul 2022 23:36:23 +0200 (CEST) Received: from smtp1.dismail.de (localhost [127.0.0.1]) by smtp1.dismail.de (OpenSMTPD) with ESMTP id 68f28a72 for <56046 <at> debbugs.gnu.org>; Tue, 5 Jul 2022 23:36:23 +0200 (CEST) Received: by dismail.de (OpenSMTPD) with ESMTPSA id fb8412e8 (TLSv1.3:TLS_AES_256_GCM_SHA384:256:NO) for <56046 <at> debbugs.gnu.org>; Tue, 5 Jul 2022 23:36:23 +0200 (CEST) MIME-Version: 1.0 Date: Tue, 05 Jul 2022 21:36:22 +0000 Content-Type: multipart/alternative; boundary="--=_RainLoop_590_926031372.1657056982" X-Mailer: RainLoop/1.16.0a From: jbranso@HIDDEN Message-ID: <1bace22a7dbd8668b9a774b0f975fa53@HIDDEN> Subject: Re: [PATCH] services: mail: add opensmtpd records to enhance opensmtpd-configuration To: 56046 <at> debbugs.gnu.org X-Spam-Score: -0.7 (/) X-Debbugs-Envelope-To: 56046 X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> X-Spam-Score: -1.7 (-) ----=_RainLoop_590_926031372.1657056982 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Here is a video link that may help the first reviewer: https://video.hardlimit.com/w/ixE7Tc1pCqpP3BgriRbYA5 (https://video.hardl= imit.com/w/ixE7Tc1pCqpP3BgriRbYA5) The video walks you through how to play with an example configuration in = emacs-geiser. Thanks, Joshua ----=_RainLoop_590_926031372.1657056982 Content-Type: text/html; charset="utf-8" Content-Transfer-Encoding: quoted-printable <!DOCTYPE html><html><head><meta http-equiv=3D"Content-Type" content=3D"t= ext/html; charset=3Dutf-8" /></head><body><div data-html-editor-font-wrap= per=3D"true" style=3D"font-family: arial, sans-serif; font-size: 13px;"><= br><br><signature></signature>Here is a video link that may help the firs= t reviewer:<br><br><a href=3D"https://video.hardlimit.com/w/ixE7Tc1pCqpP3= BgriRbYA5">https://video.hardlimit.com/w/ixE7Tc1pCqpP3BgriRbYA5</a><br><b= r><br>The video walks you through how to play with an example configurati= on in emacs-geiser.<br><br>Thanks,<br><br>Joshua</div></body></html> ----=_RainLoop_590_926031372.1657056982--
guix-patches@HIDDEN
:bug#56046
; Package guix-patches
.
Full text available.Received: (at 56046) by debbugs.gnu.org; 4 Jul 2022 21:18:27 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Mon Jul 04 17:18:27 2022 Received: from localhost ([127.0.0.1]:48750 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1o8TSc-0001xP-W1 for submit <at> debbugs.gnu.org; Mon, 04 Jul 2022 17:18:27 -0400 Received: from mx1.dismail.de ([78.46.223.134]:44270) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <jbranso@HIDDEN>) id 1o8TSb-0001xD-DU for 56046 <at> debbugs.gnu.org; Mon, 04 Jul 2022 17:18:26 -0400 Received: from mx1.dismail.de (localhost [127.0.0.1]) by mx1.dismail.de (OpenSMTPD) with ESMTP id 76cbd521 for <56046 <at> debbugs.gnu.org>; Mon, 4 Jul 2022 23:18:18 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed; d=dismail.de; h=from:to:cc :subject:date:message-id:mime-version:content-type :content-transfer-encoding; s=20190914; bh=ceJBid/FRI4gvzBm8WZEC A4t0cZHcYhfqjxDCaFbeIs=; b=TLHey6ksSu89jO3AK7tXL4F2q2FYXhz/qKx+v eu9DlRCCoKyX8gFysMQG9fAtpFxh1dAzKAHrYiSsD7I1CilyVWLpD5lDPR15LgRW IkdeB7sohlDd2y3V4EuWTnt9j4nNxrPZ0Fk2AzUnGzjPkpEvCL6y3L31+mtAquVy o8dRdNdk37qUO/AYUS85z9Kj7x1mhnhbH4Wr2hMAUmbtb/8mp/PuzLfpgItNrmKl jcDMeETOzBPyCBTFc+I9KnKXEfhkd+n3ZYkZpsuE6Kxa1RQQ4Tm7Mg0iXRIaY1E1 rkPCVMjBQt8kBVgPU6aScuwu9i0tooKb8PP0sv6xo3ln2kCGQ== Received: from smtp2.dismail.de (<unknown> [10.240.26.12]) by mx1.dismail.de (OpenSMTPD) with ESMTP id 5278a1d4 for <56046 <at> debbugs.gnu.org>; Mon, 4 Jul 2022 23:18:17 +0200 (CEST) Received: from smtp2.dismail.de (localhost [127.0.0.1]) by smtp2.dismail.de (OpenSMTPD) with ESMTP id 6be3844b for <56046 <at> debbugs.gnu.org>; Mon, 4 Jul 2022 23:18:17 +0200 (CEST) Received: by dismail.de (OpenSMTPD) with ESMTPSA id e8b786e8 (TLSv1.3:TLS_AES_256_GCM_SHA384:256:NO); Mon, 4 Jul 2022 23:18:09 +0200 (CEST) From: Joshua Branson <jbranso@HIDDEN> To: 56046 <at> debbugs.gnu.org Subject: [PATCH] services: mail: add opensmtpd records to enhance opensmtpd-configuration. Version 2 Date: Mon, 4 Jul 2022 17:17:59 -0400 Message-Id: <20220704211759.8314-1-jbranso@HIDDEN> X-Mailer: git-send-email 2.36.1 MIME-Version: 1.0 Content-Type: text/plain; charset=y Content-Transfer-Encoding: 8bit X-Debbugs-Envelope-To: 56046 Cc: Joshua Branson <jbranso@HIDDEN> X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> Openmstpd-configuration may only be configured by a config-file. This patch, enables one to configure opensmtpd by using some guile record types (defined via define-record-type*). * gnu/services/mail.scm: New records (opensmtpd-table-configuration), (opensmtpd-ca-configuration), (opensmtpd-pki-configuration), (opensmtpd-action-local-delivery-configuration), (opensmtpd-maildir-configuration), (opensmtpd-mda-configuration), (opensmtpd-action-relay-configuration), (opensmtpd-option-configuration), (opensmtpd-filter-phase-configuration), (opensmtpd-filter-configuration), (opensmtpd-listen-on-configuration), (opensmtpd-listen-on-socket-configuration), (opensmtpd-match-configuration), (opensmtpd-smtp-configuration), (opensmtpd-srs-configuration), (opensmtpd-queue-configuration), and (opensmtpd-configuration). New procedures: false?, is-value-right-type, add-comma-or-string, file-exists?, list-of-procedures->string, string-in-list?, my-sanitize, opensmtpd-filter-chain?, throw-error-duplicate-option, sanitize-list-of-options-for-match-configuration, sanitize-filters, list-has-duplicates-or-non-filters?, filter-phase-has-message-and-value?, filter-phase-decision-lacks-proper-message?, filter-phase-lacks-proper-value?, filter-phase-has-incorrect-junk-or-bypass?, filter-phase-junks-after-commit?, list-of-unique-filter-or-filter-phase?, throw-error, contains-duplicate?, list-of-type?, list-of-strings?, list-of-unique-opensmtpd-option-configuration?, list-of-opensmtpd-ca-configuration?, list-of-opensmtpd-pki-configuration?, list-of-opensmtpd-listen-on-configuration?, list-of-unique-opensmtpd-match-configuration?, list-of-strings->string, assoc-list? assoc-list, variable->string, table-whose-data-are-assoc-list?, table-whose-data-are-a-list-of-strings?, assoc-list->string, opensmtpd-table-configuration->string, opensmtpd-listen-on-configuration->string, opensmtpd-listen-on-socket-configuration->string, opensmtpd-action-relay-configuration->string, opensmtpd-lmtp-configuration->string, opensmtpd-mda-configuration->string, opensmtpd-maildir-configuration->string, opensmtpd-action-local-delivery-configuration->string, opensmtpd-action->string, opensmtpd-option-configuration->string, opensmtpd-match-configuration->string, opensmtpd-ca-configuration->string, opensmtpd-pki-configuration->string, generate-filter-chain-name, opensmtpd-filter-chain->string, opensmtpd-filter-phase-configuration->string, opensmtpd-filters->string, opensmtpd-configuration-listen->string, opensmtpd-configuration-srs->string, opensmtpd-smtp-configuration->string, opensmtpd-configuration-queue->string, get-opensmtpd-actions, get-opensmtpd-pki-configurations, get-opensmtpd-filters, flatten, get-opensmtpd-tables, opensmtpd-configuration-fieldname->string, list-of-records->string, opensmtpd-configuration->mixed-text-file. * doc/guix.texi added documentation for the new records for opensmtpd. --- doc/guix.texi | 1051 ++++++++++++++++++++- gnu/services/mail.scm | 2016 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 3056 insertions(+), 11 deletions(-) diff --git a/doc/guix.texi b/doc/guix.texi index eda0956260..e8564240d1 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -24849,14 +24849,59 @@ could instantiate a dovecot service like this: @subsubheading OpenSMTPD Service @deffn {Scheme Variable} opensmtpd-service-type -This is the type of the @uref{https://www.opensmtpd.org, OpenSMTPD} -service, whose value should be an @code{opensmtpd-configuration} object -as in this example: - -@lisp -(service opensmtpd-service-type - (opensmtpd-configuration - (config-file (local-file "./my-smtpd.conf")))) +OpenSMTPD is an easy-to-use mail transfer agent (MTA). Its configuration file is +throughly documented in @code{man 5 smtpd.conf}. OpenSMTPD @strong{listens} for incoming +mail and @strong{matches} the mail to @strong{actions}. The following records represent those +stages: + +@multitable {aaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} +@item @strong{listens} +@tab @code{<opensmtpd-listen-on-configuration} +@item +@tab @code{<opensmtpd-listen-on-socket-configuration>} +@item +@tab +@item @strong{matches} +@tab @code{<opensmtpd-match-configuration>} +@item +@tab +@item @strong{actions} +@tab @code{<opensmtpd-action-local-delivery-configuration>} +@item +@tab @code{<opensmtpd-action-relay-configuration>} +@end multitable + +Additionally, each @code{<opensmtpd-listen-on-configuration>} and +@code{<opensmtpd-listen-on-socket-configuration>} may use a list of +@code{<opensmtpd-filter-configuration>}, and/or +@code{<opensmtpd-filter-phase-configuration>} records to filter email/spam. Also +numerous records' fieldnames use @code{<opensmtpd-table-configuration>} to hold lists +or key value pairs of data. + +A simple example configuration is below: + +@lisp +(let ((smtp.gnu.org (opensmtpd-pki-configuration + (domain "smtp.gnu.org") + (cert "file.cert") + (key "file.key")))) + (service opensmtpd-service-type + (opensmtpd-configuration + (listen-ons (list + (opensmtpd-listen-on-configuration + (pki smtp.gnu.org)) + (opensmtpd-listen-on-configuration + (pki smtp.gnu.org) + (secure-connection "smtps")))) + (matches (list + (opensmtpd-match-configuration + (action + (opensmtpd-action-local-delivery-configuration + (name "local-delivery")))) + (opensmtpd-match-configuration + (action + (opensmtpd-action-relay-configuration + (name "relay"))))))))) @end lisp @end deffn @@ -24873,9 +24918,999 @@ it listens on the loopback network interface, and allows for mail from users and daemons on the local machine, as well as permitting email to remote servers. Run @command{man smtpd.conf} for more information. +@item @code{bounce} (default: @code{(list "4h")}) + +@code{bounce} is a list of strings, which send warning messages to the envelope +sender when temporary delivery failures cause a message to remain in the +queue for longer than string delay. Each string delay parameter consists +of a string beginning with a positive decimal integer and a unit 's', 'm', 'h', +or 'd'. At most four delay parameters can be specified. + +@item @code{listen-ons} (default: @code{(list (opensmtpd-listen-on-configuration))}) + +@code{listen-ons} is a list of @code{<opensmtpd-listen-on-configuration>} records. +This list details what interfaces and ports OpenSMTPD listens on as well as +other information. + +@item @code{listen-on-socket} (default: @code{(opensmtpd-listen-on-socket-configuration-configuration)}) + +Listens for incoming connections on the Unix domain socket. + +@item @code{includes} (default: @code{#f}) + +@code{includes} is a list of string filenames. Each filename's contents is +additional configuration that is inserted into the top of the configuration +file. + +@item @code{matches} default: + +@lisp + (list (opensmtpd-match-configuration + (action (opensmtpd-action-local-delivery-configuration + (name "local") + (method "mbox"))) + (for (opensmtpd-option-configuration + (option "for local")))) + (opensmtpd-match-configuration + (action (opensmtpd-action-relay-configuration + (name "outbound"))) + (from (opensmtpd-option-configuration + (option "from local"))) + (for (opensmtpd-option-configuration + (option "for any"))))) +@end lisp + +@code{matches} is a list of @code{<opensmtpd-match-configuration>} records, which +matches incoming mail and sends it to a correspending action. The match +records are evaluated sequentially, with the first match winning. If an +incoming mail does not match any match records, then it is rejected. +@c put this backin? @end itemize + +@c put this back in? @itemize +@item @code{mta-max-deferred} (default: @code{100}) + +When delivery to a given host is suspended due to temporary failures, cache +at most number envelopes for that host such that they can be delivered as +soon as another delivery succeeds to that host. The default is 100. + +@item @code{queue} (default: @code{#f}) + +@code{queue} expects an @code{<opensmtpd-queue-configuration>} record. With it, one may +compress and encrypt queue-ed emails as well as set the default expiration +time for temporarily undeliverable messages. + +@item @code{smtp} (default: @code{#f}) + +@code{smtp} expects an @code{<opensmtpd-smtp-configuration>} record, which lets one +specifiy how large email may be along with other settings. + +@item @code{srs} (default: @code{#f}) + +@code{srs} expects an @code{<opensmtpd-srs-configuration>} record, which lets one set +up SRS, the Sender Rewritting Scheme. @end table @end deftp +@itemize +@item +Data Type: opensmtpd-listen-on-configuration + +Data type representing the configuration of an +@code{<opensmtpd-listen-on-configuration>}. Listen on the fieldname @code{interface} for +incoming connections, using the same syntax as for ifconfig(8). The interface +parameter may also be an string interface group, an string IP address, or a +string domain name. Listening can optionally be restricted to a specific +address fieldname @code{family}, which can be either ``inet4'' or ``inet6''. + +@itemize +@item @code{interface} (default: ``lo'') + +The string interface to listen for incoming connections. These interface can +usually be found by the command @code{ip link}. + +@item @code{family} (default: @code{#f}) + +The string IP family to use. Valid strings are ``inet4'' or ``inet6''. + +@item @code{auth} (default: @code{#f}) + +Support SMTPAUTH: clients may only start SMTP transactions after successful +authentication. If @code{auth} is @code{#t}, then users are authenticated against +their own normal login credentials. Alternatively @code{auth} may be an +@code{<opensmtpd-table-configuration>} whose users are authenticated against +their passwords. + +@item @code{auth-optional} (default: @code{#f}) + +Support SMTPAUTH optionally: clients need not authenticate, but may do so. +This allows the @code{<opensmtpd-listen-on-configuration>} to both accept +incoming mail from untrusted senders and permit outgoing mail from +authenticated users (using @code{<opensmtpd-match-configuration>} fieldname +@code{auth}). It can be used in situations where it is not possible to listen on +a separate port (usually the submission port, 587) for users to +authenticate. + +@item @code{filters} (default: @code{#f}) + +A list of one or many @code{<opensmtpd-filter-configuration>} or +@code{<opensmtpd-filter-phase-configuration>} records. The filters are applied +sequentially. These records listen and filter on connections handled by this +listener. + +@item @code{hostname} (default: @code{#f}) + +Use string ``hostname'' in the greeting banner instead of the default server +name. + +@item @code{hostnames} (default: @code{#f}) + +Override the server name for specific addresses. Use a +@code{<opensmtpd-table-configuration>} containing a mapping of string IP +addresses to hostnames. If the address on which the connection arrives +appears in the mapping, the associated hostname is used. + +@item @code{mask-src} (default: @code{#f}) + +If @code{#t}, then omit the from part when prepending “Received” headers. + +@item @code{disable-dsn} (default: @code{#f}) + +When @code{#t}, then disable the DSN (Delivery Status Notification) extension. + +@item @code{pki} (default: @code{#f}) + +For secure connections, use an @code{<opensmtpd-pki-configuration>} +to prove a mail server's identity. + +@item @code{port} (default: @code{#f}) + +Listen on the integer port instead of the default port of 25. + +@item @code{proxy-v2} (default: @code{#f}) + +If @code{#t}, then support the PROXYv2 protocol, rewriting appropriately source +address received from proxy. + +@item @code{received-auth} (default: @code{#f}) + +If @code{#t}, then in “Received” headers, report whether the session was +authenticated and by which local user. + +@item @code{senders} (default: @code{#f}) + +Look up the authenticated user in the supplied +@code{<opensmtpd-table-configuration>} to find the email addresses that user is +allowed to submit mail as. + +@item @code{secure-connection} (default: @code{#f}) + +This is a string of one of these options: + +@multitable {aaaaaaaaaaaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} +@item ``smtps'' +@tab Support SMTPS, by default on port 465. +@item ``tls'' +@tab Support STARTTLS, by default on port 25. +@item ``tls-require-verify'' +@tab Like tls, but force clients to establish +@item +@tab a secure connection before being allowed to +@item +@tab start an SMTP transaction. With the verify +@item +@tab option, clients must also provide a valid +@item +@tab certificate to establish an SMTP session. +@end multitable + +@item @code{tag} (default: @code{#f}) + +Clients connecting to the listener are tagged with the given string tag. +@end itemize + +@item Data Type: opensmtpd-listen-on-socket-configuration + +Data type representing the configuration of an +@code{<opensmtpd-listen-on-socket-configuration>}. Listen for incoming SMTP +connections on the Unix domain socket @samp{/var/run/smtpd.sock}. This is done by +default, even if the directive is absent. + +@itemize +@item @code{filters} (default: @code{#f}) + +A list of one or many @code{<opensmtpd-filter-configuration>} or +@code{<opensmtpd-filter-phase-configuration>} records. These filter incoming +connections handled by this listener. + +@item @code{mask-src} (default: @code{#f}) + +If @code{#t}, then omit the from part when prepending “Received” headers. + +@item @code{tag} (default: @code{#f}) + +Clients connecting to the listener are tagged with the given string tag. +@end itemize + +@item Data Type: opensmtpd-match-configuration + +This data type represents the configuration of an +@code{<opensmtpd-match-configuration>} record. + +If at least one mail envelope matches the options of one match record, receive +the incoming message, put a copy into each matching envelope, and atomically +save the envelopes to the mail spool for later processing by the respective +@code{<opensmtpd-action-configuration>} found in fieldname @code{action}. + +@itemize +@item @code{action} (default: @code{#f}) + +If mail matches this match configuration, then do this action. Valid values +include @code{<opensmtpd-action-local-delivery-configuration>} or +@code{<opensmtpd-action-relay-configuration>}. + +@item @code{options} (default: @code{#f}) @code{<opensmtpd-option-configuration>} +The fieldname 'option' is a list of unique +@code{<opensmtpd-option-configuration>} records. + +Each @code{<opensmtpd-option-configuration>} record's fieldname 'option' has some +mutually exclusive options: there can be only one ``for'' and only one ``from'' option. + +@multitable {aaaaaaaaaaaaaaaaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} +@headitem for +@tab from +@item only use one of the following: +@tab only use one of the following: +@item ``for any'' +@tab ``from any'' +@item ``for local'' +@tab ``from auth'' +@item ``for domain'' +@tab ``from local'' +@item ``for rcpt-to'' +@tab ``from mail-from'' +@item +@tab ``from socket'' +@item +@tab ``from src'' +@end multitable + +The following matching options are supported and can all be negated (via not +#t). The options that support a table (anything surrounded with '<' and '>' +eg: <table>), also support specifying regex via (regex #t). + +@itemize +@item @samp{for any} + +Specify that session may address any destination. + +@item @samp{for local} + +Specify that session may address any local domain. This is the default, +and may be omitted. + +@item @samp{for domain _domain_ | <domain>} + +Specify that session may address the string or list table domain. + +@item @samp{for rcpt-to _recipient_ | <recipient>} + +Specify that session may address the string or list table recipient. + +@item @samp{from any} + +Specify that session may originate from any source. + +@item @samp{from auth} + +Specify that session may originate from any authenticated user, no matter +the source IP address. + +@item @samp{from auth _user_ | <user>} + +Specify that session may originate from authenticated user or user list +user, no matter the source IP address. + +@item @samp{from local} + +Specify that session may only originate from a local IP address, or from +the local enqueuer. This is the default, and may be omitted. + +@item @samp{from mail-from _sender_ | <sender>} + +Specify that session may originate from sender or table sender, no +matter the source IP address. + +@item @samp{from rdns} + +Specify that session may only originate from an IP address that resolves +to a reverse DNS@. + +@item @samp{from rdns _hostname_ | <hostname>} + +Specify that session may only originate from an IP address that resolves +to a reverse DNS matching string or list string hostname. + +@item @samp{from socket} + +Specify that session may only originate from the local enqueuer. + +@item @samp{from src _address_ | <address>} + +Specify that session may only originate from string or list table address +which can be a specific address or a subnet expressed in CIDR-notation. + +@item @samp{auth} + +Matches transactions which have been authenticated. + +@item @samp{auth _username_ | <username>} + +Matches transactions which have been authenticated for user or user list +username. + +@item @samp{helo _helo-name_ | <helo-name>} + +Specify that session's HELO / EHLO should match the string or list table +helo-name. + +@item @samp{mail-from _sender_ | <sender>} + +Specify that transactions's MAIL FROM should match the string or list +table sender. + +@item @samp{rcpt-to _recipient_ | <recipient>} + +Specify that transaction's RCPT TO should match the string or list table +recipient. + +@item @samp{tag tag} +Matches transactions tagged with the given tag. + +@item @samp{tls} +Specify that transaction should take place in a TLS channel. +@end itemize + +Here is a simple example: +@lisp + (opensmtpd-option-configuration + (not #t) + (regex #f) + (option "for domain") + (data (opensmtpd-table-configuration + (name "domain-table") + (data (list "gnu.org" "dismail.de"))))) +@end lisp + +The mail must NOT come from the domains @samp{gnu.org} or @samp{dismail.de}. + +@item Data Type: opensmtpd-option-configuration +@end itemize + +@item Data Type: opensmtpd-action-local-delivery-configuration + +This data type represents the configuration of an +@code{<opensmtpd-action-local-delivery-configuration>} record. + +@itemize +@item +@code{name} (default: @code{#f}) + +@code{name} is the string name of the relay action. + +@item @code{method} (default: @code{"mbox"}) + +The email delivery option. Valid options are: + +@itemize +@item @code{"mbox"} + +Deliver the message to the user's mbox with mail.local(8). + +@item @code{"expand-only"} + +Only accept the message if a delivery method was specified in an aliases +or .forward file. + +@item @code{"forward-only"} + +Only accept the message if the recipient results in a remote address after +the processing of aliases or forward file. + +@item @code{<opensmtpd-lmtp-configuration>} + +Deliver the message to an LMTP server at +@code{<opensmtpd-lmtp-configuration>}'s fieldname @code{destination}. The location +may be expressed as string host:port or as a UNIX socket. Optionally, +@code{<opensmtpd-lmtp-configuration>}'s fieldname @code{rcpt-to} might be specified +to use the recipient email address (after expansion) instead of the local +user in the LMTP session as RCPT TO@. + +@item @code{<opensmtpd-maildir-configuration>} + +Deliver the message to the maildir in +@code{<opensmtpd-maildir-configuration>}'s fieldname @code{pathname} if specified, +or by default to @samp{~/Maildir}. + +The pathname may contain format specifiers that are expanded before use +(see the below section about Format Specifiers). + +If @code{<opensmtpd-maildir-configuration>}'s record fieldname @code{junk} is @code{#t}, +then message will be moved to the ‘Junk’ folder if it contains a positive +‘X-Spam’ header. This folder will be created under fieldname @code{pathname} if +it does not yet exist. + +@item @code{<opensmtpd-mda-configuration>} + +Delegate the delivery to the @code{<opensmtpd-mda-configuration>}'s fieldname +@code{command} (type string) that receives the message on its standard input. + +The @code{command} may contain format specifiers that are expanded before use +(see Format Specifiers). +@end itemize + +@item @code{alias} (default: @code{#f}) + +Use the mapping table for aliases expansion. @code{alias} is an +@code{<opensmtpd-table-configuration>}. + +@item @code{ttl} (default: @code{#f}) + +@code{ttl} is a string specify how long a message may remain in the queue. It's +format is @samp{n@{s|m|h|d@}}. eg: ``4m'' is four minutes. + +@item @code{user} (default: @code{#f} ) + +@code{user} is the string username for performing the delivery, to be looked up +with getpwnam(3). + +This is used for virtual hosting where a single username is in charge of +handling delivery for all virtual users. + +This option is not usable with the mbox delivery method. + +@item @code{userbase} (default: @code{#f}) + +@code{userbase} is an @code{<opensmtpd-table-configuration>} record for mapping user +lookups instead of the getpwnam(3) function. + +The fieldnames @code{user} and @code{userbase} are mutually exclusive. + +@item @code{virtual} (default: @code{#f}) + +@code{virtual} is an @code{<opensmtpd-table-configuration>} record is used for virtual +expansion. +@end itemize + +@item Data Type: opensmtpd-action-relay-configuration + +This data type represents the configuration of an +@code{<opensmtpd-action-relay-configuration>} record. + +@itemize +@item @code{name} (default: @code{#f}) + +@code{name} is the string name of the relay action. + +@item @code{backup} (default: @code{#f}) + +When @code{#t}, operate as a backup mail exchanger delivering messages to any +mail exchanger with higher priority. + +@item @code{backup-mx} (default: @code{#f}) + +Operate as a backup mail exchanger delivering messages to any mail exchanger +with higher priority than mail exchanger identified as string name. + +@item @code{helo} (default: @code{#f}) + +Advertise string heloname as the hostname to other mail exchangers during +the HELO phase. + +@item @code{helo-src} (default: @code{#f} ) + + Use the mapping @code{<openmstpd-table-configuration>} to look up a hostname +matching the source address, to advertise during the HELO phase. + +@item @code{domain} (default: @code{#f}) + +Do not perform MX lookups but look up destination domain in an +@code{<opensmtpd-table-configuration>} and use matching relay url as relay host. + +@item @code{host} (default: @code{#f}) + +Do not perform MX lookups but relay messages to the relay host described by +the string relay-url. The format for relay-url is +@samp{[proto://[label@@]]host[:port]}. The following protocols are available: + +@multitable {aaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} +@item smtp +@tab Normal SMTP session with opportunistic STARTTLS (the default). +@item smtp+tls +@tab Normal SMTP session with mandatory STARTTLS@. +@item smtp+notls +@tab Plain text SMTP session without TLS@. +@item lmtp +@tab LMTP session. port is required. +@item smtps +@tab SMTP session with forced TLS on connection, default port is +@item +@tab 465. +@end multitable + +Unless noted, port defaults to 25. + +The label corresponds to an entry in a credentials table, as documented in +@samp{table(5)}. It is used with the @samp{"smtp+tls"} and @samp{"smtps"} protocols for +authentication. Server certificates for those protocols are verified by +default. + +@item @code{pki} (default: @code{#f}) + +For secure connections, use the certificate associated with +@code{<opensmtpd-pki-configuration>} (declared in a pki directive) to prove the +client's identity to the remote mail server. + +@item @code{srs} (default: @code{#f}) + +If @code{#t}, then when relaying a mail resulting from a forward, use the Sender +Rewriting Scheme to rewrite sender address. + +@item @code{tls} (default: @code{#f}) boolean or string ``no-verify'' + +When @code{#t}, Require TLS to be used when relaying, using mandatory STARTTLS by +default. When used with a smarthost, the protocol must not be +@samp{"smtp+notls://"}. When string @code{"no-verify"}, then do not require a valid +certificate. + +@item @code{auth} (default: @code{#f}) @code{<opensmtpd-table-configuration>} + +Use the alist @code{<opensmtpd-table-configuration>} for connecting to relay-url +using credentials. This option is usable only with fieldname @code{host} option. + +@item @code{mail-from} (default: @code{#f}) string + +Use the string mailaddress as MAIL FROM address within the SMTP transaction. + +@item @code{src} (default: @code{#f}) string | @code{<opensmtpd-table-configuration>} + +Use the string or @code{<opensmtpd-table-configuration>} sourceaddr for the +source IP address, which is useful on machines with multiple interfaces. If +the list contains more than one address, all of them are used in such a way +that traffic is routed as efficiently as possible. +@end itemize + +@item Data Type: opensmtpd-filter-configuration + +This data type represents the configuration of an +@code{<opensmtpd-filter-configuration>}. This is the filter record one should use +if they want to use an external package to filter email eg: rspamd or +spamassassin. + +@itemize +@item @code{name} (default: @code{#f}) + +The string name of the filter. + +@item @code{proc} (default: @code{#f}) + +The string command or process name. If @code{proc-exec} is @code{#t}, @code{proc} is +treated as a command to execute. Otherwise, it is a process name. + +@item @code{proc-exec} (default: @code{#f}) +@end itemize + +@item Data Type: opensmtpd-filter-phase-configuration + +This data type represents the configuration of an +@code{<opensmtpd-filter-phase-configuration>}. + +In a regular workflow, smtpd(8) may accept or reject a message based only on +the content of envelopes. Its decisions are about the handling of the message, +not about the handling of an active session. + +Filtering extends the decision making process by allowing smtpd(8) to stop at +each phase of an SMTP session, check that options are met, then decide if a +session is allowed to move forward. + +With filtering via an @code{<opensmtpd-filter-phase-configuration>} record, a +session may be interrupted at any phase before an envelope is complete. A +message may also be rejected after being submitted, regardless of whether the +envelope was accepted or not. + +@itemize +@item @code{name} (default: @code{#f}) + +The string name of the filter phase. + +@item @code{phase-name} (default: @code{#f}) + +The string name of the phase. Valid values are: + +@multitable {aaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} +@item ``connect'' +@tab upon connection, before a banner is displayed +@item ``helo'' +@tab after HELO command is submitted +@item ``ehlo'' +@tab after EHLO command is submitted +@item ``mail-from'' +@tab after MAIL FROM command is submitted +@item ``rcpt-to'' +@tab after RCPT TO command is submitted +@item ``data'' +@tab after DATA command is submitted +@item ``commit'' +@tab after message is fully is submitted +@end multitable + +@item @code{options} (default @code{#f}) + +A list of unique @code{<opensmtpd-option-configuration>} records. + +At each phase, various options, specified by a list of +@code{<opensmtpd-option-configuration>}, may be checked. The +@code{<opensmtpd-option-configuration>}'s fieldname 'option' values of: ``fcrdns'', +``rdns'', and ``src'' data are available in all phases, but other data must have +been already submitted before they are available. Options with a @samp{<table>} +next to them require the @code{<opensmtpd-option-configuration>}'s fieldname +@code{data} to be an @code{<opensmtpd-table-configuration>}. There are the available +options: + +@multitable {aaaaaaaaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} +@item fcrdns +@tab forward-confirmed reverse DNS is valid +@item rdns +@tab session has a reverse DNS +@item rdns <table> +@tab session has a reverse DNS in table +@item src <table> +@tab source address is in table +@item helo <table> +@tab helo name is in table +@item auth +@tab session is authenticated +@item auth <table> +@tab session username is in table +@item mail-from <table> +@tab sender address is in table +@item rcpt-to <table> +@tab recipient address is in table +@end multitable + +These conditions may all be negated by setting +@code{<opensmtpd-option-configuration>}'s fieldname @code{not} to @code{#t}. + +Any conditions that require a table may indicate that tables include regexs +setting @code{<opensmtpd-option-configuration>}'s fieldname @code{regex} to @code{#t}. + +@item @code{decision} + +A string decision to be taken. Some decisions require an @code{message} or +@code{value}. Valid strings are: + +@multitable {aaaaaaaaaaaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} +@item ``bypass'' +@tab the session or transaction bypasses filters +@item ``disconnect'' message +@tab the session is disconnected with message +@item ``junk'' +@tab the session or transaction is junked, i.e., an +@item +@tab ‘X-Spam: yes’ header is added to any messages +@item ``reject'' message +@tab the command is rejected with message +@item ``rewrite'' value +@tab the command parameter is rewritten with value +@end multitable + +Decisions that involve a message require that the message be RFC valid, +meaning that they should either start with a 4xx or 5xx status code. +Descisions can be taken at any phase, though junking can only happen before +a message is committed. + +@item @code{message} (default @code{#f}) + +A string message beginning with a 4xx or 5xx status code. + +@item @code{value} (default: @code{#f}) + +A number value. @code{value} and @code{message} are mutually exclusive. +@end itemize + +@item Data Type: opensmtpd-option-configuration + +This data type represents the configuration of an +@code{<opensmtpd-option-configuration>}, which is used by +@code{<opensmtpd-filter-phase-configuration>} and @code{<opensmtpd-match-configuration>} +to match various options for email. + +@itemize +@item @code{conditition} (default @code{#f}) + +A string option to be taken. Some options require a string or an +@code{<opensmtpd-table-configuration>} via the fieldname data. When the option +record is used inside of an @code{<opensmtpd-filter-phase-configuration>}, then +valid strings are: + +At each phase, various options may be matched. The fcrdns, rdns, and src +data are available in all phases, but other data must have been already +submitted before they are available. + +@multitable {aaaaaaaaaaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} +@item ``fcrdns'' +@tab forward-confirmed reverse DNS is valid +@item ``rdns'' +@tab session has a reverse DNS +@item ``rdns'' <table> +@tab session has a reverse DNS in table +@item ``src'' <table> +@tab source address is in table +@item ``helo'' <table> +@tab helo name is in table +@item ``auth'' +@tab session is authenticated +@item ``auth'' <table> +@tab session username is in table +@item ``mail-from'' <table> +@tab sender address is in table +@item ``rcpt-to'' <table> +@tab recipient address is in table +@end multitable + +When @code{<opensmtpd-option-configuration>} is used inside of an +@code{<opensmtpd-match-configuration>}, then valid strigs for fieldname @code{option} +are: ``for'', ``for any'', ``for local'', ``for domain'', ``for rcpt-to'', ``from any'' +``from auth'', ``from local'', ``from mail-from'', ``from rdns'', ``from socket'', +``from src'', ``auth'', ``helo'', ``mail-from'', ``rcpt-to'', ``tag'', or ``tls''. + +@item @code{data} (default @code{#f}) @code{<opensmtpd-table-configuration>} + +Some options require a table to be present. One would specify that table +here. +@item @code{regex} (default: @code{#f}) boolean + +Any options using a table may indicate that tables hold regex by +prefixing the table name with the keyword regex. + +@item @code{not} (default: @code{#f}) boolean + +When @code{#t}, this option record is negated. +@end itemize + +@item Data Type: opensmtpd-table-configuration + +This data type represents the configuration of an +@code{<opensmtpd-table-configuration>}. + +@itemize +@item @code{name} (default @code{#f}) + +@code{name} is the name of the @code{<opensmtpd-table-configuration>} record. + +@item @code{data} (default: @code{#f}) + +@code{data} expects a list of strings or an alist, which is a list of +cons cells. eg: @code{(data (list ("james" . "password")))} OR +@code{(data (list ("gnu.org" "fsf.org")))}. +@end itemize + +@item Data Type: opensmtpd-pki-configuration + +This data type represents the configuration of an +@code{<opensmtpd-pki-configuration>}. + +@itemize +@item @code{domain} (default @code{#f}) + +@code{domain} is the string name of the @code{<opensmtpd-pki-configuration>} record. + +@item @code{cert} (default: @code{#f}) + +@code{cert} (default: @code{#f}) + +@code{cert} is the string certificate filename to use for this pki. + +@item @code{key} (default: @code{#f}) + +@code{key} is the string certificate falename to use for this pki. + +@item @code{dhe} (default: @code{"none"}) + +Specify the DHE string parameter to use for DHE cipher suites with host +pkiname. Valid parameter values are ``none'', ``legacy'', or ``auto''. For ``legacy'', a +fixed key length of 1024 bits is used, whereas for ``auto'', the key length is +determined automatically. The default is ``none'', which disables DHE cipher +suites. +@end itemize + +@item Data Type: opensmtpd-maildir-configuration + +@itemize +@item @code{pathname} (default: @code{"~/Maildir"}) + +Deliver the message to the maildir if pathname if specified, or by default +to @samp{~/Maildir}. + +The pathname may contain format specifiers that are expanded before use +(see FORMAT SPECIFIERS). + +@item @code{junk} (default: @code{#f}) + +If the junk argument is @code{#t}, then the message will be moved to the @samp{‘Junk’} +folder if it contains a positive @samp{‘X-Spam’} header. This folder will be +created under pathname if it does not yet exist. +@end itemize + +@item Data Type: opensmtpd-mda-configuration + +@itemize +@item @code{name} + +The string name for this MDA command. + +@item @code{command} + +Delegate the delivery to a command that receives the message on its standard +input. + +The command may contain format specifiers that are expanded before use (see +FORMAT SPECIFIERS). +@end itemize + +@item Data Type: opensmtpd-queue-configuration + +@itemize +@item @code{compression} (default @code{#f}) + +Store queue files in a compressed format. This may be useful to save disk +space. + +@item @code{encryption} (default @code{#f}) + +Encrypt queue files with EVP@math{_aes}@math{_256}@math{_gcm}(3). If no key is specified, it is +read with getpass(3). If the string stdin or a single dash (‘-’) is given +instead of a key, the key is read from the standard input. + +@item @code{ttl-delay} (default @code{#f}) + +Set the default expiration time for temporarily undeliverable messages, +given as a positive decimal integer followed by a unit s, m, h, or d. The +default is four days (``4d''). +@end itemize + +@item Data Type: opensmtpd-smtp-configuration + +Data type representing an @code{<opensmtpd-smtp-configuration>} record. + +@itemize +@item @code{ciphers} (default: @code{#f}) + +Set the control string for SSL@math{_CTX}@math{_set}@math{_cipher}@math{_list}(3). The default is + ``HIGH:!aNULL:!MD5''. + +@item @code{limit-max-mails} (default: @code{100}) + +Limit the number of messages to count for each sessio + +@item @code{limit-max-rcpt} (default: @code{1000}) + +Limit the number of recipients to count for each transaction. + +@item @code{max-message-size} (default: @code{35M}) + +Reject messages larger than size, given as a positive number of bytes or as +a string to be parsed with scan@math{_scaled}(3). + +@item @code{sub-addr-delim character} (default: @code{+}) + +When resolving the local part of a local email address, ignore the ASCII +character and all characters following it. This is helpful for email +filters. @samp{"admin+bills@@gnu.org"} is the same email address as +@samp{"admin@@gnu.org"}. BUT an email filter can filter emails addressed to first +email address into a 'Bills' email folder. +@end itemize + +@item Data Type: opensmtpd-srs-configuration + +@itemize +@item @code{key} (default: @code{#f}) + +Set the secret key to use for SRS, the Sender Rewriting Scheme. + +@item @code{backup-key} (default: @code{#f}) + +Set a backup secret key to use as a fallback for SRS@. This can be used to +implement SRS key rotation. + +@item @code{ttl-delay} (default: @code{"4d"}) + +Set the time-to-live delay for SRS envelopes. After this delay, a bounce +reply to the SRS address will be discarded to limit risks of forged +addresses. +@end itemize + +@item Format Specifiers + +Some configuration records support expansion of their parameters at +runtime. Such records (for example +@code{<opensmtpd-maildir-configuration>}, @code{<opensmtpd-mda-configuration>}) may use +format specifiers which are expanded before delivery or relaying. The +following formats are currently supported: + +@multitable {aaaaaaaaaaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} +@item @samp{%@{sender@}} +@tab sender email address, may be empty string +@item @samp{%@{sender.user@}} +@tab user part of the sender email address, may be empty +@item @samp{%@{sender.domain@}} +@tab domain part of the sender email address, may be empty +@item @samp{%@{rcpt@}} +@tab recipient email address +@item @samp{%@{rcpt.user@}} +@tab user part of the recipient email address +@item @samp{%@{rcpt.domain@}} +@tab domain part of the recipient email address +@item @samp{%@{dest@}} +@tab recipient email address after expansion +@item @samp{%@{dest.user@}} +@tab user part after expansion +@item @samp{%@{dest.domain@}} +@tab domain part after expansion +@item @samp{%@{user.username@}} +@tab local user +@item @samp{%@{user.directory@}} +@tab home directory of the local user +@item @samp{%@{mbox.from@}} +@tab name used in mbox From separator lines +@item @samp{%@{mda@}} +@tab mda command, only available for mda wrappers +@end multitable + +Expansion formats also support partial expansion using the optional bracket notations +with substring offset. For example, with recipient domain @samp{“example.org”}: + +@multitable {aaaaaaaaaaaaaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaa} +@item @samp{%@{rcpt.domain[0]@}} +@tab expands to “e” +@item @samp{%@{rcpt.domain[1]@}} +@tab expands to “x” +@item @samp{%@{rcpt.domain[8:]@}} +@tab expands to “org” +@item @samp{%@{rcpt.domain[-3:]@}} +@tab expands to “org” +@item @samp{%@{rcpt.domain[0:6]@}} +@tab expands to “example” +@item @samp{%@{rcpt.domain[0:-4]@}} +@tab expands to “example” +@end multitable + +In addition, modifiers may be applied to the token. For example, with recipient +@samp{“User+Tag@@Example.org”}: + +@multitable {aaaaaaaaaaaaaaaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} +@item @samp{%@{rcpt:lowercase@}} +@tab expands to “user+tag@@example.org” +@item @samp{%@{rcpt:uppercase@}} +@tab expands to “USER+TAG@@EXAMPLE.ORG” +@item @samp{%@{rcpt:strip@}} +@tab expands to “User@@Example.org” +@item @samp{%@{rcpt:lowercasestrip@}} +@tab expands to “user@@example.org” +@end multitable + +For security concerns, expanded values are sanitized and potentially dangerous +characters are replaced with ‘:’. In situations where they are desirable, the +“raw” modifier may be applied. For example, with recipient +@samp{“user+t?g@@example.org”}: + +@multitable {aaaaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} +@item @samp{%@{rcpt@}} +@tab expands to “user+t:g@@example.org” +@item @samp{%@{rcpt:raw@}} +@tab expands to “user+t?g@@example.org” +@end multitable +@end itemize + @subsubheading Exim Service @cindex mail transfer agent (MTA) diff --git a/gnu/services/mail.scm b/gnu/services/mail.scm index d99743ac31..2a344e303e 100644 --- a/gnu/services/mail.scm +++ b/gnu/services/mail.scm @@ -57,8 +57,143 @@ (define-module (gnu services mail) mailbox-configuration namespace-configuration + opensmtpd-table-configuration + opensmtpd-table-configuration? + opensmtpd-table-configuration-name + opensmtpd-table-configuration-file-db + opensmtpd-table-configuration-data + + opensmtpd-ca-configuration + opensmtpd-ca-configuration? + opensmtpd-ca-configuration-name + opensmtpd-ca-configuration-file + + opensmtpd-pki-configuration + opensmtpd-pki-configuration? + opensmtpd-pki-configuration-domain + opensmtpd-pki-configuration-cert + opensmtpd-pki-configuration-key + opensmtpd-pki-configuration-dhe + + opensmtpd-action-local-delivery-configuration + opensmtpd-action-local-delivery-configuration? + opensmtpd-action-local-delivery-configuration-method + opensmtpd-action-local-delivery-configuration-alias + opensmtpd-action-local-delivery-configuration-ttl + opensmtpd-action-local-delivery-configuration-user + opensmtpd-action-local-delivery-configuration-userbase + opensmtpd-action-local-delivery-configuration-virtual + opensmtpd-action-local-delivery-configuration-wrapper + + opensmtpd-maildir-configuration + opensmtpd-maildir-configuration? + opensmtpd-maildir-configuration-pathname + opensmtpd-maildir-configuration-junk + + opensmtpd-mda-configuration + opensmtpd-mda-configuration-name + opensmtpd-mda-configuration-command + + opensmtpd-action-relay-configuration + opensmtpd-action-relay-configuration? + opensmtpd-action-relay-configuration-backup + opensmtpd-action-relay-configuration-backup-mx + opensmtpd-action-relay-configuration-helo + opensmtpd-action-relay-configuration-domain + opensmtpd-action-relay-configuration-host + opensmtpd-action-relay-configuration-pki + opensmtpd-action-relay-configuration-srs + opensmtpd-action-relay-configuration-tls + opensmtpd-action-relay-configuration-auth + opensmtpd-action-relay-configuration-mail-from + opensmtpd-action-relay-configuration-src + + opensmtpd-option-configuration + opensmtpd-option-configuration? + opensmtpd-option-configuration-option + opensmtpd-option-configuration-not + opensmtpd-option-configuration-regex + opensmtpd-option-configuration-data + + opensmtpd-filter-phase-configuration + opensmtpd-filter-phase-configuration? + opensmtpd-filter-phase-configuration-name + opensmtpd-filter-phase-configuration-phase-name + opensmtpd-filter-phase-configuration-options + opensmtpd-filter-phase-configuration-decision + opensmtpd-filter-phase-configuration-message + opensmtpd-filter-phase-configuration-value + + opensmtpd-filter-configuration + opensmtpd-filter-configuration? + opensmtpd-filter-configuration-name + opensmtpd-filter-configuration-proc + + opensmtpd-listen-on-configuration + opensmtpd-listen-on-configuration? + opensmtpd-listen-on-configuration-interface + opensmtpd-listen-on-configuration-family + opensmtpd-listen-on-configuration-auth + opensmtpd-listen-on-configuration-auth-optional + opensmtpd-listen-on-configuration-filters + opensmtpd-listen-on-configuration-hostname + opensmtpd-listen-on-configuration-hostnames + opensmtpd-listen-on-configuration-mask-src + opensmtpd-listen-on-configuration-disable-dsn + opensmtpd-listen-on-configuration-pki + opensmtpd-listen-on-configuration-port + opensmtpd-listen-on-configuration-proxy-v2 + opensmtpd-listen-on-configuration-received-auth + opensmtpd-listen-on-configuration-senders + opensmtpd-listen-on-configuration-secure-connection + opensmtpd-listen-on-configuration-tag + + opensmtpd-listen-on-socket-configuration + opensmtpd-listen-on-socket-configuration? + opensmtpd-listen-on-socket-configuration-filters + opensmtpd-listen-on-socket-configuration-mask-src + opensmtpd-listen-on-socket-configuration-tag + + opensmtpd-match-configuration + opensmtpd-match-configuration? + opensmtpd-match-configuration-action + opensmtpd-match-configuration-options + + opensmtpd-smtp-configuration + opensmtpd-smtp-configuration? + opensmtpd-smtp-configuration-ciphers + opensmtpd-smtp-configuration-limit-max-mails + opensmtpd-smtp-configuration-limit-max-rcpt + opensmtpd-smtp-configuration-max-message-size + opensmtpd-smtp-configuration-sub-addr-delim character + + opensmtpd-srs-configuration + opensmtpd-srs-configuration? + opensmtpd-srs-configuration-key + opensmtpd-srs-configuration-backup-key + opensmtpd-srs-configuration-ttl-delay + + opensmtpd-queue-configuration + opensmtpd-queue-configuration? + opensmtpd-queue-configuration-compression + opensmtpd-queue-configuration-encryption + opensmtpd-queue-configuration-ttl-delay + opensmtpd-configuration opensmtpd-configuration? + opensmtpd-package + opensmtpd-config-file + opensmtpd-configuration-bounce + opensmtpd-configuration-listen-ons + opensmtpd-configuration-listen-on-socket + opensmtpd-configuration-includes + opensmtpd-configuration-matches + opensmtpd-configuration-mda-wrappers + opensmtpd-configuration-mta-max-deferred + opensmtpd-configuration-srs + opensmtpd-configuration-smtp + opensmtpd-configuration-queue + opensmtpd-service-type %default-opensmtpd-config-file @@ -1651,13 +1786,1888 @@ (define (generate-dovecot-documentation) ;;; OpenSMTPD. ;;; +;; some fieldnames have a default value of #f, which is ok. They cannot have a value of #t. +;; for example opensmtpd-table-configuration-data can be #f, BUT NOT true. +;; my/sanitize procedure tests values to see if they are of the right kind. +;; procedure false? is needed to allow fields like 'values' to be blank, (empty), or #f BUT also +;; have a value like a list of strings. +(define (false? var) + (eq? #f var)) + +;; this procedure takes in a var and a list of procedures. It loops through list of procedures passing in var to each. +;; if one procedure returns #t, the function returns true. Otherwise #f. +;; TODO for fun rewrite this using map +;; If I rewrote it in map, then it may help with sanitizing. +;; eg: I could then potentially easily sanitize vars with lambda procedures. +(define (is-value-right-type? var list-of-procedures record fieldname) + (if (null? list-of-procedures) + #f + (cond [(procedure? (car list-of-procedures)) + (if ((car list-of-procedures) var) + #t + (is-value-right-type? var (cdr list-of-procedures) record fieldname))] + [(and (sanitize-configuration? (car list-of-procedures)) + (sanitize-configuration-error-if-proc-fails (car list-of-procedures)) + (if ((sanitize-configuration-proc (car list-of-procedures)) var) + #t + (begin + (apply string-append + (sanitize-configuration-error-message (car list-of-procedures))) + (throw 'bad! var))))] + [else (if ((sanitize-configuration-proc (car list-of-procedures)) var) + #t + (is-value-right-type? var (cdr list-of-procedures) record fieldname))]))) + +;; converts strings like this: +;; "apple, ham, cherry" -> "apple, ham, or cherry" +;; "pineapple" -> "pinneapple". +;; "cheese, grapefruit, or jam" -> "cheese, grapefruit, or jam" +(define (add-comma-or string) + (define last-comma-location (string-rindex string #\,)) + (if last-comma-location + (if (string-contains string ", or" last-comma-location) + string + (string-replace string ", or" last-comma-location + (+ 1 last-comma-location))) + string)) + +;; I could test for read-ability of a file, but then I would have to +;; test the program as root everytime instead of as a normal user... +(define (file-exists? file) +(if (string? file) + (access? file F_OK) + #f)) + +(define (list-of-procedures->string procedures) + (define string + (let loop ([procedures procedures]) + (if (null? procedures) + "" + (begin + (string-append + (cond [(eq? false? (car procedures)) + "#f , "] + [(eq? boolean? (car procedures)) + "boolean, "] + [(eq? string? (car procedures)) + "string, "] + [(eq? integer? (car procedures)) + "integer, "] + [(eq? list-of-strings? (car procedures)) + "list of strings, "] + [(eq? assoc-list? (car procedures)) + "an association list, "] + [(eq? opensmtpd-pki-configuration? (car procedures)) + "an <opensmtpd-pki-configuration> record, "] + [(eq? opensmtpd-table-configuration? (car procedures)) + "an <opensmtpd-table-configuration> record, "] + [(eq? list-of-unique-opensmtpd-match-configuration? (car procedures)) + "a list of unique <opensmtpd-match-configuration> records, "] + [(eq? table-whose-data-are-assoc-list? (car procedures)) + (string-append + "an <opensmtpd-table-configuration> record whose fieldname 'values' are an assoc-list \n" + "(eg: (opensmtpd-table-configuration (name \"table\") (data '(\"joshua\" . \"$encrypted$password\")))), ")] + [(eq? file-exists? (car procedures)) + "file, "] + [else "has an incorrect value, "]) + (loop (cdr procedures))))))) + (add-comma-or (string-append (string-drop-right string 2) ".\n"))) + +;; TODO can I M-x raise-sexp (string=? string var) in this procedure? and get rid of checking +;; if the var is a string? The previous string-in-list? had that check. +;; (string-in-list? '("hello" 5 "cat")) currently works. If I M-x raise-sexp (string=? string var) +;; then it will no longer work. +(define (string-in-list? string list) + (primitive-eval (cons 'or (map (lambda (var) (and (string? var) (string=? string var))) list)))) + +(define (my/sanitize var record fieldname list-of-procedures) + (if (is-value-right-type? var list-of-procedures record fieldname) + var + (begin + (display (string-append "<" record "> fieldname: '" fieldname "' is of type " + (list-of-procedures->string list-of-procedures) "\n")) + (throw 'bad! var)))) + +;; Some example opensmtpd-table-configurations: +;; +;; (opensmtpd-table-configuration (name "root accounts") (data '(("joshua" . "root@HIDDEN") ("joshua" . "postmaster@HIDDEN")))) +;; (opensmtpd-table-configuration (name "root accounts") (data (list "mysite.me" "your-site.com"))) +;; TODO should <opensmtpd-table-configuration> support have a fieldname 'file'? +;; Or should I change name to name-or-file ? +(define-record-type* <opensmtpd-table-configuration> + opensmtpd-table-configuration make-opensmtpd-table-configuration + opensmtpd-table-configuration? + this-record + (name opensmtpd-table-configuration-name ;; string + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-table-configuration" "name" (list string?))))) + (file-db opensmtpd-table-configuration-file-db + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-table-configuration" "file-db" + (list boolean?))))) + ;; FIXME support an aliasing table as described here: + ;; https://man.openbsd.org/table.5 + ;; One may have to use the record file for this. I don't think tables support a table like this: + ;; table "name" { joshua = joshua@HIDDEN,joshua@HIDDEN,joshua@HIDDEN, root = root@HIDDEN } + ;; If values is an absolute filename, then it will use said filename to house the table info. + ;; filename must be an absolute filename. + (data opensmtpd-table-configuration-data + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-table-configuration" "values" + (list file-exists? list-of-strings? assoc-list?))))) + ;; is a list of values or key values + ;; eg: (list "mysite.me" "your-site.com") + ;; eg: (list ("joshua" . "joshua@HIDDEN") ("james" . "james@HIDDEN")) + ;; I am currently making these values be as assocation list of strings only. + ;; FIXME should I allow a var like this? + ;; (list (cons "gnucode.me" 234.949.392.23)) + ;; can be of type: (quote list-of-strings) or (quote assoc-list) + ;; (opensmtpd-table-configuration-type record) returns the values' type. The user SHOULD NEVER set the type. + ;; TODO jpoiret: on irc reccomends that I just use an outside function to determine fieldname 'values', type. + ;; it would be "simpler" and possibly easier for the next person working on this code to understand what is happening. + (type opensmtpd-table-configuration-type + (default #f) + (thunked) + (sanitize (lambda (var) + (cond [(opensmtpd-table-configuration-data this-record) + (if (list-of-strings? (opensmtpd-table-configuration-data this-record)) + (quote list-of-strings) + (quote assoc-list))] + [(file-exists? (opensmtpd-table-configuration-data this-record)) + (if (opensmtpd-table-configuration-file-db this-record) + (quote db) + (quote file))] + [else + (display "opensmtpd-table-configuration-type is broke\n") + (throw 'bad! var)]))))) + +(define-record-type* <opensmtpd-ca-configuration> + opensmtpd-ca-configuration make-opensmtpd-ca-configuration + opensmtpd-ca-configuration? + (name opensmtpd-ca-configuration-name + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-ca-configuration" "name" (list string?))))) + (file opensmtpd-ca-configuration-file + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-ca-configuration" "file" (list file-exists?)))))) + +(define-record-type* <opensmtpd-pki-configuration> + opensmtpd-pki-configuration make-opensmtpd-pki-configuration + opensmtpd-pki-configuration? + (domain opensmtpd-pki-configuration-domain + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-pki-configuration" "domain" (list string?))))) + ;; TODO/FIXME this should probably be a list of files. The opensmtpd documentation says + ;; that you could have a list of files: + ;; + ;; pki pkiname cert certfile + ;; Associate certificate file certfile with host pkiname, and use that file to prove + ;; the identity of the mail server to clients. pkiname is the server's name, de‐ + ;; rived from the default hostname or set using either + ;; /gnu/store/2d13sdz76ldq8zgwv4wif0zx7hkr3mh2-opensmtpd-6.8.0p2/etc/mailname or us‐ + ;; ing the hostname directive. If a fallback certificate or SNI is wanted, the ‘*’ + ;; wildcard may be used as pkiname. + + ;; A certificate chain may be created by appending one or many certificates, includ‐ + ;; ing a Certificate Authority certificate, to certfile. The creation of certifi‐ + ;; cates is documented in starttls(8). + (cert opensmtpd-pki-configuration-cert + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-pki-configuration" "cert" (list file-exists?))))) + (key opensmtpd-pki-configuration-key + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-pki-configuration" "key" (list file-exists?))))) + ; todo sanitize this. valid parameters are "none", "legacy", or "auto". + (dhe opensmtpd-pki-configuration-dhe + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-dhe" "dhe" (list false? string?)))))) + +(define-record-type* <opensmtpd-lmtp-configuration> + opensmtpd-lmtp-configuration make-opensmtpd-lmtp-configuration + opensmtpd-lmtp-configuration? + (destination opensmtpd-lmtp-configuration-destination + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-lmtp-configuration" "destination" + (list string?))))) + (rcpt-to opensmtpd-lmtp-configuration-rcpt-to + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-lmtp-configuration" "rcpt-to" + (list false? string?)))))) + +(define-record-type* <opensmtpd-mda-configuration> + opensmtpd-mda-configuration make-opensmtpd-mda-configuration + opensmtpd-mda-configuration? + (name opensmtpd-mda-configuration-name + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-mda-configuration" "name" + (list string?))))) + ;; TODO should I allow this command to be a gexp? + (command opensmtpd-mda-configuration-command + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-mda-configuration" "command" + (list string?)))))) + +(define-record-type* <opensmtpd-maildir-configuration> + opensmtpd-maildir-configuration make-opensmtpd-maildir-configuration + opensmtpd-maildir-configuration? + (pathname opensmtpd-maildir-configuration-pathname + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-maildir-configuration" "pathname" + (list false? string?))))) + (junk opensmtpd-maildir-configuration-junk + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-maildir-configuration" "junk" + (list boolean?)))))) + +(define-record-type* <opensmtpd-action-local-delivery-configuration> + opensmtpd-action-local-delivery-configuration make-opensmtpd-action-local-delivery-configuration + opensmtpd-action-local-delivery-configuration? + (name opensmtpd-action-local-delivery-configuration-name + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-local-delivery-configuration" "name" + (list string?))))) + (method opensmtpd-action-local-delivery-configuration-method + (default "mbox") + (sanitize (lambda (var) + (cond + [(or (opensmtpd-lmtp-configuration? var) + (opensmtpd-maildir-configuration? var) + (opensmtpd-mda-configuration? var) + (string=? var "mbox") + (string=? var "expand-only") + (string=? var "forward-only")) + var] + [else + (begin + (display (string-append "<opensmtpd-action-local-delivery-configuration> fieldname 'method' must be of type \n" + "\"mbox\", \"expand-only\", \"forward-only\" \n" + "<opensmtpd-lmtp-configuration>, <opensmtpd-maildir-configuration>, \n" + "or <opensmtpd-mda-configuration>.\n")) + (throw 'bad! var))])))) + (alias opensmtpd-action-local-delivery-configuration-alias + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-local-delivery-configuration" "alias" + (list false? opensmtpd-table-configuration?))))) + (ttl opensmtpd-action-local-delivery-configuration-ttl + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-local-delivery-configuration" "ttl" + (list false? string?))))) + (user opensmtpd-action-local-delivery-configuration-user + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-local-delivery-configuration" "user" + (list false? string?))))) + (userbase opensmtpd-action-local-delivery-configuration-userbase + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-local-delivery-configuration" "userbase" + (list false? opensmtpd-table-configuration?))))) + (virtual opensmtpd-action-local-delivery-configuration-virtual + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-local-delivery-configuration" "virtual" + (list false? opensmtpd-table-configuration?))))) + (wrapper opensmtpd-action-local-delivery-configuration-wrapper + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-local-delivery-configuration" "wrapper" + (list false? string?)))))) + +;; FIXME/TODO this is a valid opensmtpd-relay record +;; (opensmtpd-action-relay-configuration +;; (pki (opensmtpd-pki-configuration +;; (domain "gnucode.me") +;; (cert "opensmtpd.scm") +;; (key "opensmtpd.scm")))) +;; BUT how does it relay the email? What host does it use? +;; I think opensmtpd-relay-configuration needs "method" field. +;; the method field might need to be another record...BUT basically the relay has to have a 'backup', 'backup-mx', +;; or 'domain', or 'host' defined. +(define-record-type* <opensmtpd-action-relay-configuration> + opensmtpd-action-relay-configuration make-opensmtpd-action-relay-configuration + opensmtpd-action-relay-configuration? + (name opensmtpd-action-relay-configuration-name + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "name" + (list string?)))) + (default #f)) + (backup opensmtpd-action-relay-configuration-backup ;; boolean + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "backup" + (list boolean?))))) + (backup-mx opensmtpd-action-relay-configuration-backup-mx ;; string mx name + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "backup-mx" + (list false? string?))))) + (helo opensmtpd-action-relay-configuration-helo + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "helo" + (list false? string? opensmtpd-table-configuration?)))) + (default #f)) + (helo-src opensmtpd-action-relay-configuration-helo-src + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "helo-src" + (list false? string? opensmtpd-table-configuration?)))) + (default #f)) + (domain opensmtpd-action-relay-configuration-domain + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "domain" + (list false? opensmtpd-table-configuration?)))) + (default #f)) + (host opensmtpd-action-relay-configuration-host + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "host" + (list false? string?)))) + (default #f)) + (pki opensmtpd-action-relay-configuration-pki + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "pki" + (list false? opensmtpd-pki-configuration?))))) + (srs opensmtpd-action-relay-configuration-srs + (default #f) + (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "srs" + (list boolean?)))) + (tls opensmtpd-action-relay-configuration-tls + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "tls" + (list false? string?))))) + (auth opensmtpd-action-relay-configuration-auth + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "auth" + (list false? opensmtpd-table-configuration?)))) + (default #f)) + (mail-from opensmtpd-action-relay-configuration-mail-from + (default #f)) + ;; string "127.0.0.1" or "<interface>" or "<table of IP addresses>" + ;; TODO should I do some sanitizing to make sure that the string? here is actually an IP address or a valid interface? + (src opensmtpd-action-relay-configuration-src + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "src" + (list false? string? opensmtpd-table-configuration?)))) + (default #f))) + +;; this record is used by <opensmtpd-filter-phase-configuration> & +;; <opensmtpd-match-configuration> +(define-record-type* <opensmtpd-option-configuration> + opensmtpd-option-configuration make-opensmtpd-option-configuration + opensmtpd-option-configuration? + (option opensmtpd-option-configuration-option + (default #f) + (sanitize (lambda (var) + (if (and (string? var) + (or (string-in-list? var (list "fcrdns" "rdns" + "src" "helo" + "auth" "mail-from" + "rcpt-to" + "for" + "for any" "for local" + "for domain" "for rcpt-to" + "from any" "from auth" + "from local" "from mail-from" + "from rdns" "from socket" + "from src" "auth" + "helo" "mail-from" + "rcpt-to" "tag" "tls" + )))) + var + (begin + (display (string-append "<opensmtpd-option-configuration> fieldname: 'option' is of type \n" + "string. The string can be either 'fcrdns', \n" + " 'rdns', 'src', 'helo', 'auth', 'mail-from', or 'rcpt-to', \n" + "'for', 'for any', 'for local', 'for domain', 'for rcpt-to', \n" + "'from any', 'from auth', 'from local', 'from mail-from', 'from rdns', 'from socket', \n" + "'from src', 'auth helo', 'mail-from', 'rcpt-to', 'tag', or 'tls' \n" + )) + (throw 'bad! var)))))) + (not opensmtpd-option-configuration-not + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-option-configuration" "not" + (list boolean?))))) + (regex opensmtpd-option-configuration-regex + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-option-configuration" "regex" + (list boolean?))))) + (data opensmtpd-option-configuration-data + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-option-configuration" "data" + (list false? string? opensmtpd-table-configuration?)))))) + +(define-record-type* <opensmtpd-filter-phase-configuration> + opensmtpd-filter-phase-configuration make-opensmtpd-filter-phase-configuration + opensmtpd-filter-phase-configuration? + (name opensmtpd-filter-phase-configuration-name ;; string chain-name + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-filter-phase-configuration" "name" + (list string?))))) + (phase opensmtpd-filter-phase-configuration-phase ;; string + (default #f) + (sanitize (lambda (var) + ;;(my/sanitize var "opensmtpd-filter-phase-configuration" "phase" + ;; (list (sanitize-configuration + ;; (proc (lambda (value) + ;; (and (string? var) + ;; (string-in-list? var (list "connect" + ;; "helo" + ;; "mail-from" + ;; "rcpt-to" + ;; "data" + ;; "commit"))))) + ;; (error-message (list + ;; "<opensmtpd-filter-phase-configuration> fieldname: 'phase' is of type \n" + ;; "string. The string can be either 'connect'," + ;; " 'helo', 'mail-from', 'rcpt-to', 'data', or 'commit.'\n "))))) + (if (and (string? var) + (string-in-list? var (list "connect" + "helo" + "mail-from" + "rcpt-to" + "data" + "commit"))) + var + (begin + (display (string-append "<opensmtpd-filter-phase-configuration> fieldname: 'phase' is of type \n" + "string. The string can be either 'connect'," + " 'helo', 'mail-from', 'rcpt-to', 'data', or 'commit.'\n " + )) + (throw 'bad! var))) + ))) + + (options opensmtpd-filter-phase-configuration-options + (default #f) + (sanitize (lambda (var) + ;; returns #t if list is a unique list of <opensmtpd-option-configuration> + (define (list-of-opensmtpd-option-configuration? list) + (and (list-of-type? list opensmtpd-option-configuration?) + (not (contains-duplicate? list)))) + + (define (list-has-duplicates-or-non-opensmtpd-option-configuration list) + (not (list-of-opensmtpd-option-configuration? list))) + + ;; input <opensmtpd-option-configuration> + ;; return #t if <opensmtpd-option-configuration> fieldname 'option' + ;; that needs a corresponding table has one. Otherwise #f + (define (opensmtpd-option-configuration-has-table? record) + (define decision (opensmtpd-option-configuration-option record)) + (and (string? decision) + ;; if option needs a table, check for a table + (if (string-in-list? decision (list "src" + "helo" + "mail-from" + "rcpt-to")) + (opensmtpd-table-configuration? (opensmtpd-option-configuration-data record)) + #t))) + + (define (list-of-opensmtpd-option-configuration-has-table? list) + (list-of-type? list opensmtpd-option-configuration-has-table?)) + + (define (some-opensmtpd-option-configuration-in-list-lack-table? list) + (not (list-of-opensmtpd-option-configuration-has-table? list))) + + ;;each element in list is of type <opensmtpd-option-configuration> + (cond [(list-has-duplicates-or-non-opensmtpd-option-configuration var) + (begin + (display (string-append "<opensmtpd-filter-phase-configuration> fieldname: 'options' is a list of unique \n" + "<opensmtpd-option-configuration> records.\n")) + (throw 'bad! var))] + ;; if fieldname 'option' is of string 'src', 'helo', 'mail-from', 'rcpt-to', then there should be a table + [(some-opensmtpd-option-configuration-in-list-lack-table? var) + (begin + (display (string-append "<opensmtpd-option-configuration>'s fieldname 'option' values of \n" + "'src', 'helo', 'mail-from', or 'rcpt-to' need a corresponding 'table' \n" + " of type <opensmtpd-table-configuration>. eg: \n" + "(opensmtpd-option-configuration \n" + " (option \"src\")\n" + " (table (opensmtpd-table-configuration \n" + " (name \"src-table\")\n" + " (data (list \"hello\" \"cat\")))))\n")) + ;; TODO it would be nice if the var this error message throws in the bad + ;; <opensmtpd-option-configuration>, instead of the list of records. + (throw 'bad! var))] + [else var])))) + (decision opensmtpd-filter-phase-configuration-decision + (default #f) + (sanitize (lambda (var) + (if (and (string? var) + (string-in-list? var (list "bypass" "disconnect" + "reject" "rewrite" "junk"))) + var + (begin + (display (string-append "<opensmtpd-filter-decision> fieldname: 'decision' is of type \n" + "string. The string can be either 'bypass'," + " 'disconnect', 'reject', 'rewrite', or 'junk'.\n")) + (throw 'bad! var)))))) + (message opensmtpd-filter-phase-configuration-message + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-filter-phase-configuration" "message" + (list false? string?))))) + (value opensmtpd-filter-phase-configuration-value + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-filter-phase-configuration" "value" + (list false? number?)))))) + +(define-record-type* <opensmtpd-filter-configuration> + opensmtpd-filter-configuration make-opensmtpd-filter-configuration + opensmtpd-filter-configuration? + (name opensmtpd-filter-configuration-name + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-filter" "name" + (list string?))))) + (exec opensmtpd-filter-exec + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-filter" "exec" + (list boolean?))))) + (proc opensmtpd-filter-configuration-proc ; a string like "rspamd" or the command to start it like "/path/to/rspamd --option=arg --2nd-option=arg2" + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-filter" "proc" + (list string?)))))) + +;; There is another type of filter that opensmtpd supports, which is a filter chain. +;; A filter chain is a list of <opensmtpd-filter-phase-configuration> and <opensmtpd-filter-configuration>. +;; This lets you apply several filters under one filter name. I could have defined +;; a record type for it, but the record would only have had two fields: name and list-of-filters. +;; Why write that as a record? That's too simple. +;; returns #t if list is a unique list of <opensmtpd-filter-configuration> or <opensmtpd-filter-phase-configuration> +;; returns # otherwise +(define (opensmtpd-filter-chain? %filters) + (and (list-of-unique-filter-or-filter-phase? %filters) + (< 1 (length %filters)))) + +(define-record-type* <opensmtpd-listen-on-configuration> + opensmtpd-listen-on-configuration make-opensmtpd-listen-on-configuration + opensmtpd-listen-on-configuration? + ;; interface may be an IP address, interface group, or domain name + (interface opensmtpd-listen-on-configuration-interface + (default "lo")) + (family opensmtpd-listen-on-configuration-family + (default #f) + (sanitize (lambda (var) + (cond + [(eq? #f var) ;; var == #f + var] + [(and (string? var) + (string-in-list? var (list "inet4" "inet6"))) + var] + [else + (begin + (display "<opensmtpd-listen-on-configuration> fieldname 'family' must be string \"inet4\" or \"inet6\".\n") + (throw 'bad! var))])))) + (auth opensmtpd-listen-on-configuration-auth + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-listen-on-configuration" "auth" + (list boolean? table-whose-data-are-assoc-list?))))) + (auth-optional opensmtpd-listen-on-configuration-auth-optional + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-listen-on-configuration" "auth-optional" + (list boolean? + table-whose-data-are-assoc-list?))))) + ;; TODO add a ca entry? + ;; string FIXME/TODO sanitize this to support a gexp. That way way the + ;; includes directive can include my hacky scheme code that I use for opensmtpd-dkimsign. + (filters opensmtpd-listen-on-configuration-filters + (default #f) + (sanitize (lambda (var) + (sanitize-filters var)))) + (hostname opensmtpd-listen-on-configuration-hostname + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-listen-on-configuration" "hostname" + (list false? string?))))) + (hostnames opensmtpd-listen-on-configuration-hostnames + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-listen-on-configuration" "hostnames" + (list false? table-whose-data-are-assoc-list?))))) + (mask-src opensmtpd-listen-on-configuration-mask-src + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-listen-on-configuration" "mask-src" + (list boolean?))))) + (disable-dsn opensmtpd-listen-on-configuration-disable-dsn + (default #f)) + (pki opensmtpd-listen-on-configuration-pki + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-listen-on-configuration" "pki" + (list false? opensmtpd-pki-configuration?))))) + (port opensmtpd-listen-on-configuration-port + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-listen-on-configuration" "port" + (list false? integer?))))) + (proxy-v2 opensmtpd-listen-on-configuration-proxy-k2 + (default #f)) + (received-auth opensmtpd-listen-on-configuration-received-auth + (default #f)) + ;; TODO add in a senders option! + ;; string or <opensmtpd-senders> record + ;; (senders opensmtpd-listen-on-configuration-senders + ;; (sanitize (lambda (var) + ;; (my/sanitize var "opensmtpd-listen-on-configuration" "port" (list false? integer?)))) + ;; (default #f)) + (secure-connection opensmtpd-listen-on-configuration-secure-connection + (default #f) + (sanitize (lambda (var) + (cond [(boolean? var) + var] + [(and (string? var) + (string-in-list? var + (list "smtps" "tls" + "tls-require" + "tls-require-verify"))) + var] + [else + (begin + (display (string-append "<opensmtd-listen-on> fieldname 'secure-connection' can be \n" + "one of the following strings: \n'smtps', 'tls', 'tls-require', \n" + "or 'tls-require-verify'.\n")) + (throw 'bad! var))])))) + (tag opensmtpd-listen-on-configuration-tag + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-listen-on-configuration" "tag" + (list false? string?)))) + (default #f))) + +(define-record-type* <opensmtpd-listen-on-socket-configuration-configuration> + opensmtpd-listen-on-socket-configuration-configuration make-opensmtpd-listen-on-socket-configuration-configuration + opensmtpd-listen-on-socket-configuration-configuration? + ;; false or <opensmtpd-filter-configuration> or list of <opensmtpd-filter-configuration> + (filters opensmtpd-listen-on-socket-configuration-configuration-filters + (sanitize (lambda (var) + (sanitize-filters var))) + (default #f)) + (mask-src opensmtpd-listen-on-socket-configuration-configuration-mask-src + (default #f)) + (tag opensmtpd-listen-on-socket-configuration-configuration-tag + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-listen-on-configuration" "tag" + (list false? string?)))) + (default #f))) + + +(define-record-type* <opensmtpd-match-configuration> + opensmtpd-match-configuration make-opensmtpd-match-configuration + opensmtpd-match-configuration? + ;;TODO? Perhaps I should add in a reject fieldname. If reject + ;;is #t, then the match record will be a reject match record. + ;; (opensmtpd-match (reject #t)) vs. (opensmtpd-match (action 'reject)) + ;; To do this, I will also have to 'reject' mutually exclusive. AND an match with 'reject' can have no action defined. + (action opensmtpd-match-configuration-action + (default #f) + (sanitize (lambda (var) + (if (or (opensmtpd-action-relay-configuration? var) + (opensmtpd-action-local-delivery-configuration? var) + (eq? (quote reject) var)) + var + (begin + (display + (string-append "<opensmtpd-match-configuration> fieldname 'action' is of type <opensmtpd-action-relay-configuration>, \n" + "<opensmtpd-action-local-delivery-configuration>, or (quote reject).\n" + "If its var is (quote reject), then the match rejects the incoming message\n" + "during the SMTP dialogue.\n")) + (throw 'bad! var)))))) + (options opensmtpd-match-configuration-options + (default #f) + (sanitize (lambda (var) + (cond ((not var) + #f) + ((not (list-of-unique-opensmtpd-option-configuration? var)) + (throw-error var '("<opensmtpd-match-configuration> fieldname 'options' is a list of unique \n" + "<opensmtpd-option-configuration> records. \n"))) + (else (sanitize-list-of-options-for-match-configuration var))))))) + +(define-record-type* <opensmtpd-smtp-configuration> + opensmtpd-smtp-configuration make-opensmtpd-smtp-configuration + opensmtpd-smtp-configuration? + (ciphers opensmtpd-smtp-configuration-ciphers + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-smtp-configuration" "ciphers" + (list false? string?))))) + (limit-max-mails opensmtpd-smtp-configuration-limit-max-mails + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-smtp-configuration" "limit-max-mails" + (list false? integer?))))) + (limit-max-rcpt opensmtpd-smtp-configuration-limit-max-rcpt + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-smtp-configuration" "limit-max-rcpt" + (list false? integer?))))) + (max-message-size opensmtpd-smtp-configuration-max-message-size + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-smtp-configuration" "max-message-size" + (list false? integer? string?))))) + ;; FIXME/TODO the sanitize function of sub-addr-delim should accept a string of length one not string? + (sub-addr-delim opensmtpd-smtp-configuration-sub-addr-delim + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-smtp-configuration" "sub-addr-delim" + (list false? integer? string?)))))) + +(define-record-type* <opensmtpd-srs-configuration> + opensmtpd-srs-configuration make-opensmtpd-srs-configuration + opensmtpd-srs-configuration? + ;; TODO should this be a file? + (key opensmtpd-srs-configuration-key + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-srs-configuration" "key" + (list false? boolean? string?))))) + ;; TODO should this also be a file? + (backup-key opensmtpd-srs-configuration-backup-key + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-srs-configuration" "backup-key" + (list false? integer?))))) + (ttl-delay opensmtpd-srs-configuration-ttl-delay + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-srs-configuration" "ttl-delay" + (list false? string?)))))) + +(define-record-type* <opensmtpd-queue-configuration> + opensmtpd-queue-configuration make-opensmtpd-queue-configuration + opensmtpd-queue-configuration? + (compression opensmtpd-queue-configuration-compression + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-queue-configuration" "compression" + (list boolean?))))) + (encryption opensmtpd-queue-configuration-encryption + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-queue-configuration" "encryption" + (list boolean? file-exists? string?))))) + (ttl-delay opensmtpd-queue-configuration-ttl-delay + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-queue-configuration" "ttl-delay" + (list false? string?)))))) + (define-record-type* <opensmtpd-configuration> opensmtpd-configuration make-opensmtpd-configuration opensmtpd-configuration? - (package opensmtpd-configuration-package - (default opensmtpd)) + (package opensmtpd-configuration-package + (default opensmtpd)) (config-file opensmtpd-configuration-config-file - (default %default-opensmtpd-config-file))) + (default #f)) + ;; FIXME/TODO should I include a admd authservid entry? + + ;; TODO sanitize this properly with perhaps a <sanitize-configuration>. + (bounce opensmtpd-configuration-bounce + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-configuration" "bounce" + (list false? list?))))) + (cas opensmtpd-configuration-cas + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-configuration" "cas" + (list false? list-of-opensmtpd-ca-configuration?))))) + ;; list of many records of type opensmtpd-listen-on-configuration + (listen-ons opensmtpd-configuration-listen-ons + (default (list (opensmtpd-listen-on-configuration))) + (sanitize (lambda (var) + (if (list-of-opensmtpd-listen-on-configuration? var) + var + (begin + (display "<opensmtpd-configuration> fieldname 'listen-ons' expects a list of records ") + (display "of one or more unique <opensmtpd-listen-on-configuration> records.\n") + (throw 'bad! var)))))) + ;; accepts type <opensmtpd-listen-on-socket-configuration-configuration> + (listen-on-socket opensmtpd-configuration-listen-on-socket + (default (opensmtpd-listen-on-socket-configuration-configuration))) + (includes opensmtpd-configuration-includes ;; list of strings of absolute path names + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-configuration" "includes" + (list false? list-of-strings?))))) + (matches opensmtpd-configuration-matches + (default (list (opensmtpd-match-configuration + (action (opensmtpd-action-local-delivery-configuration + (name "local") + (method "mbox"))) + (options (list + (opensmtpd-option-configuration + (option "for local"))))) + (opensmtpd-match-configuration + (action (opensmtpd-action-relay-configuration + (name "outbound"))) + (options (list + (opensmtpd-option-configuration + (option "from local")) + (opensmtpd-option-configuration + (option "for any"))))))) + ;; TODO perhaps I should sanitize this function like I sanitized the 'filters'. + ;; I definitely should sanitize this function a bit more. For example, you could have two different + ;; actions, one for local delivery and one for remote, with the same name. I should make sure that + ;; I have no two different actions with the same name. + (sanitize (lambda (var) + ;; Should we do more sanitizing here? eg: "from socket" should NOT have a table or value + var + (my/sanitize var "opensmtpd-configuration" "matches" + (list list-of-unique-opensmtpd-match-configuration?))))) + ;; list of many records of type mda-wrapper + ;; TODO/FIXME support using gexps here + ;; eg (list "name" gexp) + (mda-wrappers opensmtpd-configuration-mda-wrappers + (default #f) + (sanitize (lambda (var) + (my/sanitize var + "opensmtpd-configuration" + "mda-wrappers" + (list false? string?))))) + (mta-max-deferred opensmtpd-configuration-mta-max-deferred + (default 100) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-configuration" "mta-max-deferred" + (list number?))))) + + ;; TODO should I add a fieldname proc _proc-name_ _command_ as found in the man 5 smtpd.conf ? + + (queue opensmtpd-configuration-queue + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-configuration" "queue" + (list false? opensmtpd-queue-configuration?))))) + (smtp opensmtpd-configuration-smtp + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-configuration" "smtp" + (list false? opensmtpd-smtp-configuration?))))) + (srs opensmtpd-configuration-srs + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-configuration" "srs" + (list false? opensmtpd-srs-configuration?)))))) + +;; This is a non-exported record for passing around sanitize procedures. +;; As of 5/2/2022 I am not using it. I should probably just delete it. +(define-record-type* <sanitize-configuration> + sanitize-configuration make-sanitize-configuration + sanitize-configuration? + (proc sanitize-configuration-proc + (default #f) + ;;(sanitize (lambda (var) (procedure? var))) + ) + (args sanitize-configuration-args + (default #f) + ;;(sanitize (lambda (var) (lambda (var) (list? var)))) + ) + (error-message sanitize-configuration-error-message + (default #f) + ;;(sanitize (lambda (var) (list? var))) + ) + (error-if-proc-fails sanitize-configuration-error-if-proc-fails + (default #f))) + +;; this help procedure is used 3 or 4 times by sanitize-list-of-options-for-match-configuration +(define (throw-error-duplicate-option option error-arg) + (throw-error error-arg + (list "<opensmtpd-match-configuration>'s fieldname 'options' has two\n" + (string-append "<opensmtpd-option-configuration> records with fieldname 'option' with value '" option "'. \n") + (string-append "You can only have one option with value '" option "' in the options list.\n")))) + +;; this procedure sanitizes the fieldname opensmtpd-match-configuration-options +(define* (sanitize-list-of-options-for-match-configuration %options) + (let loop ([%traversing-options %options] + [%sanitized-options '()]) + (if (null? %traversing-options) + (remove false? + (list + (assoc-ref %sanitized-options "for") + (assoc-ref %sanitized-options "from") + (assoc-ref %sanitized-options "auth") + (assoc-ref %sanitized-options "helo") + (assoc-ref %sanitized-options "mail-from") + (assoc-ref %sanitized-options "rcpt-to") + (assoc-ref %sanitized-options "tag") + (assoc-ref %sanitized-options "tls"))) + (let* ((option-record (car %traversing-options)) + (option-string (opensmtpd-option-configuration-option option-record))) + (cond [(string=? "auth" option-string) + (if (assoc-ref %sanitized-options "auth") + (throw-error-duplicate-option "auth" %traversing-options) + (loop (cdr %traversing-options) (alist-cons "auth" option-record %sanitized-options)))] + [(string=? "helo" option-string) + (cond [(assoc-ref %sanitized-options "helo") + (throw-error-duplicate-option "helo" %traversing-options)] + [(not (opensmtpd-option-configuration-data option-record)) + (throw-error option-record + (list "<opensmtpd-option-configuration> with fieldname 'option' with value 'helo' \n" + "must have a 'data' of type string or <opensmtpd-table-configuration>.\n"))] + [else (loop (cdr %traversing-options) (alist-cons "helo" option-record %sanitized-options))])] + [(string=? "mail-from" option-string) + (cond ((assoc-ref %sanitized-options "mail-from") + (throw-error-duplicate-option "mail-from" %traversing-options)) + ((not (opensmtpd-option-configuration-data option-record)) + (throw-error option-record + (list "<opensmtpd-option-configuration> with fieldname 'option' with value 'mail-from' \n" + "must have a 'data' of type string or <opensmtpd-table-configuration>.\n"))) + (else (loop (cdr %traversing-options) (alist-cons "mail-from" option-record %sanitized-options))))] + [(string=? "rcpt-to" option-string) + (cond [(assoc-ref %sanitized-options "rcpt-to") + (throw-error-duplicate-option "rcpt-to" %traversing-options)] + [(not (opensmtpd-option-configuration-data option-record)) + (throw-error option-record + (list "<opensmtpd-option-configuration> with fieldname 'option' with value 'rcpt-to' \n" + "must have a 'data' of type string or <opensmtpd-table-configuration>.\n"))] + [else (loop (cdr %traversing-options) (alist-cons "rcpt-to" option-record %sanitized-options))])] + [(string=? "tag" option-string) + (cond ((assoc-ref %sanitized-options "tag") + (throw-error-duplicate-option "tag" %traversing-options)) + ((not (string? (opensmtpd-option-configuration-data option-record))) + (throw-error option-record + (list "<opensmtpd-option-configuration> with fieldname 'option' with value 'tag' \n" + "must have a 'data' of type string.\n"))) + (else (loop (cdr %traversing-options) (alist-cons "tag" option-record %sanitized-options))))] + [(string=? "tls" option-string) + (cond [(assoc-ref %sanitized-options "tls") + (throw-error-duplicate-option "tls" %traversing-options)] + [(or (opensmtpd-option-configuration-data option-record) + (opensmtpd-option-configuration-regex option-record)) + (throw-error option-record + (list "<opensmtpd-option-configuration> with fieldname 'option' with value 'tls' \n" + "cannot have a string or table 'data'.\n"))] + [else (loop (cdr %traversing-options) (alist-cons "tls" option-record %sanitized-options))])] + [(string=? "for" (substring option-string 0 3)) + (cond ((assoc-ref %sanitized-options "for") + (throw-error %options + `("<opensmtpd-match-configuration>'s fieldname 'options' can only have one 'for' option. \n" + "But '" ,option-string "' and '" + ,(opensmtpd-option-configuration-option (assoc-ref %sanitized-options "for")) "' are present.\n"))) + ((and (string-in-list? option-string (list "for any" "for local")) ; for any cannot have a data field. + (or (opensmtpd-option-configuration-data option-record) + (opensmtpd-option-configuration-regex option-record))) + (throw-error option-record + (list "When <openmstpd-option-configuration>'s fieldname 'options' value is 'for any' \n" + "or 'for local', then its 'data' and 'regex' field must be #f. \n"))) + ((and (string-in-list? option-string (list "for domain" "for rcpt-to")) ; for domain must have a data field. + (not (opensmtpd-option-configuration-data option-record))) + (throw-error option-record + (list "When <openmstpd-option-configuration>'s fieldname 'options' value is 'for domain' \n" + "or 'for rcpt-to', then its 'data' field must be a string or an \n" + "<opensmtpd-table-configuration> record.\n"))) + (else (loop (cdr %traversing-options) (alist-cons "for" option-record %sanitized-options))))] + [(string=? "from" (substring option-string 0 4)) + (cond ((assoc-ref %sanitized-options "from") + (throw-error %options + `("<opensmtpd-match-configuration>'s fieldname 'options' can only have one 'from' option. \n" + "But '" ,option-string "' and '" + ,(opensmtpd-option-configuration-option (assoc-ref %sanitized-options "from")) "' are present.\n"))) + ((and (string-in-list? option-string (list "from any" "from local" "from socket")) ; for any cannot have a data field. + (or (opensmtpd-option-configuration-data option-record) + (opensmtpd-option-configuration-regex option-record))) + (throw-error option-record + (list "When <openmstpd-option-configuration>'s fieldname 'options' value is 'from any', \n" + " 'from local', or 'from socket', then its 'data' and 'regex' field must be #f. \n"))) + ((and (string-in-list? option-string (list "from mail-from" "from src")) ; for domain must have a data field. + (not (opensmtpd-option-configuration-data option-record))) + (throw-error option-record + (list "When <openmstpd-option-configuration>'s fieldname 'options' value is 'from mail-from' \n" + "or 'from src', then its 'data' field must be a string or an \n" + "<opensmtpd-table-configuration> record.\n"))) + (else (loop (cdr %traversing-options) (alist-cons "from" option-record %sanitized-options))))]))))) + +;; some procedures for <opensmtpd-listen-on-configuration> and +;; <opensmtpd-listen-on-socket-configuration-configuration>. +(define (sanitize-filters %list) + ;; the order of the first two tests in this cond is important. + ;; (false?) has to be 1st and (list-has-duplicates-or-non-filters?) has to be second. + ;; You may optionally re-order the other alternates in the cond. + (cond [(false? %list) + #f] + [(list-has-duplicates-or-non-filters? %list) + (begin + (display (string-append "<opensmtpd-listen-on-configuration> fieldname: 'filters' is a list, in which each unique element \n" + "is of type <opensmtpd-filter-configuration> or <opensmtpd-filter-phase-configuration>.\n")) + (throw 'bad! %list))] + [else + (let loop ([%traversing-list %list] + [%original-list %list]) + (if (null? %traversing-list) + %original-list + (cond + [(opensmtpd-filter-configuration? (car %traversing-list)) + (loop (cdr %traversing-list) %original-list)] + [(filter-phase-has-message-and-value? (car %traversing-list)) + (begin + (display (string-append "<opensmtpd-filter-phase-configuration> cannot have defined fieldnames 'value' \n" + "and 'message'.\n")) + (throw 'bad! (car %traversing-list)))] + [(filter-phase-decision-lacks-proper-message? (car %traversing-list)) + (begin + (display (string-append "<opensmtpd-filter-phase-configuration> fieldname: 'decision' options \n" + "\"disconnect\" and \"reject\" require fieldname 'message' to have a string.\n" + "The 'message' string must be RFC commpliant, which means that the string \n" + "must begin with a 4xx or 5xx status code.\n")) + (throw 'bad! (car %traversing-list)))] + [(filter-phase-lacks-proper-value? (car %traversing-list)) + (begin + (display (string-append "<opensmtpd-filter-phase-configuration> fieldname: 'decision' option \n" + "\"rewrite\" requires fieldname 'value' to have a number.\n")) + (throw 'bad! (car %traversing-list)))] + [(filter-phase-has-incorrect-junk-or-bypass? (car %traversing-list)) + (begin + (display (string-append "<opensmtpd-filter-phase-configuration> fieldname 'decision' option \n" + "\"junk\" or 'bypass' cannot have a defined fieldnames 'message' or 'value'.\n")) + (throw 'bad! (car %traversing-list)))] + [(filter-phase-junks-after-commit? (car %traversing-list)) + (begin + (display (string-append "<opensmtpd-filter-phase-configuration> fieldname 'decision' option \n" + "\"junk\" cannot junk an email during 'phase' \"commit\".\n")) + (throw 'bad! (car %traversing-list)))] + [else (loop (cdr %traversing-list) %original-list)])))])) + +(define (list-has-duplicates-or-non-filters? list) + (not (list-of-unique-filter-or-filter-phase? list))) + +(define (filter-phase-has-message-and-value? record) + (and (opensmtpd-filter-phase-configuration-message record) + (opensmtpd-filter-phase-configuration-value record))) + +;; return #t if phase needs a message. Or if the message did not start with a 4xx or 5xx status code. +;; otherwise #f +(define (filter-phase-decision-lacks-proper-message? record) + (define decision (opensmtpd-filter-phase-configuration-decision record)) + (if (string-in-list? decision (list "disconnect" "reject")) + ;; this message needs to be RFC compliant, meaning + ;; that it need to start with 4xx or 5xx status code + (cond [(eq? #f (opensmtpd-filter-phase-configuration-message record)) + #t] + [(string? (opensmtpd-filter-phase-configuration-message record)) + (let ((number (string->number + (substring + (opensmtpd-filter-phase-configuration-message record) 0 3)))) + (if (and (number? number) + (and (< number 600) (> number 399))) + #f + #t))]) + #f)) + +;; 'decision' "rewrite" requires 'value' to be a number. +(define (filter-phase-lacks-proper-value? record) + (define decision (opensmtpd-filter-phase-configuration-decision record)) + (if (string=? "rewrite" decision) + (if (and (number? (opensmtpd-filter-phase-configuration-value record)) + (eq? #f (opensmtpd-filter-phase-configuration-message record))) + #f + #t) + #f)) + +;; 'decision' "junk" or "bypass" cannot have a message or a value. +(define (filter-phase-has-incorrect-junk-or-bypass? record) + (and + (string-in-list? + (opensmtpd-filter-phase-configuration-decision record) + (list "junk" "bypass")) + (or + (opensmtpd-filter-phase-configuration-value record) + (opensmtpd-filter-phase-configuration-message record)))) + +(define (filter-phase-junks-after-commit? record) + (and (string=? (opensmtpd-filter-phase-configuration-decision record) "junk") + (string=? (opensmtpd-filter-phase-configuration-phase record) "commit"))) + +;; returns #t if list is a unique list of <opensmtpd-filter-configuration> or <opensmtpd-filter-phase-configuration> +;; returns # otherwise +(define (list-of-unique-filter-or-filter-phase? %filters) + (and (list? %filters) + (not (null? %filters)) + ;; this list is made up of only <opensmtpd-filter-phase-configuration> or <opensmtpd-filter-configuration> + (primitive-eval + (cons 'and (map (lambda (filter) + (or (opensmtpd-filter-configuration? filter) + (opensmtpd-filter-phase-configuration? filter))) + %filters))) + (not (contains-duplicate? %filters)))) + +(define (throw-error var %strings) + (display (apply string-append %strings)) + (throw 'bad! var)) + +;; this is used for sanitizing <opensmtpd-filter-phase-configuration> fieldname 'options' +(define (contains-duplicate? list) + (if (null? list) + #f + (or + ;; check if (car list) is in (cdr list) + (primitive-eval (cons 'or + (map (lambda (var) (equal? var (car list))) + (cdr list)))) + ;; check if (cdr list) contains duplicate + (contains-duplicate? (cdr list))))) + +;; given a list and procedure, this tests that each element of list is of type +;; ie: (list-of-type? list string?) tests each list is of type string. +(define (list-of-type? list proc?) + (if (and (list? list) + (not (null? list))) + (let loop ([list list]) + (if (null? list) + #t + (if (proc? (car list)) + (loop (cdr list)) + #f))) + #f)) + +(define (list-of-strings? list) + (list-of-type? list string?)) + +(define (list-of-unique-opensmtpd-option-configuration? list) + (and (list-of-type? + list opensmtpd-option-configuration?) + (not (contains-duplicate? list)))) + +(define (list-of-opensmtpd-ca-configuration? list) + (list-of-type? list opensmtpd-ca-configuration?)) + +(define (list-of-opensmtpd-pki-configuration? list) + (list-of-type? list opensmtpd-pki-configuration?)) + +(define (list-of-opensmtpd-listen-on-configuration? list) + (and (list-of-type? list opensmtpd-listen-on-configuration?) + (not (contains-duplicate? list)))) + +(define (list-of-unique-opensmtpd-match-configuration? list) + (and (list-of-type? list opensmtpd-match-configuration?) + (not (contains-duplicate? list)))) + +(define* (list-of-strings->string list + #:key + (string-delimiter ", ") + (postpend "") + (append "") + (drop-right-number 2)) + (string-drop-right + (string-append (let loop ([list list]) + (if (null? list) + "" + (string-append append (car list) postpend + string-delimiter + (loop (cdr list))))) + append) + drop-right-number)) + +;; at the moment I cannot define this by using list-of-type? +;; the first (not (null? assoc-list)) prevents that. +(define (assoc-list? assoc-list) + (list-of-type? assoc-list (lambda (pair) + (if (and (pair? pair) + (string? (car pair)) + (string? (cdr pair))) + #t + #f)))) + +(define* (variable->string var #:key (append "") (postpend " ")) + (let ([var (if (number? var) + (number->string var) + var)]) + (if var + (string-append append var postpend) + ""))) + +;; this procedure takes in one argument. +;; if that argument is an <opensmtpd-table-configuration> whose fieldname 'values' is an assoc-list, then it returns +;; #t, #f if otherwise. +;; TODO should I remove these two functions? And instead use the (opensmtpd-table-configuration-type) procedure? +(define (table-whose-data-are-assoc-list? table) + (if (not (opensmtpd-table-configuration? table)) + #f + (assoc-list? (opensmtpd-table-configuration-data table)))) + +;; this procedure takes in one argument +;; if that argument is an <opensmtpd-table-configuration> whose fieldname 'values' is a list of strings, then it returns +;; #t, #f if otherwise. +(define (table-whose-data-are-a-list-of-strings? table) + (if (not (opensmtpd-table-configuration? table)) + #f + (list-of-strings? (opensmtpd-table-configuration-data table)))) + +;; these next few functions help me to turn <table>s +;; into strings suitable to fit into "opensmtpd.conf". +(define (assoc-list->string assoc-list) + (string-drop-right + (let loop ([assoc-list assoc-list]) + (if (null? assoc-list) + "" + ;; pair is (cons "hello" "world") -> ("hello" . "world") + (let ([pair (car assoc-list)]) + (string-append + "\"" (car pair) "\"" + " = " + "\"" (cdr pair) "\"" + ", " + (loop (cdr assoc-list)))))) + 2)) + +;; can be of type: (quote list-of-strings) or (quote assoc-list) +(define (opensmtpd-table-configuration->string table) + (string-append "table " (opensmtpd-table-configuration-name table) " " + (let ([type (opensmtpd-table-configuration-type table)]) + (cond [(eq? type (quote list-of-strings)) + (string-append "{ " (list-of-strings->string (opensmtpd-table-configuration-data table) + #:append "\"" + #:drop-right-number 3 + #:postpend "\"") " }")] + [(eq? type (quote assoc-list)) + (string-append "{ " (assoc-list->string (opensmtpd-table-configuration-data table)) " }")] + [(eq? type (quote db)) + (string-append "db:" (opensmtpd-table-configuration-data table))] + [(eq? type (quote file)) + (string-append "file:" (opensmtpd-table-configuration-data table))] + [else (throw 'youMessedUp table)])) + " \n")) + +;; The following functions convert various records into strings. + +(define (opensmtpd-listen-on-configuration->string record) + (string-append "listen on " + (opensmtpd-listen-on-configuration-interface record) " " + (let* ([hostname (opensmtpd-listen-on-configuration-hostname record)] + [hostnames (if (opensmtpd-listen-on-configuration-hostnames record) + (opensmtpd-table-configuration-name (opensmtpd-listen-on-configuration-hostnames record)) + #f)] + [filters (opensmtpd-listen-on-configuration-filters record)] + [filter-name (if filters + (if (< 1 (length filters)) + (generate-filter-chain-name filters) + (if (opensmtpd-filter-configuration? (car filters)) + (opensmtpd-filter-configuration-name (car filters)) + (opensmtpd-filter-phase-configuration-name (car filters)))) + #f)] + [mask-src (opensmtpd-listen-on-configuration-mask-src record)] + [tag (opensmtpd-listen-on-configuration-tag record)] + [secure-connection (opensmtpd-listen-on-configuration-secure-connection record)] + [port (opensmtpd-listen-on-configuration-port record)] + [pki (opensmtpd-listen-on-configuration-pki record)] + [auth (opensmtpd-listen-on-configuration-auth record)] + [auth-optional (opensmtpd-listen-on-configuration-auth-optional record)]) + (string-append + (if mask-src + (string-append "mask-src ") + "") + (variable->string hostname #:append "hostname ") + (variable->string hostnames #:append "hostnames <" #:postpend "> ") + (variable->string filter-name #:append "filter \"" #:postpend "\" ") + (variable->string tag #:append "tag \"" #:postpend "\" ") + (if secure-connection + (cond [(string=? "smtps" secure-connection) + "smtps "] + [(string=? "tls" secure-connection) + "tls "] + [(string=? "tls-require" secure-connection) + "tls-require "] + [(string=? "tls-require-verify" secure-connection) + "tls-require verify "]) + "") + (variable->string port #:append "port " #:postpend " ") + (if pki + (variable->string (opensmtpd-pki-configuration-domain pki) #:append "pki ") + "") + (if auth + (string-append "auth " + (if (opensmtpd-table-configuration? auth) + (string-append "<" (opensmtpd-table-configuration-name auth) "> ") + "")) + "") + (if auth-optional + (string-append "auth-optional " + (if (opensmtpd-table-configuration? auth-optional) + (string-append "<" (opensmtpd-table-configuration-name auth-optional) "> ") + "")) + "") + "\n")))) + +(define (opensmtpd-listen-on-socket-configuration->string record) + (string-append "listen on socket " + (let* ([filters (opensmtpd-listen-on-socket-configuration-configuration-filters record)] + [filter-name (if filters + (if (< 1 (length filters)) + (generate-filter-chain-name filters) + (if (opensmtpd-filter-configuration? (car filters)) + (opensmtpd-filter-configuration-name (car filters)) + (opensmtpd-filter-phase-configuration-name (car filters)))) + #f)] + [mask-src (opensmtpd-listen-on-socket-configuration-configuration-mask-src record)] + [tag (opensmtpd-listen-on-socket-configuration-configuration-tag record)]) + (string-append + (if mask-src + (string-append "mask-src ") + "") + (variable->string filter-name #:append "filter \"" #:postpend "\" ") + (variable->string tag #:append "tag \"" #:postpend "\" ") + "\n")))) + +(define (opensmtpd-action-relay-configuration->string record) + (let ([backup (opensmtpd-action-relay-configuration-backup record)] + [backup-mx (opensmtpd-action-relay-configuration-backup-mx record)] + [helo (opensmtpd-action-relay-configuration-helo record)] + ;; helo-src can either be a string IP address or an <opensmtpd-table-configuration> + [helo-src (if (opensmtpd-action-relay-configuration-helo-src record) + (if (string? (opensmtpd-action-relay-configuration-helo-src record)) + (opensmtpd-action-relay-configuration-helo-src record) + (string-append "<\"" + (opensmtpd-table-configuration-name + (opensmtpd-action-relay-configuration-src record)) + "\">")) + #f)] + [domain (if (opensmtpd-action-relay-configuration-domain record) + (opensmtpd-table-configuration-name + (opensmtpd-action-relay-configuration-domain record)) + #f)] + [host (opensmtpd-action-relay-configuration-host record)] + [name (opensmtpd-action-relay-configuration-name record)] + [pki (if (opensmtpd-action-relay-configuration-pki record) + (opensmtpd-pki-configuration-domain (opensmtpd-action-relay-configuration-pki record)) + #f)] + [srs (opensmtpd-action-relay-configuration-srs record)] + [tls (opensmtpd-action-relay-configuration-tls record)] + [auth (if (opensmtpd-action-relay-configuration-auth record) + (opensmtpd-table-configuration-name + (opensmtpd-action-relay-configuration-auth record)) + #f)] + [mail-from (opensmtpd-action-relay-configuration-mail-from record)] + ;; src can either be a string IP address or an <opensmtpd-table-configuration> + [src (if (opensmtpd-action-relay-configuration-src record) + (if (string? (opensmtpd-action-relay-configuration-src record)) + (opensmtpd-action-relay-configuration-src record) + (string-append "<\"" + (opensmtpd-table-configuration-name + (opensmtpd-action-relay-configuration-src record)) + "\">")) + #f)] + ) + (string-append + "\"" + name + "\" " "relay " + ;;FIXME should I always quote the host fieldname? do I need to quote localhost via "localhost" ? + (variable->string host #:append "host \"" #:postpend "\" ") + (variable->string backup) + (variable->string backup-mx #:append "backup mx ") + (variable->string helo #:append "helo ") + (variable->string helo-src #:append "helo-src ") + (variable->string domain #:append "domain <\"" #:postpend "\"> ") + (variable->string host #:append "host ") + (variable->string pki #:append "pki ") + (variable->string srs) + (variable->string tls #:append "tls ") + (variable->string auth #:append "auth <" #:postpend "> ") + (variable->string mail-from #:append "mail-from ") + (variable->string src #:append "src ") + "\n"))) + +(define (opensmtpd-lmtp-configuration->string record) + (string-append "lmtp " + (opensmtpd-lmtp-configuration-destination record) + (if (opensmtpd-lmtp-configuration-rcpt-to record) + (begin + " " (opensmtpd-lmtp-configuration-rcpt-to record)) + ""))) + +(define (opensmtpd-mda-configuration->string record) + (string-append "mda " + (opensmtpd-mda-configuration-command record) " ")) + +(define (opensmtpd-maildir-configuration->string record) + (string-append "maildir " + "\"" + (if (opensmtpd-maildir-configuration-pathname record) + (opensmtpd-maildir-configuration-pathname record) + "~/Maildir") + "\"" + (if (opensmtpd-maildir-configuration-junk record) + " junk " + " "))) + +(define (opensmtpd-action-local-delivery-configuration->string record) + (let ([name (opensmtpd-action-local-delivery-configuration-name record)] + [method (opensmtpd-action-local-delivery-configuration-method record)] + [alias (if (opensmtpd-action-local-delivery-configuration-alias record) + (opensmtpd-table-configuration-name + (opensmtpd-action-local-delivery-configuration-alias record)) + #f)] + [ttl (opensmtpd-action-local-delivery-configuration-ttl record)] + [user (opensmtpd-action-local-delivery-configuration-user record)] + [userbase (if (opensmtpd-action-local-delivery-configuration-userbase record) + (opensmtpd-table-configuration-name + (opensmtpd-action-local-delivery-configuration-userbase record)) + #f)] + [virtual (if (opensmtpd-action-local-delivery-configuration-virtual record) + (opensmtpd-table-configuration-name + (opensmtpd-action-local-delivery-configuration-virtual record)) + #f)] + [wrapper (opensmtpd-action-local-delivery-configuration-wrapper record)]) + (string-append + "\"" name "\" " + (cond [(string? method) + (string-append method " ")] + [(opensmtpd-mda-configuration? method) + (opensmtpd-mda-configuration->string method)] + [(opensmtpd-lmtp-configuration? method) + (opensmtpd-lmtp-configuration->string method)] + [(opensmtpd-maildir-configuration? method) + (opensmtpd-maildir-configuration->string method)]) + ;; FIXME/TODO support specifying alias file:/path/to/alias-file ? + ;; I do not think that is something that I can do... + (variable->string alias #:append "alias <\"" #:postpend "\"> ") + (variable->string ttl #:append "ttl ") + (variable->string user #:append "user ") + (variable->string userbase #:append "userbase <\"" #:postpend "\"> ") + (variable->string virtual #:append "virtual <" #:postpend "> ") + (variable->string wrapper #:append "wrapper ")))) + +;; this function turns both opensmtpd-action-local-delivery-configuration and +;; opensmtpd-action-relay-configuration into strings. +(define (opensmtpd-action->string record) + (string-append "action " + (cond [(opensmtpd-action-local-delivery-configuration? record) + (opensmtpd-action-local-delivery-configuration->string record)] + [(opensmtpd-action-relay-configuration? record) + (opensmtpd-action-relay-configuration->string record)]) + " \n")) + +;; this turns option records found in <opensmtpd-match-configuration> into strings. +(define* (opensmtpd-option-configuration->string record + #:key + (space-after-! #f)) + (let ([not (opensmtpd-option-configuration-not record)] + [option (opensmtpd-option-configuration-option record)] + [regex (opensmtpd-option-configuration-regex record)] + [data (opensmtpd-option-configuration-data record)]) + (string-append + (if not + (if space-after-! + "! " + "!") + "") + option " " + (if regex + "regex " + "") + (if data + (if (opensmtpd-table-configuration? data) + (string-append "<" (opensmtpd-table-configuration-name data) "> ") + (string-append data " ")) + "")))) + +(define (opensmtpd-match-configuration->string record) + (string-append "match " + (let* ([action (opensmtpd-match-configuration-action record)] + [name (cond [(opensmtpd-action-relay-configuration? action) + (opensmtpd-action-relay-configuration-name action)] + [(opensmtpd-action-local-delivery-configuration? action) + (opensmtpd-action-local-delivery-configuration-name action)] + [else 'reject])] + [options (opensmtpd-match-configuration-options record)]) + (string-append + (if options + (apply string-append + (map opensmtpd-option-configuration->string options)) + "") + (if (string? name) + (string-append "action " "\"" name "\" ") + "reject ") + "\n")))) + +(define (opensmtpd-ca-configuration->string record) + (string-append "ca " (opensmtpd-ca-configuration-name record) " " + "cert \"" (opensmtpd-ca-configuration-file record) "\"\n")) + +(define (opensmtpd-pki-configuration->string record) + (let ([domain (opensmtpd-pki-configuration-domain record)] + [cert (opensmtpd-pki-configuration-cert record)] + [key (opensmtpd-pki-configuration-key record)] + [dhe (opensmtpd-pki-configuration-dhe record)]) + (string-append "pki " domain " " "cert \"" cert "\" \n" + "pki " domain " " "key \"" key "\" \n" + (if dhe + (string-append + "pki " domain " " "dhe " dhe "\n") + "")))) + +(define (generate-filter-chain-name list-of-filters) + (string-drop-right (apply string-append + (flatten + (map (lambda (filter) + (list + (if (opensmtpd-filter-configuration? filter) + (opensmtpd-filter-configuration-name filter) + (opensmtpd-filter-phase-configuration-name filter)) + "-")) + list-of-filters))) + 1)) + +;; this procedure takes in a list of <opensmtpd-filter-configuration> and <opensmtpd-filter-phase-configuration>, +;; returns a string of the form: +;; filter "uniquelyGeneratedName" chain chain { "filter-name", "filter-name2" [, ...]} +(define (opensmtpd-filter-chain->string list-of-filters) + (string-append "filter \"" + (generate-filter-chain-name list-of-filters) + "\" " + "chain {" + (string-drop-right + (apply string-append + (flatten + (map (lambda (filter) + (list + "\"" + (if (opensmtpd-filter-configuration? filter) + (opensmtpd-filter-configuration-name filter) + (opensmtpd-filter-phase-configuration-name filter)) + "\", ")) + list-of-filters)) + ) 2) + "}\n")) + +(define (opensmtpd-filter-phase-configuration->string record) + (let ([name (opensmtpd-filter-phase-configuration-name record)] + [phase (opensmtpd-filter-phase-configuration-phase record)] + [decision (opensmtpd-filter-phase-configuration-decision record)] + [options (opensmtpd-filter-phase-configuration-options record)] + [message (opensmtpd-filter-phase-configuration-message record)] + [value (opensmtpd-filter-phase-configuration-value record)]) + (string-append "filter " + "\"" name "\" " + "phase " phase " " + "match " + (apply string-append ; turn the options into a string + (flatten + (map (lambda (option) + (opensmtpd-option-configuration->string option #:space-after-! #f)) + options))) + " " + decision " " + (if (string-in-list? decision (list "reject" "disconnect")) + (string-append "\"" message "\"") + "") + (if (string=? "rewrite" decision) + (string-append "rewrite " (number->string value)) + "") + "\n"))) + +;; filters elements may be <opensmtpd-filter-configuration>, <opensmtpd-filter-phase-configuration>, +;; and lists that look like (list (opensmtpd-filter-configuration...) (opensmtpd-filter-phase-configuration ...) +;; ...) +;; this function converts it to a string. +;; Consider if a user passed in a valid <opensmtpd-configuration>, whose total valid filters +;; so that (get-opensmtpd-filters (opensmtpd-configuration)) returns +;; look like this: (we will call this list "total filters"): +;; (list (opensmtpd-filter +;; (name "rspamd") +;; (proc "rspamd")) +;; (list (opensmtpd-filter-phase-configuration ; this is a listen-on, with a filter-chain. +;; (name "dkimsign") +;; ...) +;; (opensmtpd-filter +;; (name "rspamd") +;; (proc "rspamd")))) +;; +;; did you notice that filter "rspamd" is listed twice? How do you make sure that it is NOT +;; printed twice in smtpd.conf? +;; 1st flatten "total filters", then remove its duplicates. Then print all of those filters. +;; 2nd now we go through "total filters", and we only print the non-filter-chains. +(define (opensmtpd-filters->string filters) + ;; first display the unique <opensmtpd-filter-configuration>s. and <opensmtpd-filter-phase-configuration>s. + ;; to do this: flatten filters, then remove duplicates. + (string-append + (apply string-append + (map (lambda (filter) + (cond ((opensmtpd-filter-phase-configuration? filter) + (opensmtpd-filter-phase-configuration->string filter)) + (else ; you are a <opensmtpd-filter-configuration> + (string-append "filter " + "\"" (opensmtpd-filter-configuration-name filter) "\" " + (if (opensmtpd-filter-exec filter) + "proc-exec " + "proc ") + "\"" (opensmtpd-filter-configuration-proc filter) "\"" + "\n")))) + (delete-duplicates (flatten filters)))) + ;; now we have to print the filter chains. + (apply string-append + (remove boolean? + (map (lambda (filter) + (cond ((list? filter) + (opensmtpd-filter-chain->string filter)) + (else ; you are a <opensmtpd-filter-configuration> + #f))) + filters))))) + +(define (opensmtpd-configuration-listen->string string) + (string-append + "include \"" string "\"\n")) + +(define (opensmtpd-configuration-srs->string record) + (let ([key (opensmtpd-srs-configuration-key record)] + [backup-key (opensmtpd-srs-configuration-backup-key record)] + [ttl-delay (opensmtpd-srs-configuration-ttl-delay record)]) + (string-append + (variable->string key #:append "srs key " #:postpend "\n") + (variable->string backup-key #:append "srs key backup " #:postpend "\n") + (variable->string ttl-delay #:append "srs ttl " #:postpend "\n") + "\n"))) + +;; TODO make sure all options here work! I just fixed limit-max-rcpt! +(define (opensmtpd-smtp-configuration->string record) + (let ([ciphers (opensmtpd-smtp-configuration-ciphers record)] + [limit-max-mails (opensmtpd-smtp-configuration-limit-max-mails record)] + [limit-max-rcpt (opensmtpd-smtp-configuration-limit-max-rcpt record)] + [max-message-size (opensmtpd-smtp-configuration-max-message-size record)] + [sub-addr-delim (opensmtpd-smtp-configuration-sub-addr-delim record)]) + (string-append + (variable->string ciphers #:append "smtp ciphers " #:postpend "\n") + (variable->string limit-max-mails #:append "smtp limit max-mails " #:postpend "\n") + (variable->string limit-max-rcpt #:append "smtp limit max-rcpt " #:postpend "\n") + (variable->string max-message-size #:append "smtp max-message-size " #:postpend "\n") + (variable->string sub-addr-delim #:append "smtp sub-addr-delim " #:postpend "\n") + "\n"))) + +(define (opensmtpd-configuration-queue->string record) + (let ([compression (opensmtpd-queue-configuration-compression record)] + [encryption (opensmtpd-queue-configuration-encryption record)] + [ttl-delay (opensmtpd-queue-configuration-ttl-delay record)]) + (string-append + (if compression + "queue compression\n" + "") + (if encryption + (string-append + "queue encryption " + (if (not (boolean? encryption)) + encryption + "") + "\n") + "") + (if ttl-delay + (string-append "queue ttl" ttl-delay "\n") + "")))) + +;; build a list of <opensmtpd-action> from +;; opensmtpd-configuration-matches, which is a list of <opensmtpd-match-configuration>. +;; Each <opensmtpd-match-configuration> has a fieldname 'action', which accepts an <opensmtpd-action>. +(define (get-opensmtpd-actions record) + (define opensmtpd-actions + (let loop ([list (opensmtpd-configuration-matches record)]) + (if (null? list) + '() + (cons (opensmtpd-match-configuration-action (car list)) + (loop (cdr list)))))) + (delete-duplicates (append opensmtpd-actions))) + +;; build a list of opensmtpd-pki-configurations from +;; opensmtpd-configuration-listen-ons and +;; get-opensmtpd-actions +(define (get-opensmtpd-pki-configurations record) + ;; TODO/FIXME/maybe/wishlist could get-opensmtpd-actions -> NOT have an opensmtpd-action-relay-configuration? + ;; I think so. And if it did NOT have a relay configuration, then action-pkis would be '() when + ;; it needs to be #f. because if the opensmtpd-configuration has NO pkis, then this function will + ;; return '(), when it should return #f. If it returns '(), then opensmtpd-configuration-fieldname->string will + ;; print the string "\n" instead of "" + (define action-pkis + (let loop1 ([list (get-opensmtpd-actions record)]) + (if (null? list) + '() + (if (and (opensmtpd-action-relay-configuration? (car list)) + (opensmtpd-action-relay-configuration-pki (car list))) + (cons (opensmtpd-action-relay-configuration-pki (car list)) + (loop1 (cdr list))) + (loop1 (cdr list)))))) + ;; FIXME/TODO/maybe/wishlist + ;; this could be #f aka left blank. aka there are no listen-ons records with pkis. + ;; aka there are no lines in the configuration like: + ;; listen on eth0 tls pki smtp.gnucode.me in that case the smtpd.conf will have an extra "\n" + (define listen-on-pkis + (let loop2 ([list (opensmtpd-configuration-listen-ons record)]) + (if (null? list) + '() + (if (opensmtpd-listen-on-configuration-pki (car list)) + (cons (opensmtpd-listen-on-configuration-pki (car list)) + (loop2 (cdr list))) + (loop2 (cdr list)))))) + (delete-duplicates (append action-pkis listen-on-pkis))) + +;; takes in a <opensmtpd-configuration> and returns a list whose elements are <opensmtpd-filter-configuration>, +;; <opensmtpd-filter-phase-configuration>, and a filter-chain. +;; It returns a list of <opensmtpd-filter-configuration> and/or <opensmtpd-filter-phase-configuration> +;; here's an example of what this procedure might return: +;; (list (opensmtpd-filter-configuration...) (opensmtpd-filter-phase-configuration ...) +;; (openmstpd-filter ...) (opensmtpd-filter-phase-configuration ...) +;; ;; this next list is a filter-chain. +;; (list (opensmtpd-filter-phase-configuration ...) (opensmtpd-filter-configuration...))) +;; +;; This procedure handles filter chains a little odd. +(define (get-opensmtpd-filters record) + (define list-of-listen-on-records (if (opensmtpd-configuration-listen-ons record) + (opensmtpd-configuration-listen-ons record) + '())) + + (define listen-on-socket-filters + (if (opensmtpd-listen-on-socket-configuration-configuration-filters (opensmtpd-configuration-listen-on-socket record)) + (opensmtpd-listen-on-socket-configuration-configuration-filters (opensmtpd-configuration-listen-on-socket record)) + '())) + + (delete-duplicates + (append (remove boolean? + (map-in-order (lambda (listen-on-record) ; get the filters found in the <listen-on-record>s + (if (and (opensmtpd-listen-on-configuration-filters listen-on-record) + (= 1 (length (opensmtpd-listen-on-configuration-filters + listen-on-record)))) + (car (opensmtpd-listen-on-configuration-filters listen-on-record)) + (opensmtpd-listen-on-configuration-filters listen-on-record))) + list-of-listen-on-records)) + listen-on-socket-filters))) + +(define (flatten . lst) + "Return a list that recursively concatenates all sub-lists of LST." + (define (flatten1 head out) + (if (list? head) + (fold-right flatten1 out head) + (cons head out))) + (fold-right flatten1 '() lst)) + +;; This function takes in a record, or list, or anything, and returns +;; a list of <opensmtpd-table-configuration>s assuming the thing you passed into it had +;; any <opensmtpd-table-configuration>s. +;; +;; is object record? call func on it's fieldnames +;; is object list? loop through it's fieldnames calling func on it's records +;; is object #f or string? or '()? -> #f +(define (get-opensmtpd-tables value) + (delete-duplicates + (remove boolean? (flatten ;; turn (list '(1) '(2 '(3))) -> '(1 2 3) + (cond ((opensmtpd-table-configuration? value) + value) + ((record? value) + (let* ([record-type (record-type-descriptor value)] + [list-of-record-fieldnames (record-type-fields record-type)]) + (map (lambda (fieldname) + (get-opensmtpd-tables ((record-accessor record-type fieldname) value))) + list-of-record-fieldnames))) + ((and (list? value) (not (null? value))) + (map get-opensmtpd-tables value)) + (else #f)))))) + +(define (opensmtpd-configuration-fieldname->string record fieldname-accessor record->string) + (if (fieldname-accessor record) + (begin + (string-append + (list-of-records->string (fieldname-accessor record) record->string) "\n")) + "")) + +(define (list-of-records->string list-of-records record->string) + (string-append + (cond [(not (list? list-of-records)) + (record->string list-of-records)] + [else + (let loop ([list list-of-records]) + (if (null? list) + "" + (string-append + (record->string (car list)) + (loop (cdr list)))))]))) + + +;; FIXME/TODO should I use format here srfi-28 ? +;; web.scm nginx does a (format #f "string" "another string") +;; this could be a list like (list (file-append opensmtpd-dkimsign "/libexec/filter") "-d gnucode.me -s /path/to/selector.cert") +;; Then opensmtpd-configuration->mixed-text-file could be rewritten to be something like +;; (mixed-text-file (eval `(string-append (opensmtpd-configuration-fieldname->string ...)) (gnu services mail))) +(define (opensmtpd-configuration->mixed-text-file record) + ;; should I use this named let, or should I give this a name, or not use it at all... + ;; eg: (write-all-fieldnames (list (cons fieldname fieldname->string) (cons fieldname2 fieldname->string))) + ;; (let loop ([list (list (cons opensmtpd-configuration-includes (lambda (string) + ;; (string-append + ;; "include \"" string "\"\n"))) + ;; (cons opensmtpd-configuration-smtp opensmtpd-smtp->string) + ;; (cons opensmtpd-configuration-srs opensmtpd-srs->string))]) + ;; (if (null? list) + ;; "" + ;; (string-append (opensmtpd-configuration-fieldname->string record + ;; (caar list) + ;; (cdar list)) + ;; (loop (cdr list))))) + + (mixed-text-file "opensmtpd.conf" + (string-append + ;; write out the includes + (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-includes + opensmtpd-configuration-listen->string) + (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-bounce + (lambda (%bounce) + (if %bounce + (list-of-strings->string %bounce) + ""))) + (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-smtp + opensmtpd-smtp-configuration->string) + (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-srs + opensmtpd-configuration-srs->string) + (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-queue + opensmtpd-configuration-queue->string) + ;; write out the mta-max-deferred + (opensmtpd-configuration-fieldname->string + record opensmtpd-configuration-mta-max-deferred + (lambda (var) + (string-append "mta max-deferred " + (number->string (opensmtpd-configuration-mta-max-deferred record)) "\n"))) + ;;write out all the tables + (opensmtpd-configuration-fieldname->string record get-opensmtpd-tables opensmtpd-table-configuration->string) + ;; TODO should I change the below line of code into these two lines of code? + ;;(opensmtpd-configuration-fieldname->string record get-opensmtpd-filters-and-filter-phases opensmtpd-filter-and-filter-phase->string) + ;;(opensmtpd-configuration-fieldname->string record get-opensmtpd-filter-chains opensmtpd-filter-chain->string) + ;; write out all the filters + (opensmtpd-filters->string (get-opensmtpd-filters record)) + ;; write out all the cas + (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-cas opensmtpd-ca-configuration->string) + ;; write out all the pkis + (opensmtpd-configuration-fieldname->string record get-opensmtpd-pki-configurations opensmtpd-pki-configuration->string) + ;; write all of the listen-on-records + (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-listen-ons + opensmtpd-listen-on-configuration->string) + (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-listen-on-socket + opensmtpd-listen-on-socket-configuration->string) + ;; write all the actions + (opensmtpd-configuration-fieldname->string record get-opensmtpd-actions + opensmtpd-action->string) + ;; write all of the matches + (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-matches opensmtpd-match-configuration->string)))) + (define %default-opensmtpd-config-file (plain-file "smtpd.conf" " -- 2.36.1
guix-patches@HIDDEN
:bug#56046
; Package guix-patches
.
Full text available.Received: (at 56046) by debbugs.gnu.org; 17 Jun 2022 21:54:32 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Fri Jun 17 17:54:32 2022 Received: from localhost ([127.0.0.1]:47131 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1o2JvE-0002Gh-CT for submit <at> debbugs.gnu.org; Fri, 17 Jun 2022 17:54:32 -0400 Received: from mx1.dismail.de ([78.46.223.134]:21709) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <jbranso@HIDDEN>) id 1o2JvC-0002GT-Gv for 56046 <at> debbugs.gnu.org; Fri, 17 Jun 2022 17:54:31 -0400 Received: from mx1.dismail.de (localhost [127.0.0.1]) by mx1.dismail.de (OpenSMTPD) with ESMTP id 6e36d91a for <56046 <at> debbugs.gnu.org>; Fri, 17 Jun 2022 23:54:23 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed; d=dismail.de; h=from:to:cc :subject:date:message-id:mime-version:content-type :content-transfer-encoding; s=20190914; bh=q2Ce8q6uAxJu4Oen6Bhg4 +NbiTB2Phd+AjQxSfHsI3I=; b=DA2lvnFTF/3ht7A4bZpJqFMFSitWzwzlI3B81 iPlTnzmYO1gl0dlTpbcAhtM2SJL1AwI/Zcwzms6tBP3Q58azxDh8oJ0082t/1fcR QXT7ORoAFKCOERnj6Wkp/5ib5Z7oTY8qEh69sf4FQN8ywAew57wGq7RxeRieALrf MFx0ZFM+ew3lk59FyT/RlRLKbueRnGFNsDHLu25Wg7rw3WIIng1fZY9dcBSOcVA4 ddm9R+C6j2RVeJW7AmVfs0H2fF4i+JL4j5A/Pb6eKKnZa9erDT7bRz/76SoFLYLD MZKe1kYsprR+CXm3HWt4S8rY5n4t48ZHQz26LFtyDqmO1D/dw== Received: from smtp2.dismail.de (<unknown> [10.240.26.12]) by mx1.dismail.de (OpenSMTPD) with ESMTP id 779a86ac for <56046 <at> debbugs.gnu.org>; Fri, 17 Jun 2022 23:54:22 +0200 (CEST) Received: from smtp2.dismail.de (localhost [127.0.0.1]) by smtp2.dismail.de (OpenSMTPD) with ESMTP id caf993c3 for <56046 <at> debbugs.gnu.org>; Fri, 17 Jun 2022 23:54:22 +0200 (CEST) Received: by dismail.de (OpenSMTPD) with ESMTPSA id c1362d04 (TLSv1.3:AEAD-AES256-GCM-SHA384:256:NO); Fri, 17 Jun 2022 23:54:18 +0200 (CEST) From: Joshua Branson <jbranso@HIDDEN> To: 56046 <at> debbugs.gnu.org Subject: [PATCH] gnu: services: opensmtpd-records-task-list.org: Some notes about how I thought about building this service. And some additional task lists, as well as the WIP documentation. Date: Fri, 17 Jun 2022 17:54:07 -0400 Message-Id: <20220617215407.21290-1-jbranso@HIDDEN> X-Mailer: git-send-email 2.36.1 MIME-Version: 1.0 Content-Type: text/plain; charset=y Content-Transfer-Encoding: 8bit X-Debbugs-Envelope-To: 56046 Cc: Joshua Branson <jbranso@HIDDEN> X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> --- gnu/services/opensmtpd-records-task-list.org | 5122 ++++++++++++++++++ 1 file changed, 5122 insertions(+) create mode 100644 gnu/services/opensmtpd-records-task-list.org diff --git a/gnu/services/opensmtpd-records-task-list.org b/gnu/services/opensmtpd-records-task-list.org new file mode 100644 index 0000000000..c138aab8fe --- /dev/null +++ b/gnu/services/opensmtpd-records-task-list.org @@ -0,0 +1,5122 @@ +#+title: Opensmtpd Records Task List +#+AUTHOR: Joshua Branson + + +(service (@ (gnu services mail) opensmtpd-service-type) + ((@ (gnu services mail) opensmtpd-configuration) + (config-file …))) + +* tasks +** PROJ I have decent data structures. now let's get some good code. [0/6] +*** why are good data structures important? +**** nckx's advice: use a simple 1-1 mapping +"...as I think Guix services ought to faithfully wrap the native +syntax whenever possible (implement alternative simple APIs on top of +that — fine)." + +-nckx from irc on #guix + +**** To follow nckx's advice, one might create the =<opensmtpd-service>= like this: +#+BEGIN_SRC scheme + (service opensmtpd-service + (opensmtpd-configuration + (includes ...) + (tables ...) + (pkis ...) + (filters ...) + (listen-on ...) + (actions ...) + (matches ...))) +#+END_SRC + +Defining the service this way, makes it VERY easy from a development point of +view. But it makes it possible for users to create simple mistakes when +defining the service. + +For example, it is possible to define an nginx service that will successfully +reconfigure the system. BUT after reboot nginx refuses to start. Why? Who knows. +Guix won't tell you. Neither will the Shepherd. To fix this, the user has to go +digging into the nginx logs, and he might not know where to find those. If +possible, when the user specificies a =<opensmtpd-configuration>= that has +obvious errors, then the guix services should make reconfigure fail and print a +helpful error message. + +**** BUT it would be better if the service uses better datastructures. + +I should follow nckx's advice, and Linus' advice: good programmers use good +datastructures. If you have good datastructures, then your code will almost +write itself. + +It might make the service a little harder to develop, but end-users will find +the service easier to use. This would eliminate common errors like misspellings +and give appropriate error messages. Practically it would ensure each +=<opensmtpd-match-configuration>= has a corresponding =<opensmtpd-action>=, +creating a table name and then misspelling the table name later, and defining +a table but never using it, etc. + +**** Example configuration + +#+BEGIN_SRC scheme +(service opensmtpd-service-type + (let ([interface "lo"] + [creds-table (opensmtpd-table-configuration + (name "creds") + (data + (list + (cons "joshua" + "$6$Ec4m8FgKjT2F/03Y$k66ABdse9TzCX6qaALB3WBL9GC1rmAWJmaoSjFMpbhzat7DOpFqpnOwpbZ34wwsQYIK8RQlqwM1I/v6vsRq86."))))] + [receive-action (opensmtpd-action-local-delivery-configuration + (name "receive") + (method (opensmtpd-maildir-configuration + (pathname "/home/%{rcpt.user}/Maildir") + (junk #t))) + (virtual (opensmtpd-table-configuration + (name "virtual") + (data (list "josh" "jbranso@HIDDEN")))))] + [filter-dkimsign (opensmtpd-filter-configuration + (name "dkimsign") + (exec #t) + (proc (string-append "/path/to/dkimsign -d gnucode.me -s 2021-09-22 -c relaxed/relaxed -k " + "/path/to/dkimsign-key user nobody group nobody")))] + [smtp.gnucode.me (opensmtpd-pki-configuration + (domain "smtp.gnucode.me") + (cert "opensmtpd.scm") + (key "opensmtpd.scm"))]) + (opensmtpd-configuration + (mta-max-deferred 50) + (queue + (opensmtpd-queue-configuration + (compression #t))) + (smtp + (opensmtpd-smtp-configuration + (max-message-size "10M"))) + (srs + (opensmtpd-srs-configuration + (ttl-delay "5d"))) + (listen-ons + (list + (opensmtpd-listen-on-configuration + (interface interface) + (port 25) + (secure-connection "tls") + (filters (list (opensmtpd-filter-phase-configuration + (name "noFRDNS") + (phase "commit") + (options (list (opensmtpd-option-configuration + (option "fcrdns") + (not #t)))) + (decision "disconnect") + (message "433 No FCRDNS")))) + (pki smtp.gnucode.me)) + ;; this lets local users logged into the system via ssh send email + (opensmtpd-listen-on-configuration + (interface interface) + (port 465) + (secure-connection "smtps") + (pki smtp.gnucode.me) + (auth creds-table) + (filters (list filter-dkimsign))) + (opensmtpd-listen-on-configuration + (interface interface) + (port 587) + (secure-connection "tls-require") + (pki smtp.gnucode.me) + (auth creds-table) + (filters (list filter-dkimsign))))) + (matches (list + (opensmtpd-match-configuration + (action (opensmtpd-action-relay-configuration + (name "relay"))) + (options (list (opensmtpd-option-configuration + (option "for any")) + (opensmtpd-option-configuration + (option "from any")) + (opensmtpd-option-configuration + (option "auth"))))) + (opensmtpd-match-configuration + (action receive-action) + (options (list (opensmtpd-option-configuration + (option "from any")) + (opensmtpd-option-configuration + (option "for domain") + (data (opensmtpd-table-configuration + (name "domain-table") + (data (list "gnucode.me" "gnu-hurd.com")))))))) + (opensmtpd-match-configuration + (action receive-action) + (options (list (opensmtpd-option-configuration + (option "for local")))))))))) +#+END_SRC + +:OldConfigurationSyntax: +#+BEGIN_SRC scheme + (service opensmtpd-service-type + (opensmtpd-configuration + (pkis (list + (opensmtpd-pki-configuration + ...))) + (tables (list + (opensmtpd-table-configuration + ...) + (opensmtpd-table-configuration + ...))) + (listen-ons + (list + (opensmtpd-listen-on-configuration + ...) + (opensmtpd-listen-on-configuration + ...))) + (actions + (list + (opensmtpd-action + ...) + (opensmtpd-action + ...))) + (matches (list + (opensmtpd-match-configuration + ...) + (opensmtpd-match-configuration + ...))) + (filter-chains + (list + (opensmtpd-filter-chain + (name "dropDumbEmails") + (filter-names (list "nofcrdnsDisconnect" + "nordnsDisconnect"))))) + (filter-phases + (list (opensmtpd-filter-phase-configuration + ...) + (opensmtpd-filter-phase-configuration + ...))))) +#+END_SRC + +Here you have to define the =pki=s twice! You define it once in the =pkis= +section, and then you reference it later. This could potentially cause a +mispelling error. That would be silly to debug as an end-user. + +:END: + +*** PROJ tweek the code for =<opensmtpd-filter-configuration>= & =<opensmtpd-filter-phase-configuration>= records [4/7] +**** Why I chose the current datastructures of =<opensmtpd-filter-configuration>= & =<opensmtpd-filter-phase-configuration>= + +According to the man page I have a four kinds of filters: + +#+BEGIN_EXAMPLE +1. filter chain-name chain {filter-name [, ...]} + Register a chain of filters chain-name, consisting of the filters listed from filter-name. + Filters part of a filter chain are executed in order of declaration for each phase that + they are registered for. A filter chain may be used in place of a filter for any direc‐ + tive but filter chains themselves. +2. filter filter-name phase phase-name match conditions decision + Register a filter filter-name. A decision about what to do with the mail is taken at + phase phase-name when matching conditions. Phases, matching conditions, and decisions are + described in MAIL FILTERING, below. +3. filter filter-name proc proc-name + Register "proc" filter filter-name backed by the proc-name process. +4. filter filter-name proc-exec command + Register and execute "proc" filter filter-name from command. If command starts with a + slash it is executed with an absolute path, else it will be run from + “/gnu/store/2d13sdz76ldq8zgwv4wif0zx7hkr3mh2-opensmtpd-6.8.0p2/libexec/opensmtpd”. +#+END_EXAMPLE + +=chain-name= could be easily represented as a list of filters. in the +opensmtpd-configuration-filter fieldname: + +#+BEGIN_SRC scheme +(opensmtpd-configuration + (listen-on + (filter + (list (opensmtpd-filter-configuration) + (opensmtpd-filter-configuration) + (opensmtpd-filter-configuration))))) +#+END_SRC + +For example, this is probably easier + +#+BEGIN_SRC scheme + (opensmtpd-configuration + (actions (list + (opensmtpd-action + (name "relay") + (method (opensmtpd-relay-configuration + (domain (opensmtpd-table-configuration + ;;(name "domains") ;; with some smart coding, the name would NOT be needed. + (data (list + "gnucode.me" + "gnu-hurd.com")))))))))) +#+END_SRC + +than the alternative: + +#+BEGIN_SRC scheme + (opensmtpd-configuration + (tables (list + (opensmtpd-table-configuration + (name "domains") + (data (list + "gnucode.me" + "gnu-hurd.com"))))) + (actions (list + (opensmtpd-action + (name "relay") + (method (opensmtpd-relay-configuration + (domain "domains"))))))) +#+END_SRC + +**** some example code for each of the 3 types of filters + +1. filter phase +#+BEGIN_SRC scheme +(opensmtpd-filter-phase-configuration + (name "phase") + (phase "connect") + (options + (list + (opensmtpd-option-configuration + (option "src") + (not #t) + (regex #t) + (table (opensmtpd-table-configuration (name "src-option-table") + (data (list "cat" "hot"))))))) + (decision "reject") + (message "We do not want spam here!")) +#+END_SRC + +#+RESULTS: + +2. filter proc +this is a filter-proc +#+BEGIN_SRC scheme +(opensmtpd-filter + (name "proc") + (proc "dkimsign")) +#+END_SRC + +3. filter proc-exec +#+BEGIN_SRC scheme +(opensmtpd-filter + (name "proc") + (exec #t) + (proc "dkimsign")) +#+END_SRC + +***** Why am I doing the data structure like the above? + +filter-proc and proc-exec as defined in man smtpd.conf can both use the same +<opensmtpd-filter-configuration> record. That works just fine. + +But filter-phase is a different beast. I do NOT want someone to accidentally +define something like the following which is BAD data: + +#+BEGIN_SRC scheme +(opensmtpd-filter + (name "proc") + (exec #t) + (proc "dkimsign")) + +#+END_SRC +**** NO Is it advantageous/desireable to merge =<opensmtpd-filter-configuration>= & <opensmtpd-filter-phase-configuration> + +When a user creates a filter, he is either going to create a +=<opensmtpd-filter-configuration>~ or an ~<opensmtpd-filter-phase>= NOT both. If +we define separate records, then it is impossible for a user to accidentally +define a filter record using fieldnames from both filter types. eg: + +#+BEGIN_SRC scheme +(opensmtpd-filter-configuration + (name "filter") + (exec #t) + (proc "dkimsign") + (phase "connect")) ;; this phase should NOT be there. this is a <opensmtpd-filter-configuration> +#+END_SRC + +If =<opensmtpd-filter-configuration>= & =<opensmtpd-filter-phase-configuration>= are separte then the above +would correctly result in an error message for free. +**** TODO make <opensmtpd-filter-configuration> fieldname 'proc' accept a list of strings and/or a <gexp>s + +Suppose you want to do dkimsigning in smtpd.conf. Here is how you might +register the official opensmtpd dkimsign filter: + +#+BEGIN_EXAMPLE +filter "dkimsign" proc-exec "filter-dkimsign -d <domain> -s <selector> \ + -k /gnu/store/2d13sdz76ldq8zgwv4wif0zx7hkr3mh2-opensmtpd-6.8.0p2/etc/dkim/private.key" user _dkimsign group _dkimsign +#+END_EXAMPLE + +For example my hacky code to do dkimsigning looks like: + +#+BEGIN_SRC scheme +filter \"dkimsign\" \ + proc-exec \"" path-to-filter-dkimsign " -d gnucode.me -s 2021-09-22 -c relaxed/relaxed -k " etc-dkimsign-key-file + " \" \ + user nobody group nogroup + +#+END_SRC + +Here is some example code of how we could create an +=<opensmtpd-filter-configuration>= that registers a dkimsign filter. The code +below probably will NOT work. + +#+BEGIN_SRC scheme +(let ((etc-dkimsign-key-file "filename.key") + (path-to-dkimsign-key "/etc/opensmtpd/"))) +(opensmtpd-filter-configuration + (name "dkimsign") + (proc (list + (file-append opensmtpd-filter-dkimsign "/libexec/opensmtpd/filter-dkimsign") + " -d gnucode.me -s 2021-09-22 -c relaxed/relaxed -k " + ~#(let ([UID-nobody (passwd:uid (getpw "nobody"))] + [GID-root (group:gid (getgr "root"))] + [GID-nogroup (group:gid (getgr "nogroup"))]) + ;; #o550 user root can read/open the directory + ;; and the group "root" can read/open the directory. + ;; change these two lines to (mkdir-p) ? + (unless (file-exists? "/etc/opensmtpd") + (mkdir "/etc/opensmtpd" #o550)) + + ;; root can read/write/execute on directory dkimsign + ;; group "root" can read and execute + (unless (file-exists? "/etc/opensmtpd/dkimsign") + (mkdir "/etc/opensmtpd/dkimsign" #o750)) + + (copy-file path-to-dkimsign-key etc-dkimsign-key-file) + ;; make the etc-dkimsign-key-file to owned by nobody and group nogroup. + (chown "/etc/opensmtpd" UID-nobody GID-root) + (chown "/etc/opensmtpd/dkimsign" UID-nobody GID-root) + (chown etc-dkimsign-key-file UID-nobody GID-nogroup) + "/etc/opensmtpd/dkimsign/2021-09-22-rsa1024-gnucode.me.key") + "user nobody group nogroup")) + (exec #)) +#+END_SRC + +Here is the full for how I currently run opensmtpd: + +#+BEGIN_SRC sh :dir ~/prog/gnu/guix/guix-config/linode-guix-system-configuration/ :results raw +cat opensmtpd.scm +#+END_SRC + +#+RESULTS: +#+BEGIN_SRC scheme +(define-module (opensmtpd) + #:use-module (guix gexp) + #:use-module (guix records) + #:use-module (gnu packages mail) ;;for finding location of filter-dkimsign + #:export ( + %smtpd.conf + )) + + +;; to create credentials for now, I need to do the following: +;; find /gnu/store -name '*encrypt*' | grep opensmtpd +;; /gnu/store/blah/opensmtpd/encrypt +(define creds + (plain-file "creds" + ;; this is my joshua's password for server. This can be found on dobby's /home/joshua/.authinfo/ + "joshua $6$Ec4m8FgKjT2F/03Y$k66ABdse9TzCX6qaALB3WBL9GC1rmAWJmaoSjFMpbhzat7DOpFqpnOwpbZ34wwsQYIK8RQlqwM1I/v6vsRq86.")) + +(define vdoms + (plain-file + "vdoms" + "gnucode.me +gnu-hurd.com")) + +(define vusers + (plain-file + "vusers" + "joshua@HIDDEN joshua +jbranso@HIDDEN joshua +postmaster@HIDDEN joshua")) + +(define path-to-filter-dkimsign + (file-append opensmtpd-filter-dkimsign "/libexec/opensmtpd/filter-dkimsign")) + +(define path-to-dkimsign-key (string-append (getcwd) "/email-dkim-ssh-keys/2021-09-22-rsa1024-gnucode.me.key")) +(define etc-dkimsign-key-file "/etc/opensmtpd/dkimsign/2021-09-22-rsa1024-gnucode.me.key") + +;; FIXME: This should become a derivation. Currently it just runs when I evaluate +;; %smtpd.conf. For example it should look like this? +;; (define build-exp +;; #~(begin +;; (mkdir #$output) +;; (chdir #$output) +;; (symlink (string-append #$coreutils "/bin/ls") +;; "list-files"))) + +;; I will need to extend the opensmtpd service, to create a directory +;; in etc. This line needs to be added to etc-service. +;; (service-extension etc-service-type opensmtpd-etc-service) +;; I'll then need to create a opensmtpd-etc-service procedure. ganeti has +;; a good example. + +;; It should also use the /etc service, which is a service for creating +;; directories and files in /etc ? +(define (create-etc-dkimsign-key-file) + #~(let ([UID-nobody (passwd:uid (getpw "nobody"))] + [GID-root (group:gid (getgr "root"))] + [GID-nogroup (group:gid (getgr "nogroup"))]) + ;; #o550 user root can read/open the directory + ;; and the group "root" can read/open the directory. + ;; change these two lines to (mkdir-p) ? + (unless (file-exists? "/etc/opensmtpd") + (mkdir "/etc/opensmtpd" #o550)) + + ;; root can read/write/execute on directory dkimsign + ;; group "root" can read and execute + (unless (file-exists? "/etc/opensmtpd/dkimsign") + (mkdir "/etc/opensmtpd/dkimsign" #o750)) + + (copy-file path-to-dkimsign-key etc-dkimsign-key-file) + ;; ;; ;; make the etc-dkimsign-key-file to owned by nobody and group nogroup. + (chown "/etc/opensmtpd" UID-nobody GID-root) + (chown "/etc/opensmtpd/dkimsign" UID-nobody GID-root) + (chown etc-dkimsign-key-file UID-nobody GID-nogroup) + etc-dkimsign-key-file)) + +(define %smtpd.conf + (mixed-text-file "smtpd.conf" + " +# This is the smtpd server system-wide configuration file. +# See smtpd.conf(5) for more information. +# borrowed from the archlinux guix +# https://wiki.archlinux.org/index.php/OpenSMTPD#Simple_OpenSMTPD/mbox_configuration + +# My TLS certificate and key +table aliases file:/etc/aliases +pki smtp.gnucode.me cert \"/etc/letsencrypt/live/gnucode.me/fullchain.pem\" +pki smtp.gnucode.me key \"/etc/letsencrypt/live/gnucode.me/privkey.pem\" + +# for now I am NOT using the virtual credentials +# table creds { joshua = $6$Ec4m8FgKjT2F/03Y$k66ABdse9TzCX6qaALB3WBL9GC1rmAWJmaoSjFMpbhzat7DOpFqpnOwpbZ34wwsQYIK8RQlqwM1I/v6vsRq86. } +table creds \"" creds "\" +table vdoms \"" vdoms "\" +# table vdoms { gnucode.me, gnu-hurd.com } +# table vusers { joshua@HIDDEN = joshua, jbranso@HIDDEN = joshua, postmaster@HIDDEN = joshua } +table vusers \"" vusers "\" + +# this totally works! run this as user nobody! +# info about dkimsign ...ing +# https://openports.pl/path/mail/opensmtpd-filters/dkimsign +# sudo -u nobody /gnu/store/g17vdv4l03bacn7qbdpb5v8l8vgdxcld-opensmtpd-filter-dkimsign-0.5/libexec/opensmtpd/filter-dkimsign -d gnucode.me -s 2020 -c relaxed/relaxed -k etc-dkimsign-key-file /home/joshua/linode-guix-system-configuration/email-dkim-ssh-keys/20201004-gnucode.me.key user nobody group nogroup + +filter \"dkimsign\" \ + proc-exec \"" path-to-filter-dkimsign " -d gnucode.me -s 2021-09-22 -c relaxed/relaxed -k " etc-dkimsign-key-file ;;(create-etc-dkimsign-key-file) + " \" \ + user nobody group nogroup + +# port 25 is used only for receiving from external servers, and they may start a +# TLS session if the want. +listen on eth0 port 25 tls pki smtp.gnucode.me + +# For sending messages from outside of this server, you need to authenticate and use +# TLS +listen on eth0 port 465 smtps pki smtp.gnucode.me auth <creds> filter \"dkimsign\" +listen on eth0 port 587 tls-require pki smtp.gnucode.me auth <creds> filter \"dkimsign\" + +# users logged-in/ssh-ed into the system can send email +listen on lo port 25 tls pki smtp.gnucode.me + +# receive email action +action \"receive\" maildir \"/home/%{rcpt.user}/Maildir\" junk virtual <vusers> +# action send the email to the world +action \"send\" relay + +# We accept to send email from any mail from authenticated users +match for any from any auth action \"send\" + +#finally we receive any incoming email +# maybe the next \"from any\" should be changed to \"for rdns\". +match from any for domain <vdoms> action \"receive\" +match for local action \"receive\"")) +(define-module (opensmtpd) + #:use-module (guix gexp) + #:use-module (guix records) + #:use-module (gnu packages mail) ;;for finding location of filter-dkimsign + #:export ( + %smtpd.conf + )) + + +;; to create credentials for now, I need to do the following: +;; find /gnu/store -name '*encrypt*' | grep opensmtpd +;; /gnu/store/blah/opensmtpd/encrypt +(define creds + (plain-file "creds" + ;; this is my joshua's password for server. This can be found on dobby's /home/joshua/.authinfo/ + "joshua $6$Ec4m8FgKjT2F/03Y$k66ABdse9TzCX6qaALB3WBL9GC1rmAWJmaoSjFMpbhzat7DOpFqpnOwpbZ34wwsQYIK8RQlqwM1I/v6vsRq86.")) + +(define vdoms + (plain-file + "vdoms" + "gnucode.me +gnu-hurd.com")) + +(define vusers + (plain-file + "vusers" + "joshua@HIDDEN joshua +jbranso@HIDDEN joshua +postmaster@HIDDEN joshua")) + +(define path-to-filter-dkimsign + (file-append opensmtpd-filter-dkimsign "/libexec/opensmtpd/filter-dkimsign")) + +(define path-to-dkimsign-key (string-append (getcwd) "/email-dkim-ssh-keys/2021-09-22-rsa1024-gnucode.me.key")) +(define etc-dkimsign-key-file "/etc/opensmtpd/dkimsign/2021-09-22-rsa1024-gnucode.me.key") + +;; FIXME: This should become a derivation. Currently it just runs when I evaluate +;; %smtpd.conf. For example it should look like this? +;; (define build-exp +;; #~(begin +;; (mkdir #$output) +;; (chdir #$output) +;; (symlink (string-append #$coreutils "/bin/ls") +;; "list-files"))) + +;; I will need to extend the opensmtpd service, to create a directory +;; in etc. This line needs to be added to etc-service. +;; (service-extension etc-service-type opensmtpd-etc-service) +;; I'll then need to create a opensmtpd-etc-service procedure. ganeti has +;; a good example. + +;; It should also use the /etc service, which is a service for creating +;; directories and files in /etc ? +(define (create-etc-dkimsign-key-file) + #~(let ([UID-nobody (passwd:uid (getpw "nobody"))] + [GID-root (group:gid (getgr "root"))] + [GID-nogroup (group:gid (getgr "nogroup"))]) + ;; #o550 user root can read/open the directory + ;; and the group "root" can read/open the directory. + ;; change these two lines to (mkdir-p) ? + (unless (file-exists? "/etc/opensmtpd") + (mkdir "/etc/opensmtpd" #o550)) + + ;; root can read/write/execute on directory dkimsign + ;; group "root" can read and execute + (unless (file-exists? "/etc/opensmtpd/dkimsign") + (mkdir "/etc/opensmtpd/dkimsign" #o750)) + + (copy-file path-to-dkimsign-key etc-dkimsign-key-file) + ;; ;; ;; make the etc-dkimsign-key-file to owned by nobody and group nogroup. + (chown "/etc/opensmtpd" UID-nobody GID-root) + (chown "/etc/opensmtpd/dkimsign" UID-nobody GID-root) + (chown etc-dkimsign-key-file UID-nobody GID-nogroup) + etc-dkimsign-key-file)) + +(define %smtpd.conf + (mixed-text-file "smtpd.conf" + " +# This is the smtpd server system-wide configuration file. +# See smtpd.conf(5) for more information. +# borrowed from the archlinux guix +# https://wiki.archlinux.org/index.php/OpenSMTPD#Simple_OpenSMTPD/mbox_configuration + +# My TLS certificate and key +table aliases file:/etc/aliases +pki smtp.gnucode.me cert \"/etc/letsencrypt/live/gnucode.me/fullchain.pem\" +pki smtp.gnucode.me key \"/etc/letsencrypt/live/gnucode.me/privkey.pem\" + +# for now I am NOT using the virtual credentials +# table creds { joshua = $6$Ec4m8FgKjT2F/03Y$k66ABdse9TzCX6qaALB3WBL9GC1rmAWJmaoSjFMpbhzat7DOpFqpnOwpbZ34wwsQYIK8RQlqwM1I/v6vsRq86. } +table creds \"" creds "\" +table vdoms \"" vdoms "\" +# table vdoms { gnucode.me, gnu-hurd.com } +# table vusers { joshua@HIDDEN = joshua, jbranso@HIDDEN = joshua, postmaster@HIDDEN = joshua } +table vusers \"" vusers "\" + +# this totally works! run this as user nobody! +# info about dkimsign ...ing +# https://openports.pl/path/mail/opensmtpd-filters/dkimsign +# sudo -u nobody /gnu/store/g17vdv4l03bacn7qbdpb5v8l8vgdxcld-opensmtpd-filter-dkimsign-0.5/libexec/opensmtpd/filter-dkimsign -d gnucode.me -s 2020 -c relaxed/relaxed -k etc-dkimsign-key-file /home/joshua/linode-guix-system-configuration/email-dkim-ssh-keys/20201004-gnucode.me.key user nobody group nogroup + +filter \"dkimsign\" \ + proc-exec \"" path-to-filter-dkimsign " -d gnucode.me -s 2021-09-22 -c relaxed/relaxed -k " etc-dkimsign-key-file ;;(create-etc-dkimsign-key-file) + " \" \ + user nobody group nogroup + +# port 25 is used only for receiving from external servers, and they may start a +# TLS session if the want. +listen on eth0 port 25 tls pki smtp.gnucode.me + +# For sending messages from outside of this server, you need to authenticate and use +# TLS +listen on eth0 port 465 smtps pki smtp.gnucode.me auth <creds> filter \"dkimsign\" +listen on eth0 port 587 tls-require pki smtp.gnucode.me auth <creds> filter \"dkimsign\" + +# users logged-in/ssh-ed into the system can send email +listen on lo port 25 tls pki smtp.gnucode.me + +# receive email action +action \"receive\" maildir \"/home/%{rcpt.user}/Maildir\" junk virtual <vusers> +# action send the email to the world +action \"send\" relay + +# We accept to send email from any mail from authenticated users +match for any from any auth action \"send\" + +#finally we receive any incoming email +# maybe the next \"from any\" should be changed to \"for rdns\". +match from any for domain <vdoms> action \"receive\" +match for local action \"receive\"")) + +#+END_SRC +**** TODO what does rewrite needs value mean? Should it be a number? this is for =<opensmtpd-filter-phase-configuration>= +from the documentation + +rewrite value the command parameter is rewritten with value +**** DONE sanitize the <opensmtpd-listen-on-socket-configuration> fieldname 'filters'. + +I can probably reuse existing code from the sanitize procedure found in +=<opensmtpd-listen-on-configuration>= fieldname 'filters'. +**** DONE write a get-opensmtpd-filters procedure + +This procedure takes all the values of <opensmtpd-listen-on-configuration> fieldname 'filters' +and <opensmtpd-listen-on-socket-configuration> fieldname 'filters'. It returns a list of +<opensmtpd-filter-configuration>, <opensmtpd-filter-phase-configuration>, and filter-chains, which is a list +of <opensmtpd-filter-configuration> and <opensmtpd-filter-phase-configuration>. An example of what this +might return is: + +#+BEGIN_SRC scheme +(list (list (opensmtpd-filter)) + (list (opensmtpd-filter-phase-configuration)) + (list (opensmtpd-filter) ; this list is a filter-chain + (opensmtpd-filter-phase-configuration)) + (list (opensmtpd-filter-phase-configuration) ; this list is also a filter chain + (opensmtpd-filter) + (opensmtpd-filter))) +#+END_SRC + +These are some example bits of code that I can test my resulting code on. + +All unique filters of <opensmtpd-filter-configuration> and <opensmtpd-filter-phase-configuration>. 4 of +them. One listen-on has no filters. +#+BEGIN_SRC scheme +(let ([interface "lo"] + [filter-dkimsign (opensmtpd-filter + (name "dkimsign") + (exec #t) + (proc (string-append "/path/to/dkimsign -d gnucode.me -s 2021-09-22 -c relaxed/relaxed -k " + "/path/to/dkimsign-key user nobody group nobody")))]) + (opensmtpd-configuration + (listen-on-socket + (opensmtpd-listen-on-socket-configuration-configuration + (filters (list (opensmtpd-filter + (name "rspamd") + (proc "rspamd")))))) + (listen-ons + (list + (opensmtpd-listen-on-configuration + (interface interface) + (filters (list (opensmtpd-filter-phase-configuration + (name "noFRDNS") + (phase "commit") + (options (list (opensmtpd-option-configuration + (option "fcrdns") + (not #t)))) + (decision "disconnect") + (message "No FCRDNS"))))) + ;; this lets local users logged into the system via ssh send email + (opensmtpd-listen-on + (interface interface) + (port 27) + (filters (list filter-dkimsign))) + (opensmtpd-listen-on-configuration + (port 29) + (interface interface)) + (opensmtpd-listen-on-configuration + (interface interface) + (filters (list (opensmtpd-filter + (name "rspamd") + (proc "rspamd") + (exec #t))))))))) +#+END_SRC + +4 unique filters. One of the filters is a filter chain. +#+BEGIN_SRC scheme +(let ([interface "lo"] + [filter-dkimsign (opensmtpd-filter + (name "dkimsign") + (exec #t) + (proc (string-append "/path/to/dkimsign -d gnucode.me -s 2021-09-22 -c relaxed/relaxed -k " + "/path/to/dkimsign-key user nobody group nobody")))]) + (opensmtpd-configuration + (listen-on-socket + (opensmtpd-listen-on-socket-configuration-configuration + (filters (list (opensmtpd-filter + (name "spamassassain") + (proc "spamassassain")) + (opensmtpd-filter-phase-configuration + (name "rdns") + (phase "data") + (options (list + (opensmtpd-option-configuration + (option "rdns") + (not #t)))) + (decision "reject") + (message "No RDNS")) + (opensmtpd-filter + (name "block") + (proc "block")) + (opensmtpd-filter-phase-configuration + (name "auth") + (phase "commit") + (options (list + (opensmtpd-option-configuration + (option "auth") + (regex #t) + (not #t) + (table (opensmtpd-table-configuration + (name "auth-table") + (data (list ".*@gmail.com" + ".*@dismail.de"))))))) + (decision "junk")))))) + (listen-ons + (list + (opensmtpd-listen-on-configuration + (interface interface) + (filters (list (opensmtpd-filter-phase-configuration + (name "noFRDNS") + (phase "commit") + (options (list (opensmtpd-option-configuration + (option "fcrdns") + (not #t)))) + (decision "disconnect") + (message "No FCRDNS"))))) + ;; this lets local users logged into the system via ssh send email + (opensmtpd-listen-on-configuration + (interface interface) + (port 27) + (filters (list filter-dkimsign))) + (opensmtpd-listen-on-configuration + (port 29) + (interface interface)) + (opensmtpd-listen-on-configuration + (interface interface) + (filters (list (opensmtpd-filter + (name "rspamd") + (proc "rspamd") + (exec #t))))))))) +#+END_SRC + +No filters at all. +#+BEGIN_SRC scheme +(get-opensmtpd-filters (opensmtpd-configuration)) ; no filters at all. +#+END_SRC + +This one prints rspamd twice! The get-opensmtpd-filters procedure returns a +duplicate filter. While get-opensmtpd-filters does return a duplicate filter. +#+BEGIN_SRC scheme +(opensmtpd-configuration + (listen-on-socket + (opensmtpd-listen-on-configuration-socket-configuration + (filters + (list (opensmtpd-filter + (name "rando") + (proc "rando")))))) + (listen-ons + (list + (opensmtpd-listen-on-configuration + (port 25) + (filters (list (opensmtpd-filter-phase-configuration + (name "noFRDNS") + (phase "commit") + (options (list (opensmtpd-option-configuration + (option "fcrdns") + (not #t)))) + (decision "disconnect") + (message "No FCRDNS")) + (opensmtpd-filter + (name "rspamd") + (proc "rspamd"))))) + (opensmtpd-listen-on-configuration + (port 465) + (filters (list + (opensmtpd-filter + (name "rspamd") + (proc "rspamd")) + (opensmtpd-filter + (name "block") + (proc "block"))))) + (opensmtpd-listen-on-configuration + (port 587)) + (opensmtpd-listen-on-configuration + (port 999) + (filters (list + (opensmtpd-filter + (name "bogofilter") + (proc "bogofilter")))))))) +#+END_SRC + +#+BEGIN_SRC scheme +(opensmtpd-configuration + (listen-ons + (list + (opensmtpd-listen-on-configuration + (filters (list (opensmtpd-filter + (name "bogofilter") + (proc "bogofilter"))))) + (opensmtpd-listen-on-configuration + (filters (list (opensmtpd-filter + (name "noFRDNS") + (proc "noFRDNS")) + (opensmtpd-filter + (name "rspamd") + (proc "rspamd")))))))) +#+END_SRC +**** PROJ by which method should I turn the filters in =<opensmtpd-configuration>= into strings? [1/3] + +in (opensmtpd-configuration->mixed-text-file ) do either + +1. one line of code. faster, but violates the convention set by the other lines + of code around it. + #+BEGIN_SRC scheme + ;; write out all the filters + (opensmtpd-filters->string (get-opensmtpd-filters record)) + #+END_SRC + + This task in done: [[*(opensmtpd-filters->string (get-opensmtpd-filters record))][(opensmtpd-filters->string (get-opensmtpd-filters record))]] +2. two lines of code. slower, but follows the convention set by the other lines + of code around it. + #+BEGIN_SRC scheme + (opensmtpd-configuration-fieldname->string record get-opensmtpd-filters-and-filter-phases opensmtpd-filter-and-filter-phase->string) + (opensmtpd-configuration-fieldname->string record get-opensmtpd-filter-chains opensmtpd-filter-chain->string) + #+END_SRC + +***** DONE (opensmtpd-filters->string (get-opensmtpd-filters record)) + +Have one procedure that prints out all filters. + + #+BEGIN_SRC scheme +;; write out all the filters +(opensmtpd-filters->string (get-opensmtpd-filters record)) + #+END_SRC + +***** PROJ 4 procedures: get-filter-and-filter-phases, filter-and-filter-phases->string, get-filter-chains, filter-chains->string + +The bonus with this method is that I can add these two lines in +opensmtpd-configuration->mixed-text-file and keep a consistent coding framework: + +#+BEGIN_SRC scheme +;; write out all the filters and filter-phases +(opensmtpd-configuration-fieldname->string record get-opensmtpd-filters-and-filter-phases opensmtpd-filter-and-filter-phase->string) +;; write out all the filter chains +(opensmtpd-configuration-fieldname->string record get-opensmtpd-filter-chains opensmtpd-filter-chain->string) +#+END_SRC +**** DONE fix the sanitize procedure for =<opensmtpd-filter-phase-configuration>= fieldnames 'phase-name', 'decision', etc. [5/5] +***** DONE sanitize =<opensmtpd-filter-phase-configuration>= so that fieldname 'decision' option "reject" and "disconnect" requires a 'message'. +:LOGBOOK: +CLOCK: [2022-04-01 Fri 22:45]--[2022-04-02 Sat 04:13] => 5:28 +:END: + +This message must be RFC compliant. The message must start with 4xx or 5xx +status code. + +#+BEGIN_SRC scheme +(opensmtpd-listen-on-configuration + (filters (list + (opensmtpd-filter-phase-configuration + (name "junk") + (phase "connect") + (decision "junk") + (options + (list + (opensmtpd-option-configuration + (option "rdns"))))) + (opensmtpd-filter-phase-configuration + (name "src") + (phase "connect") + (options + (list + (opensmtpd-option-configuration + (option "src") + (data (opensmtpd-table-configuration + (name "src-table") + (data (list "cat" "hat"))))))) + (decision "reject"))))) +#+END_SRC + +#+RESULTS: + +#+BEGIN_EXAMPLE +<opensmtpd-filter-phase-configuration> fieldname: 'decision' options "disconnect" and "reject" require fieldname 'message' +to have a string. +ice-9/boot-9.scm:1685:16: In procedure raise-exception: +Throw to key `bad!' with args `(#<<opensmtpd-filter-phase-configuration> name: "src" phase: "connect" options: (#<<opensmtpd-option-configuration> option: "src" not: #f regex: #f table: #<<opensmtpd-table-configuration> name: "src-table" file-db: #f values: ("cat" "hat") type: #<procedure 435f560 at <unknown port>:5894:22 (x)>>>) decision: "reject" message: #f value: #f>)'. + +Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue. +#+END_EXAMPLE + +#+BEGIN_SRC scheme +(opensmtpd-listen-on-configuration + (filters (list + (opensmtpd-filter-phase-configuration + (name "junk") + (phase "connect") + (decision "junk") + (options + (list + (opensmtpd-option-configuration + (option "rdns"))))) + (opensmtpd-filter-phase-configuration + (name "src") + (phase "connect") + (options + (list + (opensmtpd-option-configuration + (option "src") + (data (opensmtpd-table-configuration + (name "src-table") + (data (list "cat" "hat"))))))) + (decision "reject") + (message "322 Hello"))))) +<opensmtpd-filter-phase-configuration> fieldname: 'decision' options +"disconnect" and "reject" require fieldname 'message' to have a string. +The 'message' string must be RFC commpliant, which means that the string +must begin with a 4xx or 5xx status code. +ice-9/boot-9.scm:1685:16: In procedure raise-exception: +Throw to key `bad!' with args `(#<<opensmtpd-filter-phase-configuration> name: "src" phase: "connect" options: (#<<opensmtpd-option-configuration> option: "src" not: #f regex: #f data: #<<opensmtpd-table-configuration> name: "src-table" file-db: #f data: ("cat" "hat") type: #<procedure 44d2140 at <unknown port>:1153:21 (x)>>>) decision: "reject" message: "322 Hello" value: #f>)'. + +Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue. +#+END_SRC +***** DONE sanitize =<opensmtpd-filter-phase-configuration>= so that fieldname 'decision' option "rewrite" requires a 'value'. +:LOGBOOK: +CLOCK: [2022-04-01 Fri 22:45]--[2022-04-02 Sat 04:13] => 5:28 +:END: + +#+BEGIN_SRC scheme +(opensmtpd-listen-on-configuration + (filters + (list + (opensmtpd-filter-phase-configuration + (name "noFRDNS") + (phase "commit") + (options (list (opensmtpd-option-configuration + (option "fcrdns") + (not #t)))) + (value #f) + (decision "rewrite")) ;; there needs to be a value here. rewrite requires a value! + ))) +$12 = #<<opensmtpd-listen-on-configuration> interface: "lo" family: #f auth: #f auth-optional: #f filters: (#<<opensmtpd-filter-phase-configuration> name: "noFRDNS" phase: "commit" options: (#<<opensmtpd-option-configuration> option: "fcrdns" not: #t regex: #f data: #f>) decision: "rewrite" message: #f value: 343>) hostname: #f hostnames: #f mask-src: #f disable-dsn: #f pki: #f port: #f proxy-v2: #f received-auth: #f secure-connection: #f tag: #f> +scheme@(opensmtpd-records) [10]> (opensmtpd-listen-on-configuration + (filters + (list + (opensmtpd-filter-phase-configuration + (name "noFRDNS") + (phase "commit") + (options (list (opensmtpd-option-configuration + (option "fcrdns") + (not #t)))) + (decision "rewrite")) ;; there needs to be a value here. rewrite requires a value! + ))) +<opensmtpd-filter-phase-configuration> fieldname: 'decision' option +"rewrite" requires fieldname 'value' +to have a number. +ice-9/boot-9.scm:1685:16: In procedure raise-exception: +Throw to key `bad!' with args `(#<<opensmtpd-filter-phase-configuration> name: "noFRDNS" phase: "commit" options: (#<<opensmtpd-option-configuration> option: "fcrdns" not: #t regex: #f data: #f>) decision: "rewrite" message: #f value: #f>)'. + +Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue. +#+END_SRC + +***** DONE sanitize =<opensmtpd-filter-phase-configuration>= so that fieldname 'decision' option "junk" and "bypass" have no message or value + +#+BEGIN_SRC scheme +(opensmtpd-listen-on-configuration + (filters + (list + (opensmtpd-filter-phase-configuration + (name "noFRDNS") + (phase "commit") + (options (list (opensmtpd-option-configuration + (option "fcrdns") + ))) + (decision "junk") + (message "This is not a good email."))))) ; there should NOT be a message here! junk has no message. +#+END_SRC +***** DONE sanitize the options too. rdns requires a table for instance: + +#+BEGIN_EXAMPLE +At each phase, various conditions may be matched. The fcrdns, rdns, and src data are +available in all phases, but other data must have been already submitted before they are +available. + + fcrdns forward-confirmed reverse DNS is valid + rdns session has a reverse DNS + rdns <table> session has a reverse DNS in table + src <table> source address is in table + helo <table> helo name is in table + auth session is authenticated + auth <table> session username is in table + mail-from <table> sender address is in table + rcpt-to <table> recipient address is in table +#+END_EXAMPLE +***** DONE sanitize <opensmtpd-filter-phase-configuration> make sure that 'junking happens before phase 'committed'. +#+BEGIN_EXAMPLE + Descisions can be taken at any phase, though junking can only happen before a message is committed. +#+END_EXAMPLE + +#+BEGIN_SRC scheme +(opensmtpd-listen-on-configuration + (filters + (list + (opensmtpd-filter-phase-configuration + (name "junk-after-commit") + (phase "commit") + (options (list (opensmtpd-option-configuration + (option "fcrdns") + ))) + (decision "junk"))))) +#+END_SRC +*** PROJ make fieldnames that need a table accept a value of table [3/4] +**** DONE opensmtpd-action-local-delivery-configuration [3/3] +***** DONE alias =<alias>= [2/2] +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 03:53] +:END: +****** DONE change the sanitize portion of the fieldname alias in the <opensmtpd-action-local-delivery-configuration> + +The below code does work in a REPL. +#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/ +(add-to-load-path (dirname (current-filename))) +(use-modules (opensmtpd-records)) + +(opensmtpd-action-local-delivery-configuration + (alias + (opensmtpd-table-configuration + (name "My-table") + (data (list "gnu-hurd.com" "gnucode.me"))))) +#+END_SRC + +#+RESULTS: +****** DONE change relevant portions in opensmtpd-action-local-delivery-configuration->string +This bit of code works in the repl too! +#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/ +(add-to-load-path (dirname (current-filename))) +(use-modules (opensmtpd-records)) + +((@@ (opensmtpd-records) opensmtpd-action-local-delivery-configuration->string) + (opensmtpd-action-local-delivery-configuration + (alias + (opensmtpd-table-configuration + (name "My-table") + (data (list "gnu-hurd.com" "gnucode.me")))))) +#+END_SRC + +***** DONE userbase =<users>= [2/2] +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 03:53] +:END: + +****** DONE change the sanitize portion of the fieldname userbase in the <opensmtpd-action-local-delivery-configuration> + +The below code does work in a REPL. +#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/ +(add-to-load-path (dirname (current-filename))) +(use-modules (opensmtpd-records)) +(opensmtpd-action-local-delivery-configuration + (userbase + (opensmtpd-table-configuration + (name "this") + (data (list "job" "done"))))) +#+END_SRC + +#+RESULTS: +****** DONE change relevant portions in opensmtpd-action-local-delivery-configuration->string +This bit of code works in the repl too! +#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/ +(add-to-load-path (dirname (current-filename))) +(use-modules (opensmtpd-records)) + +((@@ (opensmtpd-records) opensmtpd-action-local-delivery-configuration->string) + (opensmtpd-action-local-delivery-configuration + (userbase + (opensmtpd-table-configuration + (name "this") + (data (list "job" "done")))))) +#+END_SRC + +***** DONE virtual [2/2] +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 03:53] +:END: +****** DONE change the sanitize portion of the fieldname virtual in the <opensmtpd-action-local-delivery-configuration> + +The below code does work in a REPL. +#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/ +(add-to-load-path (dirname (current-filename))) +(use-modules (opensmtpd-records)) +(opensmtpd-action-local-delivery-configuration + (virtual + (opensmtpd-table-configuration + (name "this") + (data (list "job" "done"))))) +#+END_SRC + +#+RESULTS: +****** DONE change relevant portions in opensmtpd-action-local-delivery-configuration->string +This bit of code works in the repl too! +#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/ +(add-to-load-path (dirname (current-filename))) +(use-modules (opensmtpd-records)) + +((@@ (opensmtpd-records) opensmtpd-action-local-delivery-configuration->string) + (opensmtpd-action-local-delivery-configuration + (virtual + (opensmtpd-table-configuration + (name "this") + (data (list "job" "done")))))) + +#+END_SRC + +**** DONE opensmtpd-relay-configuration [4/4] +***** DONE helo-src <helos> +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:00] +:END: +***** DONE domain =<domains>= [2/2] +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:00] +:END: + +****** DONE change the sanitize portion of the fieldname domain in the <opensmtpd-action-relay-configuration> + +The below code does work in a REPL. +#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/ +(add-to-load-path (dirname (current-filename))) +(use-modules (opensmtpd-records)) + +(opensmtpd-action-relay-configuration + (domain + (opensmtpd-table-configuration + (name "this") + (data (list "gnucode.me" "gnu-hurd.com"))))) + +#+END_SRC + +#+RESULTS: +****** DONE change relevant portions in opensmtpd-action-relay-configuration->string +This bit of code works in the repl too! +#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/ +(add-to-load-path (dirname (current-filename))) +(use-modules (opensmtpd-records)) + +((@@ (opensmtpd-records) opensmtpd-action-relay-configuration->string) + (opensmtpd-action-relay-configuration + (domain + (opensmtpd-table-configuration + (name "this") + (data (list "gnucode.me" "gnu-hurd.com")))))) + + +#+END_SRC + +***** DONE auth =<credentials>= [2/2] +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:00] +:END: +****** DONE change the sanitize portion of the fieldname =<auth>= in the <opensmtpd-action-relay-configuration> + +The below code does work in a REPL. +#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/ +(add-to-load-path (dirname (current-filename))) +(use-modules (opensmtpd-records)) + +(opensmtpd-action-relay-configuration + (auth + (opensmtpd-table-configuration + (name "this") + (data (list "gnucode.me" "gnu-hurd.com"))))) + +#+END_SRC + +#+RESULTS: +****** DONE change relevant portions in opensmtpd-action-relay-configuration->string +This bit of code works in the repl too! +#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/ +(add-to-load-path (dirname (current-filename))) +(use-modules (opensmtpd-records)) + +((@@ (opensmtpd-records) opensmtpd-action-relay-configuration->string) + (opensmtpd-action-relay-configuration + (auth + (opensmtpd-table-configuration + (name "this") + (data (list "gnucode.me" "gnu-hurd.com")))))) + + +#+END_SRC + +***** DONE src srcaddress | <IP addresses> +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:01] +:END: +****** DONE change the sanitize portion of the fieldname =<auth>= in the <opensmtpd-action-relay-configuration> + +The below code does work in a REPL. +#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/ +(add-to-load-path (dirname (current-filename))) +(use-modules (opensmtpd-records)) + +(opensmtpd-action-relay-configuration + (src + (opensmtpd-table-configuration + (name "this") + (data (list "gnucode.me" "gnu-hurd.com"))))) + +#+END_SRC + +#+RESULTS: +****** DONE change relevant portions in opensmtpd-action-relay-configuration->string +This bit of code works in the repl too! +#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/ +(add-to-load-path (dirname (current-filename))) +(use-modules (opensmtpd-records)) + +((@@ (opensmtpd-records) opensmtpd-action-relay-configuration->string) + (opensmtpd-action-relay-configuration + (src + (opensmtpd-table-configuration + (name "this") + (data (list "gnucode.me" "gnu-hurd.com")))))) + + +#+END_SRC + +Use the string scraddress or list table <IP addresses> for the source IP address. +**** DONE opensmtpd-listen-on-configuration [3/3] +***** DONE auth =<credentials>= [3/3] +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:01] +:END: +****** DONE change the sanitize portion of the fieldname 'auth' in the <opensmtpd-listen-on-configuration> + +The below code does work in a REPL. +#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/ +(add-to-load-path (dirname (current-filename))) +(use-modules (opensmtpd-records)) + +(opensmtpd-listen-on-configuration + (auth + (opensmtpd-table-configuration + (name "My-table") + (data '(("joshua" . "$some$Long$EncrytpedPassword")))))) +#+END_SRC + +#+RESULTS: +****** DONE change relevant portions in opensmtpd-listen-on-configuration->string +This bit of code works in the repl too! +#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/ +(add-to-load-path (dirname (current-filename))) +(use-modules (opensmtpd-records)) + +((@@ (opensmtpd-records) opensmtpd-listen-on-configuration->string) + (opensmtpd-listen-on-configuration + (auth + (opensmtpd-table-configuration + (name "credentials") + (data '(("joshua" . "$someLongEncrytpedPassword"))))))) +#+END_SRC + +****** DONE sanitize the =<opensmtpd-table-configuration>= so that it can only be an opensmtpd-table-configuration, whose fieldname values are an assoc-list + +#+BEGIN_SRC scheme +(opensmtpd-listen-on-configuration (auth (opensmtpd-table-configuration (name "the") (data (list "the" "cat"))))) +#+END_SRC + +#+RESULTS: =<opensmtpd-listen-on-configuration>= fieldname: 'auth' is of type boolean, or an =<opensmtpd-table-configuration>= record whose fieldname 'values' are an assoc-list. + +***** DONE auth-optional =<credentials>= [2/2] +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:01] +:END: +****** DONE change the sanitize portion of the fieldname 'auth-optional' in the <opensmtpd-listen-on-configuration> + +The below code does work in a REPL. +#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/ +(add-to-load-path (dirname (current-filename))) +(use-modules (opensmtpd-records)) + +(opensmtpd-listen-on-configuration + (auth + (opensmtpd-table-configuration + (name "My-table") + (data '(("joshua" . "$some$Long$EncrytpedPassword")))))) +#+END_SRC + +#+RESULTS: + +AND the below code will correctly result in an error! +#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/ +(add-to-load-path (dirname (current-filename))) +(use-modules (opensmtpd-records)) + +(opensmtpd-listen-on-configuration + (auth + (opensmtpd-table-configuration + (name "My-table") + (data '("joshua" "$some$Long$EncrytpedPassword"))))) +#+END_SRC + +#+RESULTS: +: =<opensmtpd-listen-on-configuration>= fieldname: 'auth' is of type boolean, or an =<opensmtpd-table-configuration>= record whose fieldname 'values' are an assoc-list +: (eg: (opensmtpd-table-configuration (name "table") (data '("joshua" . "$encrypted$password")))). + +****** DONE change relevant portions in opensmtpd-listen-on-configuration->string +This bit of code works in the repl too! +#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/ +(add-to-load-path (dirname (current-filename))) +(use-modules (opensmtpd-records)) + +((@@ (opensmtpd-records) opensmtpd-listen-on-configuration->string) + (opensmtpd-listen-on-configuration + (auth + (opensmtpd-table-configuration + (name "credentials") + (data '(("joshua" . "$someLongEncrytpedPassword"))))))) +#+END_SRC + +***** DONE hostnames =<hostnames>= [2/2] +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:01] +:END: +****** DONE change the sanitize portion of the fieldname 'hostnames' in the <opensmtpd-listen-on-configuration> + +The below code does work in a REPL. +#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/ +(add-to-load-path (dirname (current-filename))) +(use-modules (opensmtpd-records)) + +(opensmtpd-listen-on-configuration + (hostnames + (opensmtpd-table-configuration + (name "My-table") + (data '(("joshua" . "$some$Long$EncrytpedPassword")))))) +#+END_SRC + +#+RESULTS: + +AND the below code will correctly result in an error! +#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/ +(add-to-load-path (dirname (current-filename))) +(use-modules (opensmtpd-records)) + +(opensmtpd-listen-on-configuration + (hostnames + (opensmtpd-table-configuration + (name "My-table") + (data '("joshua" "$some$Long$EncrytpedPassword"))))) +#+END_SRC + +#+RESULTS: +: =<opensmtpd-listen-on-configuration>= fieldname: 'hostname' is of type boolean, or an =<opensmtpd-table-configuration>= record whose fieldname 'values' are an assoc-list +: (eg: (opensmtpd-table-configuration (name "table") (data '("joshua" . "$encrypted$password")))). + +****** DONE change relevant portions in opensmtpd-listen-on-configuration->string +This bit of code works in the repl too! +#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/ +(add-to-load-path (dirname (current-filename))) +(use-modules (opensmtpd-records)) + +((@@ (opensmtpd-records) opensmtpd-listen-on-configuration->string) + (opensmtpd-listen-on-configuration + (hostnames + (opensmtpd-table-configuration + (name "credentials") + (data '(("joshua" . "$someLongEncrytpedPassword"))))))) +#+END_SRC + +**** TODO opensmtpd-match [20/24] +******* NO list approach +Guix probably won't like the list approach. +#+BEGIN_SRC scheme + (openmstpd-match + (for + (list 'not "for domain regex" + (opensmtpd-table-configuration + (data (list "gnucode.me" "gnu-hurd.com")))))) +#+END_SRC + +#+BEGIN_SRC scheme + (openmstpd-match + (for + (list "! for domain" + (opensmtpd-table-configuration + (data (list "gnucode.me" "gnu-hurd.com")))))) +#+END_SRC +******* opensmtpd-options-configuration approach I like this one quite a bit. + +This method is a little bit more verbose. Well I guess it's a lot +more verbose. But it's easier for me to properly parse what the user wants. + +I would sanitize the options in the opensmtpd-match-configuration-for, +openmsmtpd-match-from, opensmtpd-match-configuration-auth, opensmtpd-match-configuration-helo, +opensmtpd-match-configuration-mail-from, opensmtpd-match-configuration-rcpt-to fieldnames. +********* for +#+BEGIN_SRC scheme + (openmstpd-match + (for + (opensmtpd-options-configuration + (not #t) + (method "domain regex") ;; valid options for "for" are "domain" or "domain regex" + (opensmtpd-table-configuration + (data (list "gnucode.me" "gnu-hurd.com")))))) +#+END_SRC + +Do I want a regex fieldname? Probably not. It makes it more verbose... + +#+BEGIN_SRC scheme + (openmstpd-match + (for + (opensmtpd-options-configuration + (not #t) + (regex #t) + (method "domain") ;; valid options for "for" are "domain" + (opensmtpd-table-configuration + (data (list "gnucode.me" "gnu-hurd.com")))))) +#+END_SRC + +********* from +#+BEGIN_SRC scheme + (openmstpd-match + (from + (opensmtpd-options-configuration + (not #t) + (method "rdns regex") ;;valid options for from are "auth" "auth regex", "mail-from" "mail-from regex", + ;; "rdns", "rdns regex", "src", "src regex" + (opensmtpd-table-configuration + (data (list "gnucode.me" "gnu-hurd.com")))))) +#+END_SRC + +Do I want a regex fieldname? + +#+BEGIN_SRC scheme + (openmstpd-match + (from + (opensmtpd-options-configuration + (not #t) + (regex #t) + (method "rdns") ;;valid options for from are "auth", "mail-from", "rdns", "src" + (opensmtpd-table-configuration + (data (list "gnucode.me" "gnu-hurd.com")))))) +#+END_SRC + +********* auth +#+BEGIN_SRC scheme + (openmstpd-match + (auth + (opensmtpd-options-configuration + (not #t) + (method "auth regex") + (opensmtpd-table-configuration + (data (list "gnucode.me" "gnu-hurd.com")))))) +#+END_SRC + +Do I want a regex fieldname? + +#+BEGIN_SRC scheme + (openmstpd-match + (auth + (opensmtpd-options-configuration + (not #t) + (regex #t) + (method "auth") ;; valid options for auth are "auth" or this method can be left blank. + (opensmtpd-table-configuration + (data (list "gnucode.me" "gnu-hurd.com")))))) +#+END_SRC + +********* mail-from +#+BEGIN_SRC scheme + (openmstpd-match + (mail-from + (opensmtpd-options-configuration + (not #t) + (method "mail from") + (opensmtpd-table-configuration + (data (list "gnucode.me" "gnu-hurd.com")))))) +#+END_SRC +****** I tweak opensmtpd-match record and add a opensmtpd-options + +#+BEGIN_SRC scheme + (opensmtpd-match-configuration + (name "action-name") + (options + (list + (opensmtpd-options-configuration + (method "for domain regex"))) + )) +#+END_SRC + +****** PROJ many of these options are not completely sanitized. + +For example: "for domain" requires a domain | <domain> BUT this record, which +does not have a domain gives no errors: + +#+BEGIN_SRC scheme +(opensmtpd-match-configuration + (for (opensmtpd-option-configuration + (option "for domain"))) + (action (opensmtpd-action-local-delivery-configuration + (name "local") ))) +#+END_SRC + +And there are a ton of other examples of this. +****** DONE for domain <domains> +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:17] +:END: + +The datastructures work: + +#+BEGIN_SRC scheme +(opensmtpd-match-configuration + (name "local") + (for (opensmtpd-option-configuration + (option "for domain") + (value (opensmtpd-table-configuration + (name "this") + (data (list "helo" "hello")))))) + (action (opensmtpd-action-local-delivery-configuration))) +#+END_SRC + +#Results +: $4 = #<<opensmtpd-match-configuration> name: "local" action: #<<opensmtpd-action-local-delivery-configuration> method: "mbox" alias: #f ttl: #f user: #f userbase: #f virtual: #f wrapper: #f> for: #<<opensmtpd-option-configuration> option: "for domain" not: #f regex: #f value: #<<opensmtpd-table-configuration> name: "this" file-db: #f values: ("helo" "hello") type: #<procedure 2b33238 at <unknown port>:148:97 (x)>>> from: #f auth: #f helo: #f mail-from: #f rcpt-to: #f tag: #f tls: #f> + +****** DONE for domain regexp <domains> +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:19] +:END: + +the datastructure works +#+BEGIN_SRC scheme +(opensmtpd-match-configuration + (name "local") + (for (opensmtpd-option-configuration + (regex #t) + (option "for domain") + (value (opensmtpd-table-configuration + (name "this") + (data (list "helo" "hello")))))) + (action (opensmtpd-action-local-delivery-configuration))) +#+END_SRC + +#Results +: $4 = #<<opensmtpd-match-configuration> name: "local" action: #<<opensmtpd-action-local-delivery-configuration> method: "mbox" alias: #f ttl: #f user: #f userbase: #f virtual: #f wrapper: #f> for: #<<opensmtpd-option-configuration> option: "for domain" not: #f regex: #f value: #<<opensmtpd-table-configuration> name: "this" file-db: #f values: ("helo" "hello") type: #<procedure 2b33238 at <unknown port>:148:97 (x)>>> from: #f auth: #f helo: #f mail-from: #f rcpt-to: #f tag: #f tls: #f> + +****** DONE make opensmtpd-match-configuration->string work print for rcpt the appropriate match lines if some values now accept <opensmtpd-table-configuration> + +Seems to work: + +#+BEGIN_SRC scheme +(opensmtpd-match-configuration->string (opensmtpd-match-configuration + (name "local") + (for (opensmtpd-option-configuration + (regex #t) + (option "for domain") + (value (opensmtpd-table-configuration + (name "this") + (data (list "helo" "hello")))))) + (action (opensmtpd-action-local-delivery-configuration)))) +$6 = "match for domain regex =<this>= action \"local\" \n" +#+END_SRC + +also seems to work + +#+BEGIN_SRC scheme +(opensmtpd-match-configuration->string (opensmtpd-match-configuration + (name "local") + (for (opensmtpd-option-configuration + (option "for domain") + (value (opensmtpd-table-configuration + (name "this") + (data (list "helo" "hello")))))) + (action (opensmtpd-action-local-delivery-configuration)))) +$7 = "match for domain =<this>= action \"local\" \n" +#+END_SRC +****** DONE for rcpt <receipt> +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:19] +:END: +****** DONE for rcpt regexp <recepit> +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:19] +:END: +****** DONE from auth user | <user> +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:19] +:END: + +#+BEGIN_SRC scheme +(opensmtpd-option-configuration + (regex #t) + (option "from auth") + (value (opensmtpd-table-configuration + (name "this") + (data (list "helo" "hello"))))) +$8 = #<<opensmtpd-option-configuration> option: "from auth" not: #f regex: #t value: #<<opensmtpd-table-configuration> name: "this" file-db: #f values: ("helo" "hello") type: #<procedure 2bfa848 at <unknown port>:224:14 (x)>>> + +#+END_SRC +****** DONE from auth regex user | <user> +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:20] +:END: +****** DONE make sure opensmtpd-option-configuration->string works for from auth if they use <opensmtpd-table-configuration> + +#+BEGIN_SRC scheme +(opensmtpd-option-configuration->string + (opensmtpd-option-configuration + (regex #t) + (option "from auth") + (value (opensmtpd-table-configuration + (name "this") + (data (list "helo" "hello")))))) +$10 = "from auth regex =<this>= " +#+END_SRC +****** DONE from mail-from sender | <sender> +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:20] +:END: +****** DONE from mail-from regexp <sender> +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:20] +:END: +****** DONE from rdns <hostname> +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:20] +:END: +****** DONE from rdns regex <hostname> +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:20] +:END: +****** DONE from src <address> +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:21] +:END: +****** DONE from src regex <address> +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:21] +:END: +****** TODO do some more sanitizing of these last couple of options +There may be some way to specify invalid data. For example: + +tls does not support regex, not, or value fields. The below code should be an error. +#+BEGIN_SRC scheme + (display (opensmtpd-match-configuration->string (opensmtpd-match-configuration + (tls (opensmtpd-option-configuration + (option "tls") ;; this should be auth!!! NOT "helo" + (regex #t) + (not #t) + (value (opensmtpd-table-configuration (name "mytable") + (data (list "cat" "kitten")))))) + (from (opensmtpd-option-configuration (option "from rdns") + (value (opensmtpd-table-configuration (name "table") + (data (list "cat" "hat")))))) + (action (opensmtpd-action-local-delivery-configuration + (name "matches")))))) +match from rdns <table> ! tls regex <mytable> action "matches" +#+END_SRC + +****** DONE auth <username> +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:21] +:END: + +Well I need to fix this bug. Basically, I can make an +<opensmtpd-option-configuration> with a table of values for 'option' "auth". And +I can print that table with (opensmtpd-option-configuration->string)...That works +fine... + +But if I put that same record into an =<opensmtpd-match-configuration>= ...for some reason that +'auth' table is not being printed. + +#+BEGIN_SRC scheme +(opensmtpd-option-configuration + (option "auth") + (value (opensmtpd-table-configuration (name "mytable") + (data (list "cat" "hat"))))) +$20 = #<<opensmtpd-option-configuration> option: "auth" not: #f regex: #f value: #<<opensmtpd-table-configuration> name: "mytable" file-db: #f values: ("cat" "hat") type: #<procedure 1ee6ce8 at <unknown port>:860:40 (x)>>> +scheme@(opensmtpd-records) [4]> (opensmtpd-option-configuration->string $20) +$21 = "auth =<mytable>= " +scheme@(opensmtpd-records) [4]> (opensmtpd-match (name "matches") + (auth (opensmtpd-option-configuration + (option "auth") + (value (opensmtpd-table-configuration (name "mytable") + (data (list "cat" "kitten")))))) + (from (opensmtpd-option-configuration (option "from rdns") + (value (opensmtpd-table-configuration (name "table") + (data (list "cat" "hat")))))) + (action (opensmtpd-action-local-delivery-configuration))) +$22 = #<<opensmtpd-match-configuration> name: "matches" action: #<<opensmtpd-action-local-delivery-configuration> method: "mbox" alias: #f ttl: #f user: #f userbase: #f virtual: #f wrapper: #f> for: #f from: #<<opensmtpd-option-configuration> option: "from rdns" not: #f regex: #f value: #<<opensmtpd-table-configuration> name: "table" file-db: #f values: ("cat" "hat") type: #<procedure 30bac20 at <unknown port>:876:89 (x)>>> auth: #<<opensmtpd-option-configuration> option: "auth" not: #f regex: #f value: #<<opensmtpd-table-configuration> name: "mytable" file-db: #f values: ("cat" "kitten") type: #<procedure 30baa80 at <unknown port>:873:63 (x)>>> helo: #f mail-from: #f rcpt-to: #f tag: #f tls: #f> +scheme@(opensmtpd-records) [5]> (opensmtpd-match-configuration->string $22) +$23 = "match from rdns =<table>= auth action \"matches\" \n" ;; THERE IS SUPPOSED TO BE a "auth <mytable>" here +scheme@(opensmtpd-records) [5]> +#+END_SRC +****** TODO [!] auth +****** TODO auth regex <username> +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:21] +:END: + +This does NOT show the regex for the auth option. or the table <mytable> why? +#+BEGIN_SRC scheme +(opensmtpd-match-configuration + (auth (opensmtpd-option-configuration + (option "auth") ;; this should be auth!!! NOT "helo" + (regex #t) + (value (opensmtpd-table-configuration (name "mytable") + (data (list "cat" "kitten")))))) + (from (opensmtpd-option-configuration (option "from rdns") + (value (opensmtpd-table-configuration (name "table") + (data (list "cat" "hat")))))) + (action (opensmtpd-action-local-delivery-configuration + (name "matches")))) +$9 = #<<opensmtpd-match-configuration> action: #<<opensmtpd-action-local-delivery-configuration> name: "matches" method: "mbox" alias: #f ttl: #f user: #f userbase: #f virtual: #f wrapper: #f> for: #f from: #<<opensmtpd-option-configuration> option: "from rdns" not: #f regex: #f value: #<<opensmtpd-table-configuration> name: "table" file-db: #f values: ("cat" "hat") type: #<procedure 1fb5fc0 at <unknown port>:144:52 (x)>>> auth: #<<opensmtpd-option-configuration> option: "auth" not: #f regex: #t value: #<<opensmtpd-table-configuration> name: "mytable" file-db: #f values: ("cat" "kitten") type: #<procedure 1fb5c40 at <unknown port>:141:15 (x)>>> helo: #f mail-from: #f rcpt-to: #f tag: #f tls: #f> +scheme@(opensmtpd-records) [4]> (display (opensmtpd-match-configuration->string $9)) +match from rdns <table> auth action "matches" ;; there should be a regex in there. +#+END_SRC +****** DONE helo <helo-name> +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:21] +:END: +#+BEGIN_SRC scheme +(opensmtpd-match-configuration + (helo (opensmtpd-option-configuration + (option "helo") ;; this should be auth!!! NOT "helo" + (regex #t) + (value (opensmtpd-table-configuration (name "mytable") + (data (list "cat" "kitten")))))) + (from (opensmtpd-option-configuration (option "from rdns") + (value (opensmtpd-table-configuration (name "table") + (data (list "cat" "hat")))))) + (action (opensmtpd-action-local-delivery-configuration + (name "matches")))) +$10 = #<<opensmtpd-match-configuration> action: #<<opensmtpd-action-local-delivery-configuration> name: "matches" method: "mbox" alias: #f ttl: #f user: #f userbase: #f virtual: #f wrapper: #f> for: #f from: #<<opensmtpd-option-configuration> option: "from rdns" not: #f regex: #f value: #<<opensmtpd-table-configuration> name: "table" file-db: #f values: ("cat" "hat") type: #<procedure 24d6840 at <unknown port>:252:52 (x)>>> auth: #f helo: #<<opensmtpd-option-configuration> option: "helo" not: #f regex: #t value: #<<opensmtpd-table-configuration> name: "mytable" file-db: #f values: ("cat" "kitten") type: #<procedure 24d64c0 at <unknown port>:249:15 (x)>>> mail-from: #f rcpt-to: #f tag: #f tls: #f> +scheme@(opensmtpd-records) [5]> (opensmtpd-match-configuration->string $10) +$11 = "match from rdns <table> helo regex <mytable> action \"matches\" \n" +#+END_SRC +****** DONE mail-from <sender> +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:22] +:END: +#+BEGIN_SRC scheme + (display (opensmtpd-match-configuration->string (opensmtpd-match-configuration + (mail-from (opensmtpd-option-configuration + (option "mail-from") ;; this should be auth!!! NOT "helo" + (regex #t) + (not #t) + (value (opensmtpd-table-configuration (name "mytable") + (data (list "cat" "kitten")))))) + (from (opensmtpd-option-configuration (option "from rdns") + (value (opensmtpd-table-configuration (name "table") + (data (list "cat" "hat")))))) + (action (opensmtpd-action-local-delivery-configuration + (name "matches")))))) +match from rdns <table> ! mail-from regex <mytable> action "matches" +#+END_SRC +****** DONE mail-from regex <sender> +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:22] +:END: +#+BEGIN_SRC scheme + (display (opensmtpd-match-configuration->string (opensmtpd-match-configuration + (mail-from (opensmtpd-option-configuration + (option "mail-from") ;; this should be auth!!! NOT "helo" + (regex #t) + (not #t) + (value (opensmtpd-table-configuration (name "mytable") + (data (list "cat" "kitten")))))) + (from (opensmtpd-option-configuration (option "from rdns") + (value (opensmtpd-table-configuration (name "table") + (data (list "cat" "hat")))))) + (action (opensmtpd-action-local-delivery-configuration + (name "matches")))))) +match from rdns <table> ! mail-from regex <mytable> action "matches" +#+END_SRC +****** DONE rcpt-to <receipient> +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:22] +:END: +****** DONE rcpt-to regex <receipient> +:LOGBOOK: +- State "TODO" from "TODO" [2021-11-02 Tue 04:23] +:END: + +*** PROJ sanitize the =<opensmtpd-option-configuration>= records in =<opensmtpd-filter-phase>= & =<opensmtpd-match-configuration>= +**** PROJ testing the sanitize-list-of-options-for-match-configuration-assoc precodure [5/5] +***** DONE make sure each option is unique (no duplicate "for"s). + +#+BEGIN_SRC scheme +(opensmtpd-configuration + (matches (list + (opensmtpd-match-configuration + (options (list + (opensmtpd-option-configuration + (option "for any")) + (opensmtpd-option-configuration + (option "for local")))) + (action + (opensmtpd-action-relay-configuration + (name "relay"))))))) +#+END_SRC +***** DONE make sure there is no duplicate from's +#+BEGIN_SRC scheme +(opensmtpd-configuration + (matches (list + (opensmtpd-match-configuration + (options (list + (opensmtpd-option-configuration + (option "from any")) + (opensmtpd-option-configuration + (option "from auth")))) + (action + (opensmtpd-action-relay-configuration + (name "relay"))))))) +#+END_SRC +***** DONE for any data and regex must be false + +#+BEGIN_SRC scheme +(opensmtpd-configuration + (matches (list + (opensmtpd-match-configuration + (options (list + (opensmtpd-option-configuration + (option "for any") + (regex #t)))) + (action + (opensmtpd-action-relay-configuration + (name "relay"))))))) +#+END_SRC +***** DONE 'rcpt-to' must have data + +#+BEGIN_SRC scheme +(opensmtpd-configuration + (matches (list + (opensmtpd-match-configuration + (options (list + (opensmtpd-option-configuration + (option "rcpt-to")))) + (action + (opensmtpd-action-relay-configuration + (name "relay"))))))) +#+END_SRC +***** DONE 'tls' cannot have a 'data' or 'regex' + +#+BEGIN_SRC scheme +(opensmtpd-configuration + (matches (list + (opensmtpd-match-configuration + (options (list + (opensmtpd-option-configuration + (option "tls") + (data "hello") + ))) + (action + (opensmtpd-action-relay-configuration + (name "relay"))))))) +#+END_SRC +*** TODO sanitize <opensmtpd-configuration> fieldname 'matches' so that no two unique actions have the same name +I definitely should sanitize 'matches' a bit more. For example, you could have two different +actions, one for local delivery and one for remote, with the same name. I +should make sure that all unique actions have unique names. + +Here is an example of two actions that have the same name, but different ttl values: + +#+BEGIN_SRC scheme +(opensmtpd-configuration + (matches + (list (opensmtpd-match-configuration + (action + (opensmtpd-action-local-delivery-configuration + (name "my-local-delivery") + (ttl "50m")))) ; 50 minutes + (opensmtpd-match-configuration + (action + (opensmtpd-action-local-delivery-configuration + (name "my-local-delivery") + (ttl "50h"))))))) ; 50 hours +#+END_SRC + +*** PROJ follow the style guide and style up my project [0/4] +https://mumble.net/~campbell/scheme/style.txt + +:SchemeStyleGuide: +#+BEGIN_SRC org +Riastradh's Lisp Style Rules -*- outline -*- + + Copyright (C) 2007--2011 Taylor R. Campbell + + CC BY-NC-SA 3.0 + + This work is licensed under a Creative Commons + Attribution-NonCommercial-ShareAlike 3.0 Unported License: + <http://creativecommons.org/licenses/by-nc-sa/3.0/>. + +This is a guide to Lisp style, written by Taylor R. Campbell, to +describe the standard rules of Lisp style as well as a set of more +stringent rules for his own style. This guide should be useful for +Lisp in general, but there are [or will be in the final draft] parts +that are focussed on or specific to Scheme or Common Lisp. + +This guide is written primarily as a collection of rules, with +rationale for each rule. (If a rule is missing rationale, please +inform the author!) Although a casual reader might go through and read +the rules without the rationale, perhaps reasoning that reading of the +rationale would require more time than is available, such a reader +would derive little value from this guide. In order to apply the rules +meaningfully, their spirit must be understood; the letter of the rules +serves only to hint at the spirit. The rationale is just as important +as the rules. + +There are many references in this document to `Emacs', `GNU Emacs', +`Edwin', and so on. In this document, `Emacs' means any of a class of +editors related in design to a common ancestor, the EMACS editor macros +written for TECO on ITS on the PDP-10 in the middle of the nineteen +seventies. All such editors -- or `all Emacsen', since `Emacsen' is +the plural of `Emacs' -- have many traits in common, such as a very +consistent set of key bindings, extensibility in Lisp, and so on. `GNU +Emacs' means the member of the class of editors collectively known as +Emacsen that was written for the GNU Project in the middle of the +nineteen eighties, and which is today probably the most popular Emacs. +`Edwin' is MIT Scheme's Emacs, which is bundled as part of MIT Scheme, +and not available separately. There are other Emacsen as well, such as +Hemlock and Climacs, but as the author of this document has little +experience with Emacsen other than GNU Emacs and Edwin, there is little +mention of other Emacsen. + +This guide is a work in progress. To be written: + +- Indentation rules for various special operators. +- Philosophical rambling concerning naming. +- Rules for breaking lines. +- Many more examples. +- A more cohesive explanation of the author's principles for composing + programs, and their implications. +- Rules for writing portable code. +- Some thoughts concerning extensions to the lexical syntax. +- Rules for writing or avoiding macros. +- Some unfinished rationale. +- More on documentation. +- The `Dependencies' subsection of the `General Layout' section should + be put in a different section, the rest of which has yet to be + written, on organization of programs, module systems, and portable + code. + +Feedback is welcome; address any feedback by email to the host +mumble.net's user `campbell', or by IRC to Riastradh in the #scheme +channel on Freenode (irc.freenode.net). Feedback includes reports of +typos, questions, requests for clarification, and responses to the +rationale, except in the case of round brackets versus square +brackets, the argument surrounding which is supremely uninteresting +and now not merely a dead horse but a rotting carcass buzzing with +flies and being picked apart by vultures. + +As this document has grown, the line between standard Lisp rules and +the author's own style has been blurred. The author is considering +merging of the partition, but has not yet decided on this with +certainty. Opinions on the subject are welcome -- is the partition +still useful to keep the author's biases and idiosyncrasies out of the +standard rules, or has the partition with its arbitrary nature only +caused disorganization of the whole document? + +Unfortunately, this document is entirely unscientific. It is at best a +superstition or philosophy, but one that the author of this document +has found to have improved his programs. Furthermore, the author is +somewhat skeptical of claims of scientific analyses of these matters: +analyzing human behaviour, especially confined to the set of excellent +programmers who often have strong opinions about their methods for +building programs, is a very tricky task. + +,* Standard Rules + +These are the standard rules for formatting Lisp code; they are +repeated here for completeness, although they are surely described +elsewhere. These are the rules implemented in Emacs Lisp modes, and +auxiliary utilities such as Paredit. + +The rationale given here is merely the author's own speculation of the +origin of these rules, and should be taken as nothing more than it. +The reader shall, irrespective of the author's rationale, accept the +rules as sent by the reader's favourite deity, or Cthulhu if no such +deity strikes adequate fear into the heart of the reader. + +,** Parentheses + +,*** Terminology + +This guide avoids the term /parenthesis/ except in the general use of +/parentheses/ or /parenthesized/, because the word's generally accepted +definition, outside of the programming language, is a statement whose +meaning is peripheral to the sentence in which it occurs, and *not* the +typographical symbols used to delimit such statements. + +The balanced pair of typographical symbols that mark parentheses in +English text are /round brackets/, i.e. ( and ). There are several +other balanced pairs of typographical symbols, such as /square +brackets/ (commonly called simply `brackets' in programming circles), +i.e. [ and ]; /curly braces/ (sometimes called simply `braces'), i.e. { +and }; /angle brackets/ (sometimes `brokets' (for `broken brackets')), +i.e. < and >. + +In any balanced pair of typographical symbols, the symbol that begins +the region delimited by the symbols is called the /opening bracket/ or +the /left bracket/, such as ( or [ or { or <. The symbol that ends +that region is called the /right bracket/ or the /closing bracket/, +such as > or } or ] or ). + +,*** Spacing + +If any text precedes an opening bracket or follows a closing bracket, +separate that text from that bracket with a space. Conversely, leave +no space after an opening bracket and before following text, or after +preceding text and before a closing bracket. + + Unacceptable: + + (foo(bar baz)quux) + (foo ( bar baz ) quux) + + Acceptable: + + (foo (bar baz) quux) + + Rationale: This is the same spacing found in standard typography of + European text. It is more aesthetically pleasing. + +,*** Line Separation + +Absolutely do *not* place closing brackets on their own lines. + + Unacceptable: + + (define (factorial x) + (if (< x 2) + 1 + (* x (factorial (- x 1 + ) + ) + ) + ) + ) + + Acceptable: + + (define (factorial x) + (if (< x 2) + 1 + (* x (factorial (- x 1))))) + + Rationale: The parentheses grow lonely if their closing brackets are + all kept separated and segregated. + +,**** Exceptions to the Above Rule Concerning Line Separation + +Do not heed this section unless you know what you are doing. Its title +does *not* make the unacceptable example above acceptable. + +When commenting out fragments of expressions with line comments, it may +be necessary to break a line before a sequence of closing brackets: + + (define (foo bar) + (list (frob bar) + (zork bar) + ;; (zap bar) + )) + +This is acceptable, but there are other alternatives. In Common Lisp, +one can use the read-time optional syntax, `#+' or `#-', with a +feature optional that is guaranteed to be false or true -- `#+(OR)' +or `#-(AND)' --; for example, + + (define (foo bar) + (list (frob bar) + (zork bar) + ,#+(or) (zap bar))). + +Read-time optionals are expression-oriented, not line-oriented, so +the closing brackets need not be placed on the following line. Some +Scheme implementations, and SRFI 62, also support expression comments +with `#;', which are operationally equivalent to the above read-time +optionals for Common Lisp: + + (define (foo bar) + (list (frob bar) + (zork bar) + #; + (zap bar))) + +The expression is placed on another line in order to avoid confusing +editors that do not recognize S-expression comments; see the section +titled `Comments' below for more details. However, the `#;' notation +is not standard -- it appears in neither the IEEE 1178 document nor in +the R5RS --, so line comments are preferable for portable Scheme code, +even if they require breaking a line before a sequence of closing +brackets. + +Finally, it is acceptable to break a line immediately after an opening +bracket and immediately before a closing bracket for very long lists, +especially in files under version control. This eases the maintenance +of the lists and clarifies version diffs. Example: + + (define colour-names ;Add more colour names to this list! + '( + blue + cerulean + green + magenta + purple + red + scarlet + turquoise + )) + +,*** Parenthetical Philosophy + +The actual bracket characters are simply lexical tokens to which little +significance should be assigned. Lisp programmers do not examine the +brackets individually, or, Azathoth forbid, count brackets; instead +they view the higher-level structures expressed in the program, +especially as presented by the indentation. Lisp is not about writing +a sequence of serial instructions; it is about building complex +structures by summing parts. The composition of complex structures +from parts is the focus of Lisp programs, and it should be readily +apparent from the Lisp code. Placing brackets haphazardly about the +presentation is jarring to a Lisp programmer, who otherwise would not +even have seen them for the most part. + +,** Indentation and Alignment + +The operator of any form, i.e. the first subform following the opening +round bracket, determines the rules for indenting or aligning the +remaining forms. Many names in this position indicate special +alignment or indentation rules; these are special operators, macros, or +procedures that have certain parameter structures. + +If the first subform is a non-special name, however, then if the second +subform is on the same line, align the starting column of all following +subforms with that of the second subform. If the second subform is on +the following line, align its starting column with that of the first +subform, and do the same for all remaining subforms. + +In general, Emacs will indent Lisp code correctly. Run `C-M-q' +(indent-sexp) on any code to ensure that it is indented correctly, and +configure Emacs so that any non-standard forms are indented +appropriately. + + Unacceptable: + + (+ (sqrt -1) + (* x y) + (+ p q)) + + (+ + (sqrt -1) + (* x y) + (+ p q)) + + Acceptable: + + (+ (sqrt -1) + (* x y) + (+ p q)) + + (+ + (sqrt -1) + (* x y) + (+ p q)) + + Rationale: The columnar alignment allows the reader to follow the + operands of any operation straightforwardly, simply by scanning + downward or upward to match a common column. Indentation dictates + structure; confusing indentation is a burden on the reader who wishes + to derive structure without matching parentheses manually. + +,*** Non-Symbol Indentation and Alignment + +The above rules are not exhaustive; some cases may arise with strange +data in operator positions. + +,**** Lists + +Unfortunately, style varies here from person to person and from editor +to editor. Here are some examples of possible ways to indent lists +whose operators are lists: + + Questionable: + + ((car x) ;Requires hand indentation. + (cdr x) + foo) + + ((car x) (cdr x) ;GNU Emacs + foo) + + Preferable: + + ((car x) ;Any Emacs + (cdr x) + foo) + + ((car x) (cdr x) ;Edwin + foo) + + Rationale: The operands should be aligned, as if it were any other + procedure call with a name in the operator position; anything other + than this is confusing because it gives some operands greater visual + distinction, allowing others to hide from the viewer's sight. For + example, the questionable indentation + + ((car x) (cdr x) + foo) + + can make it hard to see that FOO and (CDR X) are both operands here + at the same level. However, GNU Emacs will generate that indentation + by default. (Edwin will not.) + +,**** Strings + +If the form in question is meant to be simply a list of literal data, +all of the subforms should be aligned to the same column, irrespective +of the first subform. + + Unacceptable: + + ("foo" "bar" "baz" "quux" "zot" + "mumble" "frotz" "gargle" "mumph") + + Questionable, but acceptable: + + (3 1 4 1 5 9 2 6 5 3 5 8 9 7 9 3 2 3 8 4 6 2 6 4 + 3 3 8 3 2 7 9 5 0 2 8 8 4 1 9 7 1 6 9 3 9 9 3) + + Acceptable: + + ("foo" "bar" "baz" "quux" "zot" + "mumble" "frotz" "gargle" "mumph") + + ("foo" + "bar" "baz" "quux" "zot" + "mumble" "frotz" "gargle" "mumph") + + Rationale: Seldom is the first subform distinguished for any reason, + if it is a literal; usually in this case it indicates pure data, not + code. Some editors and pretty-printers, however, will indent + unacceptably in the example given unless the second subform is on the + next line anyway, which is why the last way to write the fragment is + usually best. + +,** Names + +Naming is subtle and elusive. Bizarrely, it is simultaneously +insignificant, because an object is independent of and unaffected by +the many names by which we refer to it, and also of supreme +importance, because it is what programming -- and, indeed, almost +everything that we humans deal with -- is all about. A full +discussion of the concept of name lies far outside the scope of this +document, and could surely fill not even a book but a library. + +Symbolic names are written with English words separated by hyphens. +Scheme and Common Lisp both fold the case of names in programs; +consequently, camel case is frowned upon, and not merely because it is +ugly. Underscores are unacceptable separators except for names that +were derived directly from a foreign language without translation. + + Unacceptable: + + XMLHttpRequest + foreach + append_map + + Acceptable: + + xml-http-request + for-each + append-map + +,*** Funny Characters + +There are several different conventions in different Lisps for the use +of non-alphanumeric characters in names. + +,**** Scheme + +,***** Question Marks: Predicates + +Affix a question mark to the end of a name for a procedure whose +purpose is to ask a question of an object and to yield a boolean +answer. Such procedures are called `predicates'. Do not use a +question mark if the procedure may return any object other than a +boolean. + + Examples: pair? procedure? proper-list? + Non-examples: member assoc any every + +Pronounce the question mark as if it were the isolated letter `p'. For +example, to read the fragment (PAIR? OBJECT) aloud, say: `pair-pee +object.' + +,***** Exclamation Marks: Destructive Operations + +Affix an exclamation mark to the end of a name for a procedure (or +macro) whose primary purpose is to modify an object. Such procedures +are called `destructive'. + + Examples: set-car! append! + +Avoid using the exclamation mark willy nilly for just *any* procedure +whose operation involves any kind of mutation or side effect; instead, +use the exclamation mark to identify procedures that exist *solely* for +the purpose of destructive update (e.g., SET-CAR!), or to distinguish a +destructive, or potentially destructive (in the case of linear-update +operations such as APPEND!), variant of a procedure of which there also +exists a purely functional variant (e.g., APPEND). + +Pronounce the exclamation mark as `bang'. For example, to read the +fragment (APPEND! LIST TAIL) aloud, say: `append-bang list tail.' + +,***** Asterisks: Variants, Internal Routines, Mutable Globals + +Affix an asterisk to the end of a name to make a variation on a theme +of the original name. + + Example: let -> let* + +Prefer a meaningful name over an asterisk; the asterisk does not +explain what variation on the theme the name means. + +Affix an asterisk to the beginning of a name to make an internal +routine for that name. Again, prefer a meaningful name over an +asterisk. + +Affix asterisks to the beginning and end of a globally mutable +variable. This allows the reader of the program to recognize very +easily that it is badly written! + +,***** `WITH-' and `CALL-WITH-': Dynamic State and Cleanup + +Prefix `WITH-' to any procedure that establishes dynamic state and +calls a nullary procedure, which should be the last (required) +argument. The dynamic state should be established for the extent of +the nullary procedure, and should be returned to its original state +after that procedure returns. + + Examples: with-input-from-file with-output-to-file + + Exception: Some systems provide a procedure (WITH-CONTINUATION + <continuation> <thunk>), which calls <thunk> in the given + continuation, using that continuation's dynamic state. If <thunk> + returns, it will return to <continuation>, not to the continuation of + the call to WITH-CONTINUATION. This is acceptable. + +Prefix `CALL-WITH-' to any procedure that calls a procedure, which +should be its last argument, with some arguments, and is either somehow +dependent upon the dynamic state or continuation of the program, or +will perform some action to clean up data after the procedure argument +returns. Generally, `CALL-WITH-' procedures should return the values +that the procedure argument returns, after performing the cleaning +action. + + Examples: + + - CALL-WITH-INPUT-FILE and CALL-WITH-OUTPUT-FILE both accept a + pathname and a procedure as an argument, open that pathname (for + input or output, respectively), and call the procedure with one + argument, a port corresponding with the file named by the given + pathname. After the procedure returns, CALL-WITH-INPUT-FILE and + CALL-WITH-OUTPUT-FILE close the file that they opened, and return + whatever the procedure returned. + + - CALL-WITH-CURRENT-CONTINUATION is dependent on the continuation + with which it was called, and passes as an argument an escape + procedure corresponding with that continuation. + + - CALL-WITH-OUTPUT-STRING, a common but non-standard procedure + definable in terms of OPEN-OUTPUT-STRING and GET-OUTPUT-STRING from + SRFI 6 (Basic String Ports), calls its procedure argument with an + output port, and returns a string of all of the output written to + that port. Note that it does not return what the procedure + argument returns, which is an exception to the above rule. + +Generally, the distinction between these two classes of procedures is +that `CALL-WITH-...' procedures should not establish fresh dynamic +state and instead pass explicit arguments to their procedure arguments, +whereas `WITH-...' should do the opposite and establish dynamic state +while passing zero arguments to their procedure arguments. + +,** Comments + +Write heading comments with at least four semicolons; see also the +section below titled `Outline Headings'. + +Write top-level comments with three semicolons. + +Write comments on a particular fragment of code before that fragment +and aligned with it, using two semicolons. + +Write margin comments with one semicolon. + +The only comments in which omission of a space between the semicolon +and the text is acceptable are margin comments. + + Examples: + + ;;;; Frob Grovel + + ;;; This section of code has some important implications: + ;;; 1. Foo. + ;;; 2. Bar. + ;;; 3. Baz. + + (define (fnord zarquon) + ;; If zob, then veeblefitz. + (quux zot + mumble ;Zibblefrotz. + frotz)) + +,* Riastradh's Non-Standard Rules + +Three principles guide this style, roughly ordered according to +descending importance: + +1. The purpose of a program is to describe an idea, and not the way + that the idea must be realized; the intent of the program's meaning, + rather than peripheral details that are irrelevant to its intent, + should be the focus of the program, *irrespective* of whether a + human or a machine is reading it. [It would be nice to express this + principle more concisely.] + +2. The sum of the parts is easier to understand than the whole. + +3. Aesthetics matters. No one enjoys reading an ugly program. + +,** General Layout + +This section contains rules that the author has found generally helpful +in keeping his programs clean and presentable, though they are not +especially philosophically interesting. + +Contained in the rationale for some of the following rules are +references to historical limitations of terminals and printers, which +are now considered aging cruft of no further relevance to today's +computers. Such references are made only to explain specific measures +chosen for some of the rules, such as a limit of eighty columns per +line, or sixty-six lines per page. There is a real reason for each of +the rules, and this real reason is not intrinsically related to the +historical measures, which are mentioned only for the sake of +providing some arbitrary measure for the limit. + +,*** File Length + +If a file exceeds five hundred twelve lines, begin to consider +splitting it into multiple files. Do not write program files that +exceed one thousand twenty-four lines. Write a concise but +descriptive title at the top of each file, and include no content in +the file that is unrelated to its title. + + Rationale: Files that are any larger should generally be factored + into smaller parts. (One thousand twenty-four is a nicer number than + one thousand.) Identifying the purpose of the file helps to break it + into parts if necessary and to ensure that nothing unrelated is + included accidentally. + +,*** Top-Level Form Length + +Do not write top-level forms that exceed twenty-one lines, except for +top-level forms that serve only the purpose of listing large sets of +data. If a procedure exceeds this length, split it apart and give +names to its parts. Avoid names formed simply by appending a number +to the original procedure's name; give meaningful names to the parts. + + Rationale: Top-level forms, especially procedure definitions, that + exceed this length usually combine too many concepts under one name. + Readers of the code are likely to more easily understand the code if + it is composed of separately named parts. Simply appending a number + to the original procedure's name can help only the letter of the + rule, not the spirit, however, even if the procedure was taken from a + standard algorithm description. Using comments to mark the code with + its corresponding place in the algorithm's description is acceptable, + but the algorithm should be split up in meaningful fragments anyway. + + Rationale for the number twenty-one: Twenty-one lines, at a maximum + of eighty columns per line, fits in a GNU Emacs instance running in a + 24x80 terminal. Although the terminal may have twenty-four lines, + three of the lines are occupied by GNU Emacs: one for the menu bar + (which the author of this guide never uses, but which occupies a line + nevertheless in a vanilla GNU Emacs installation), one for the mode + line, and one for the minibuffer's window. The writer of some code + may not be limited to such a terminal, but the author of this style + guide often finds it helpful to have at least four such terminals or + Emacs windows open simultaneously, spread across a twelve-inch laptop + screen, to view multiple code fragments. + +,*** Line Length + +Do not write lines that exceed eighty columns, or if possible +seventy-two. + + Rationale: Following multiple lines that span more columns is + difficult for humans, who must remember the line of focus and scan + right to left from the end of the previous line to the beginning of + the next line; the more columns there are, the harder this is to do. + Sticking to a fixed limit helps to improve readability. + + Rationale for the numbers eighty and seventy-two: It is true that we + have very wide screens these days, and we are no longer limited to + eighty-column terminals; however, we ought to exploit our wide + screens not by writing long lines, but by viewing multiple fragments + of code in parallel, something that the author of this guide does + very often. Seventy-two columns leave room for several nested layers + of quotation in email messages before the code reaches eighty + columns. Also, a fixed column limit yields nicer printed output, + especially in conjunction with pagination; see the section + `Pagination' below. + +,*** Blank Lines + +Separate each adjacent top-level form with a single blank line (i.e. +two line breaks). If two blank lines seem more appropriate, break the +page instead. Do not place blank lines in the middle of a procedure +body, except to separate internal definitions; if there is a blank +line for any other reason, split the top-level form up into multiple +ones. + + Rationale: More than one blank line is distracting and sloppy. If + the two concepts that are separated by multiple blank lines are + really so distinct that such a wide separator is warranted, then + they are probably better placed on separate pages anyway; see the + next section, `Pagination'. + +,*** Pagination + +Separate each file into pages of no more than sixty-six lines and no +fewer than forty lines with form feeds (ASCII #x0C, or ^L, written in +Emacs with `C-q C-l'), on either side of which is a single line break +(but not a blank line). + + Rationale: Keeping distinct concepts laid out on separate pages + helps to keep them straight. This is helpful not only for the + writer of the code, but also for the reader. It also allows readers + of the code to print it onto paper without fiddling with printer + settings to permit pages of more than sixty-six lines (which is the + default number for many printers), and pagination also makes the + code easier to navigate in Emacs, with the `C-x [' and `C-x ]' keys + (`backward-page' and `forward-page', respectively). To avoid + excessively small increments of page-by-page navigation, and to + avoid wasting paper, each page should generally exceed forty lines. + + `C-x l' in Emacs will report the number of lines in the page on which + the point lies; this is useful for finding where pagination is + necessary. + +,*** Outline Headings + +Use Emacs's Outline Mode to give titles to the pages, and if +appropriate a hierarchical structure. Set `outline-regexp' (or +`outline-pattern' in Edwin) to "\f\n;;;;+ ", so that each form feed +followed by an line break followed by at least four semicolons and a +space indicates an outline heading to Emacs. Use four semicolons for +the highest level of headings in the hierarchy, and one more for each +successively nested level of hierarchy. + + Rationale: Not only does this clarify the organization of the code, + but readers of the code can then navigate the code's structure with + Outline Mode commands such as `C-c C-f', `C-c C-b', `C-c C-u', and + `C-c C-d' (forward, backward, up, down, respectively, headings). + +,*** Dependencies + +When writing a file or module, minimize its dependencies. If there are +too many dependencies, consider breaking the module up into several +parts, and writing another module that is the sum of the parts and that +depends only on the parts, not their dependencies. + + Rationale: A fragment of a program with fewer dependencies is less + of a burden on the reader's cognition. The reader can more easily + understand the fragment in isolation; humans are very good at local + analyses, and terrible at global ones. + +,** Naming + +This section requires an elaborate philosophical discussion which the +author is too ill to have the energy to write at this moment. + +Compose concise but meaningful names. Do not cheat by abbreviating +words or using contractions. + + Rationale: Abbreviating words in names does not make them shorter; + it only makes them occupy less screen space. The reader still must + understand the whole long name. This does not mean, however, that + names should necessarily be long; they should be descriptive. Some + long names are more descriptive than some short names, but there are + also descriptive names that are not long and long names that are not + descriptive. Here is an example of a long name that is not + descriptive, from SchMUSE, a multi-user simulation environment + written in MIT Scheme: + + frisk-descriptor-recursive-subexpr-descender-for-frisk-descr-env + + Not only is it long (sixty-four characters) and completely + impenetrable, but halfway through its author decided to abbreviate + some words as well! + +Do not write single-letter variable names. Give local variables +meaningful names composed from complete English words. + + Rationale: It is tempting to reason that local variables are + invisible to other code, so it is OK to be messy with their names. + This is faulty reasoning: although the next person to come along and + use a library may not care about anything but the top-level + definitions that it exports, this is not the only audience of the + code. Someone will also want to read the code later on, and if it is + full of impenetrably terse variable names without meaning, that + someone will have a hard time reading the code. + +Give names to intermediate values where their expressions do not +adequately describe them. + + Rationale: An `expression' is a term that expresses some value. + Although a machine needs no higher meaning for this value, and + although it should be written to be sufficiently clear for a human to + understand what it means, the expression might mean something more + than just what it says where it is used. Consequently, it is helpful + for humans to see names given to expressions. + + Example: A hash table HASH-TABLE maps foos to bars; (HASH-TABLE/GET + HASH-TABLE FOO #F) expresses the datum that HASH-TABLE maps FOO to, + but that expression gives the reader no hint of any information + concerning that datum. (LET ((BAR (HASH-TABLE/GET HASH-TABLE FOO + #F))) ...) gives a helpful name for the reader to understand the + code without having to find the definition of HASH-TABLE. + + Index variables such as i and j, or variables such as A and D naming + the car and cdr of a pair, are acceptable only if they are completely + unambiguous in the scope. For example, + + (do ((i 0 (+ i 1))) + ((= i (vector-length vector))) + (frobnicate (vector-ref vector i))) + + is acceptable because the scope of i is very clearly limited to a + single vector. However, if more vectors are involved, using more + index variables such as j and k will obscure the program further. + +Avoid functional combinators, or, worse, the point-free (or +`point-less') style of code that is popular in the Haskell world. At +most, use function composition only where the composition of functions +is the crux of the idea being expressed, rather than simply a procedure +that happens to be a composition of two others. + + Rationale: Tempting as it may be to recognize patterns that can be + structured as combinations of functional combinators -- say, `compose + this procedure with the projection of the second argument of that + other one', or (COMPOSE FOO (PROJECT 2 BAR)) --, the reader of the + code must subsequently examine the elaborate structure that has been + built up to obscure the underlying purpose. The previous fragment + could have been written (LAMBDA (A B) (FOO (BAR B))), which is in + fact shorter, and which tells the reader directly what argument is + being passed on to what, and what argument is being ignored, without + forcing the reader to search for the definitions of FOO and BAR or + the call site of the final composition. The explicit fragment + contains substantially more information when intermediate values are + named, which is very helpful for understanding it and especially for + modifying it later on. + + The screen space that can be potentially saved by using functional + combinators is made up for by the cognitive effort on the part of the + reader. The reader should not be asked to search globally for usage + sites in order to understand a local fragment. Only if the structure + of the composition really is central to the point of the narrative + should it be written as such. For example, in a symbolic integrator + or differentiator, composition is an important concept, but in most + code the structure of the composition is completely irrelevant to the + real point of the code. + +If a parameter is ignored, give it a meaningful name nevertheless and +say that it is ignored; do not simply call it `ignored'. + +In Common Lisp, variables can be ignored with (DECLARE (IGNORE ...)). +Some Scheme systems have similar declarations, but the portable way to +ignore variables is just to write them in a command context, where +their values will be discarded, preferably with a comment indicating +this purpose: + + (define (foo x y z) + x z ;ignore + (frobnitz y)) + + Rationale: As with using functional combinators to hide names, + avoiding meaningful names for ignored parameters only obscures the + purpose of the program. It is helpful for a reader to understand + what parameters a procedure is independent of, or if someone wishes + to change the procedure later on, it is helpful to know what other + parameters are available. If the ignored parameters were named + meaninglessly, then these people would be forced to search for call + sites of the procedure in order to get a rough idea of what + parameters might be passed here. + +When naming top-level bindings, assume namespace partitions unless in a +context where they are certain to be absent. Do not write explicit +namespace prefixes, such as FOO:BAR for an operation BAR in a module +FOO, unless the names will be used in a context known not to have any +kind of namespace partitions. + + Rationale: Explicit namespace prefixes are ugly, and lengthen names + without adding much semantic content. Common Lisp has its package + system to separate the namespaces of symbols; most Schemes have + mechanisms to do so as well, even if the RnRS do not specify any. It + is better to write clear names which can be disambiguated if + necessary, rather than to write names that assume some kind of + disambiguation to be necessary to begin with. Furthermore, explicit + namespace prefixes are inadequate to cover name clashes anyway: + someone else might choose the same namespace prefix. Relegating this + issue to a module system removes it from the content of the program, + where it is uninteresting. + +,** Comments + +Write comments only where the code is incapable of explaining itself. +Prefer self-explanatory code over explanatory comments. Avoid +`literate programming' like the plague. + + Rationale: If the code is often incapable of explaining itself, then + perhaps it should be written in a more expressive language. This may + mean using a different programming language altogether, or, since we + are talking about Lisp, it may mean simply building a combinator + language or a macro language for the purpose. `Literate programming' + is the logical conclusion of languages incapable of explaining + themselves; it is a direct concession of the inexpressiveness of the + computer language implementing the program, to the extent that the + only way a human can understand the program is by having it rewritten + in a human language. + +Do not write interface documentation in the comments for the +implementation of the interface. Explain the interface at the top of +the file if it is a single-file library, or put that documentation in +another file altogether. (See the `Documentation' section below if the +interface documentation comments grow too large for a file.) + + Rationale: A reader who is interested only in the interface really + should not need to read through the implementation to pick out its + interface; by putting the interface documentation at the top, not + only is such a reader's task of identifying the interface made + easier, but the implementation code can be more liberally commented + without fear of distracting this reader. To a reader who is + interested in the implementation as well, the interface is still + useful in order to understand what concepts the implementation is + implementing. + + Example: <http://mumble.net/~campbell/scheme/skip-list.scm> + + In this example of a single-file library implementing the skip list + data structure, the first page explains the purpose and dependencies + of the file (which are useful for anyone who intends to use it, even + though dependencies are really implementation details), and the next + few pages explain the usage of skip lists as implemented in that + file. On the first page of implementation, `Skip List Structure', + there are some comments of interest only to a reader who wishes to + understand the implementation; the same goes for the rest of the + file, none of which must a reader read whose interest is only in the + usage of the library. + +Avoid block comments (i.e. #| ... |#). Use S-expression comments (`#;' +in Scheme, with the expression to comment on the next line; `#+(OR)' or +`#-(AND)' in Common Lisp) to comment out whole expressions. Use blocks +of line comments for text. + + Rationale: Editor support for block comments is weak, because it + requires keeping a detailed intermediate parse state of the whole + buffer, which most Emacsen do not do. At the very least, #|| ... ||# + is better, because most Emacsen will see vertical bars as symbol + delimiters, and lose trying to read a very, very long symbol, if they + try to parse #| ... |#, whereas they will just see two empty symbols + and otherwise innocuous text between them if they try to parse #|| + ... ||#. In any case, in Emacs, `M-x comment-region RET', or `M-;' + (comment-dwim), is trivial to type. + + The only standard comments in Scheme are line comments. There are + SRFIs for block comments and S-expression comments, but support for + them varies from system to system. Expression comments are not hard + for editors to deal with because it is safe not to deal with them at + all; however, in Scheme S-expression comments, which are written by + prefixing an expression with `#;', the expression to be commented + should be placed on the next line. This is because editors that do + not deal with them at all may see the semicolon as the start of a + line comment, which will throw them off. Expression comments in + Common Lisp, however, are always safe. + + In Common Lisp, the two read-time optionals that are guaranteed to + ignore any form following them are `#+(OR)' and `#-(AND)'. `#+NIL' + is sometimes used in their stead, but, while it may appear to be an + obviously false optional, it actually is not. The feature + expressions are read in the KEYWORD package, so NIL is read not as + CL:NIL, i.e. the boolean false value, but as :NIL, a keyword symbol + whose name happens to be `NIL'. Not only is it not read as the + boolean false value, but it has historically been used to indicate a + feature that might be enabled -- in JonL White's New Implementation + of Lisp! However, the New Implementation of Lisp is rather old these + days, and unlikely to matter much...until Alastair Bridgewater writes + Nyef's Implementation of Lisp. + +,** Documentation + +On-line references and documentation/manuals are both useful for +independent purposes, but there is a very fine distinction between +them. Do not generate documentation or manuals automatically from the +text of on-line references. + + Rationale: /On-line references/ are quick blurbs associated with + objects in a running Lisp image, such as documentation strings in + Common Lisp or Emacs Lisp. These assume that the reader is familiar + with the gist of the surrounding context, but unclear on details; + on-line references specify the details of individual objects. + + /Documentation/ and /manuals/ are fuller, organized, and cohesive + documents that explain the surrounding context to readers who are + unfamiliar with it. A reader should be able to pick a manual up and + begin reading it at some definite point, perusing it linearly to + acquire an understanding of the subject. Although manuals may be + dominated by reference sections, they should still have sections that + are linearly readable to acquaint the reader with context. + +,** Round and Square Brackets + +Some implementations of Scheme provide a non-standard extension of the +lexical syntax whereby balanced pairs of square brackets are +semantically indistinguishable from balanced pairs of round brackets. +Do not use this extension. + + Rationale: Because this is a non-standard extension, it creates + inherently non-portable code, of a nature much worse than using a + name in the program which is not defined by the R5RS. The reason + that we have distinct typographical symbols in the first place is to + express different meaning. The only distinction between round + brackets and square brackets is in convention, but the precise nature + of the convention is not specified by proponents of square brackets, + who suggest that they be used for `clauses', or for forms that are + parts of enclosing forms. This would lead to such constructions as + + (let [(x 5) (y 3)] ...) + + or + + (let ([x 5] [y 3]) ...) + + or + + (let [[x 5] [y 3]] ...), + + the first two of which the author of this guide has seen both of, and + the last of which does nothing to help to distinguish the parentheses + anyway. + + The reader of the code should not be forced to stumble over a + semantic identity because it is expressed by a syntactic distinction. + The reader's focus should not be directed toward the lexical tokens; + it should be directed toward the structure, but using square brackets + draws the reader's attention unnecessarily to the lexical tokens. + +,* Attribution + +#+END_SRC +:END: + +**** TODO I have to get change (let ([x 5] [y 3])) -> (let ((x 5) (y 3))) +**** TODO comments + +#+BEGIN_SRC scheme + ;;;; Frob Grovel + + ;;; This section of code has some important implications: + ;;; 1. Foo. + ;;; 2. Bar. + ;;; 3. Baz. + + (define (fnord zarquon) + ;; If zob, then veeblefitz. + (quux zot + mumble ;Zibblefrotz. + frotz)) + +#+END_SRC +**** TODO literal data +Strings + +If the form in question is meant to be simply a list of literal data, +all of the subforms should be aligned to the same column, irrespective +of the first subform. + + Unacceptable: + + ("foo" "bar" "baz" "quux" "zot" + "mumble" "frotz" "gargle" "mumph") + + Questionable, but acceptable: + + (3 1 4 1 5 9 2 6 5 3 5 8 9 7 9 3 2 3 8 4 6 2 6 4 + 3 3 8 3 2 7 9 5 0 2 8 8 4 1 9 7 1 6 9 3 9 9 3) + + Acceptable: + + ("foo" "bar" "baz" "quux" "zot" + "mumble" "frotz" "gargle" "mumph") + + ("foo" + "bar" "baz" "quux" "zot" + "mumble" "frotz" "gargle" "mumph") +**** TODO follow this syntax convention proc args + +:alignment: +The operator of any form, i.e. the first subform following the opening +round bracket, determines the rules for indenting or aligning the +remaining forms. Many names in this position indicate special +alignment or indentation rules; these are special operators, macros, or +procedures that have certain parameter structures. + +If the first subform is a non-special name, however, then if the second +subform is on the same line, align the starting column of all following +subforms with that of the second subform. If the second subform is on +the following line, align its starting column with that of the first +subform, and do the same for all remaining subforms. + +In general, Emacs will indent Lisp code correctly. Run `C-M-q' +(indent-sexp) on any code to ensure that it is indented correctly, and +configure Emacs so that any non-standard forms are indented +appropriately. + + Unacceptable: + + (+ (sqrt -1) + (* x y) + (+ p q)) + + (+ + (sqrt -1) + (* x y) + (+ p q)) + + Acceptable: + + (+ (sqrt -1) + (* x y) + (+ p q)) + + (+ + (sqrt -1) + (* x y) + (+ p q)) + + Rationale: The columnar alignment allows the reader to follow the + operands of any operation straightforwardly, simply by scanning + downward or upward to match a common column. Indentation dictates + structure; confusing indentation is a burden on the reader who wishes + to derive structure without matching parentheses manually. + +:END: + +If you have a procedure, then it's arguments should be on the same line. +#+BEGIN_SRC scheme +(proc args + (proc (proc + args) + (proc args) + (proc (proc + args) + (proc (proc + (proc (proc + args)))))) + + (proc (proc + args))) +#+END_SRC + +*** TODO write various tests for =<opensmtpd-configuration>= + +I have many bits of code in opensmtpd.org.archive that should result in an +error. I should write some tests for this. +** NO should I modifiy some of the records to include a sanitize field? +Probably not. It would be cool if this function ran automatically upon record +initiation, but there's no to make it do that. +** TODO which sanitize function is better? Pick the better sanitize method and use that one. + +The sanitize function found in opensmtpd-listen-on-configuration-filters + +Or the sanitize function +sanitize-list-of-options-for-match-configuration ? + +sanitize-list-of-options-for-match-configuration is probably faster. But is it? +It is an iteratize loop that checks for all issues as it loops through the +options. There is a lot of repetitive code in this procedure. + +BUT opensmtpd-listen-on-configuration-filters certainly seems easier to follow. +** TODO remove opensmtpd-table-type fieldname and instead move that it its own procedure outside of the record + +ONly use one function instead of + + +;; this procedure takes in one argument. +;; if that argument is an <opensmtpd-table-configuration> whose fieldname 'values' is an assoc-list, then it returns +;; #t, #f if otherwise. +;; TODO should I remove these two functions? And instead use the (opensmtpd-table-configuration-type) procedure? +(define (table-whose-data-are-assoc-list? table) + (if (not (opensmtpd-table-configuration? table)) + #f + (assoc-list? (opensmtpd-table-configuration-data table)))) + +;; this procedure takes in one argument +;; if that argument is an <opensmtpd-table-configuration> whose fieldname 'values' is a list of strings, then it returns +;; #t, #f if otherwise. +(define (table-whose-data-are-a-list-of-strings? table) + (if (not (opensmtpd-table-configuration? table)) + #f + (list-of-strings? (opensmtpd-table-configuration-data table)))) + + And opensmtpd-table-type +** TODO OpenSMTPD Service documentation + +OpenSMTPD is an easy-to-use mail transfer agent (MTA). Its configuration file is +throughly documented in man 5 =smtpd.conf=. OpenSMTPD *listens* for incoming +mail and *matches* the mail to *actions*. The following records represent those +stages: ~<opensmtpd-listen-on-configuration>~, +~<opensmtpd-listen-on-socket-configuration>=, =<opensmtpd-match-configuration>~, +~<opensmtpd-action-local-delivery-configuration>~, and +~<opensmtpd-action-relay-configuration>~. + +Additionally, each ~<opensmtpd-listen-on-configuration>~ and +~<opensmtpd-listen-on-socket-configuration>~ may use a list of +~<opensmtpd-filter-configuration>~, and/or +~<opensmtpd-filter-phase-configuration>~ records to filter email/spam. Also +numerous records' fieldnames use ~<opensmtpd-table-configuration>~ to hold lists +or key value pairs of data. + +A simple example configuration is below: + +#+BEGIN_SRC scheme +(let ((smtp.gnu.org (opensmtpd-pki-configuration + (domain "smtp.gnu.org") + (cert "file.cert") + (key "file.key")))) + (service opensmtpd-service-type + (opensmtpd-configuration + (listen-ons (list + (opensmtpd-listen-on-configuration + (pki smtp.gnu.org)) + (opensmtpd-listen-on-configuration + (pki smtp.gnu.org) + (secure-connection "smtps")))) + (matches (list + (opensmtpd-match-configuration + (action + (opensmtpd-action-local-delivery-configuration + (name "local-delivery")))) + (opensmtpd-match-configuration + (action + (opensmtpd-action-relay-configuration + (name "relay"))))))))) +#+END_SRC + +- Scheme Variable: opensmtpd-service-type + + Service type for the OpenSMTPD ([[https://www.opensmtpd.org][https://www.opensmtpd.org]]) email server. The + value for this service type is a ~<opensmtpd-configuration>~ record. + +- Data Type: opensmtpd-configuration + + Data type representing the configuration of OpenSMTPD. + + - ~package~ (default: ~opensmtpd~) + + The OpenSMTPD package to use. + + - ~config-file~ (default: ~#f~) + + File-like object of the OpenSMTPD configuration file to use. By default it + listens on the loopback network interface, and allows for mail from users + and daemons on the local machine, as well as permitting email to remote + servers. Run ~man smtpd.conf~ for more information. + + - ~bounce~ (default: ~(list "4h")~) + + ~bounce~ is a list of strings, which send warning messages to the envelope + sender when temporary delivery failures cause a message to remain in the + queue for longer than string _delay_. Each string _delay_ parameter consists + of a string beginning with a positive decimal integer and a unit s, m, h, + or d. At most four delay parameters can be specified. + + - ~listen-ons~ (default: ~(list (opensmtpd-listen-on-configuration))~ ) + + ~listen-ons~ is a list of ~<opensmtpd-listen-on-configuration>~ records. + This list details what interfaces and ports OpenSMTPD listens on as well as + other information. + + - ~listen-on-socket~ (default: ~(opensmtpd-listen-on-socket-configuration-configuration)~ ) + + Listens for incoming connections on the Unix domain socket. + + - ~includes~ (default: ~#f~) + + # TODO ~includes~ should support a list of string filenames or gexps. + ~includes~ is a list of string _filenames_. Each filename's contents is + additional configuration that is inserted into the top of the configuration + file. + + - ~matches~ default: + + #+BEGIN_SRC scheme + (list (opensmtpd-match-configuration + (action (opensmtpd-action-local-delivery-configuration + (name "local") + (method "mbox"))) + (for (opensmtpd-option-configuration + (option "for local")))) + (opensmtpd-match-configuration + (action (opensmtpd-action-relay-configuration + (name "outbound"))) + (from (opensmtpd-option-configuration + (option "from local"))) + (for (opensmtpd-option-configuration + (option "for any"))))) + #+END_SRC + + ~matches~ is a list of ~<opensmtpd-match-configuration>~ records, which + matches incoming mail and sends it to a correspending action. The match + records are evaluated sequentially, with the first match winning. If an + incoming mail does not match any match records, then it is rejected. + + # TODO when the code supports mda-wrappers, add in this documentation. + # - ~mda-wrappers~ + + - ~mta-max-deferred~ (default: ~100~) + + When delivery to a given host is suspended due to temporary failures, cache + at most _number_ envelopes for that host such that they can be delivered as + soon as another delivery succeeds to that host. The default is 100. + + - ~queue~ (default: ~#f~) + + ~queue~ expects an ~<opensmtpd-queue-configuration>~ record. With it, one may + compress and encrypt queue-ed emails as well as set the default expiration + time for temporarily undeliverable messages. + + - ~smtp~ (default: ~#f~) + + ~smtp~ expects an ~<opensmtpd-smtp-configuration>~ record, which lets one + specifiy how large email may be along with other settings. + + - ~srs~ (default: ~#f~) + + ~srs~ expects an ~<opensmtpd-srs-configuration>~ record, which lets one set + up SRS, the Sender Rewritting Scheme. + +- Data Type: opensmtpd-listen-on-configuration + + Data type representing the configuration of an + ~<opensmtpd-listen-on-configuration>~. Listen on the fieldname ~interface~ for + incoming connections, using the same syntax as for ifconfig(8). The interface + parameter may also be an string interface group, an string IP address, or a + string domain name. Listening can optionally be restricted to a specific + address fieldname ~family~, which can be either "inet4" or "inet6". + + - ~interface~ (default: "lo") + + The string interface to listen for incoming connections. These interface can + usually be found by the command ~ip link~. + + - ~family~ (default: ~#f~) + + The string IP family to use. Valid strings are "inet4" or "inet6". + + - ~auth~ (default: ~#f~) + + Support SMTPAUTH: clients may only start SMTP transactions after successful + authentication. If ~auth~ is ~#t~, then users are authenticated against + their own normal login credentials. Alternatively ~auth~ may be an + ~<opensmtpd-table-configuration>~ whose users are authenticated against + their passwords. + + - ~auth-optional~ (default: ~#f~) + + Support SMTPAUTH optionally: clients need not authenticate, but may do so. + This allows the ~<opensmtpd-listen-on-configuration>~ to both accept + incoming mail from untrusted senders and permit outgoing mail from + authenticated users (using ~<opensmtpd-match-configuration>~ fieldname + ~auth~). It can be used in situations where it is not possible to listen on + a separate port (usually the submission port, 587) for users to + authenticate. + + - ~filters~ (default: ~#f~) + + A list of one or many ~<opensmtpd-filter-configuration>~ or + ~<opensmtpd-filter-phase-configuration>~ records. The filters are applied + sequentially. These records listen and filter on connections handled by this + listener. + + - ~hostname~ (default: ~#f~) + + Use string "hostname" in the greeting banner instead of the default server + name. + + - ~hostnames~ (default: ~#f~) + + Override the server name for specific addresses. Use a + ~<opensmtpd-table-configuration>~ containing a mapping of string IP + addresses to hostnames. If the address on which the connection arrives + appears in the mapping, the associated hostname is used. + + - ~mask-src~ (default: ~#f~) + + If ~#t~, then omit the from part when prepending “Received” headers. + + - ~disable-dsn~ (default: ~#f~) + + When ~#t~, then disable the DSN (Delivery Status Notification) extension. + + - ~pki~ (default: ~#f~) + + For secure connections, use an ~<opensmtpd-pki-configuration>~ + to prove a mail server's identity. + + - ~port~ (default: ~#f~) + + Listen on the _integer_ port instead of the default port of 25. + + - ~proxy-v2~ (default: ~#f~) + + If ~#t~, then support the PROXYv2 protocol, rewriting appropriately source + address received from proxy. + + - ~received-auth~ (default: ~#f~) + + If ~#t~, then in “Received” headers, report whether the session was + authenticated and by which local user. + + - ~senders~ (default: ~#f~) + + Look up the authenticated user in the supplied + ~<opensmtpd-table-configuration>~ to find the email addresses that user is + allowed to submit mail as. + + - ~secure-connection~ (default: ~#f~) + + This is a string of one of these options: + + |----------------------+---------------------------------------------| + | "smtps" | Support SMTPS, by default on port 465. | + |----------------------+---------------------------------------------| + | "tls" | Support STARTTLS, by default on port 25. | + |----------------------+---------------------------------------------| + | "tls-require-verify" | Like tls, but force clients to establish | + | | a secure connection before being allowed to | + | | start an SMTP transaction. With the verify | + | | option, clients must also provide a valid | + | | certificate to establish an SMTP session. | + |----------------------+---------------------------------------------| + + - ~tag~ (default: ~#f~) + + Clients connecting to the listener are tagged with the given string tag. + +- Data Type: opensmtpd-listen-on-socket-configuration + + Data type representing the configuration of an + ~<opensmtpd-listen-on-socket-configuration>~. Listen for incoming SMTP + connections on the Unix domain socket =/var/run/smtpd.sock=. This is done by + default, even if the directive is absent. + + - ~filters~ (default: ~#f~) + + A list of one or many ~<opensmtpd-filter-configuration>~ or + ~<opensmtpd-filter-phase-configuration>~ records. These filter incoming + connections handled by this listener. + + - ~mask-src~ (default: ~#f~) + + If ~#t~, then omit the from part when prepending “Received” headers. + + - ~tag~ (default: ~#f~) + + Clients connecting to the listener are tagged with the given string tag. + +- Data Type: opensmtpd-match-configuration + + This data type represents the configuration of an + ~<opensmtpd-match-configuration>~ record. + + If at least one mail envelope matches the options of one match record, receive + the incoming message, put a copy into each matching envelope, and atomically + save the envelopes to the mail spool for later processing by the respective + ~<opensmtpd-action-configuration>~ found in fieldname ~action~. + + - ~action~ (default: ~#f~) + + If mail matches this match configuration, then do this action. Valid values + include ~<opensmtpd-action-local-delivery-configuration>~ or + ~<opensmtpd-action-relay-configuration>~. + + - ~options~ (default: ~#f~) ~<opensmtpd-option-configuration>~ + The fieldname 'option' is a list of unique + ~<opensmtpd-option-configuration>~ records. + + Each ~<opensmtpd-option-configuration>~ record's fieldname 'option' has some + mutually exclusive options: there can be one "for" and one "from" option. + + |---------------------------+--------------------------------| + | for | from | + |---------------------------+--------------------------------| + | use one of the following: | only use one of the following: | + |---------------------------+--------------------------------| + | "for any" | "from any" | + | "for local" | "from auth" | + | "for domain" | "from local" | + | "for rcpt-to" | "from mail-from" | + | | "from socket" | + | | "from src" | + |---------------------------+--------------------------------| + + The following matching options are supported and can all be negated via (not + #t). The options that support a table (anything surrounded with '<' and '>' + eg: <table>), also support specifying regex via (regex #t). + + - =for any= + + Specify that session may address any destination. + + - =for local= + + Specify that session may address any local domain. This is the default, + and may be omitted. + + - =for domain _domain_ | <domain>= + + Specify that session may address the string or list table _domain_. + + - =for rcpt-to _recipient_ | <recipient>= + + Specify that session may address the string or list table _recipient_. + + - =from any= + + Specify that session may originate from any source. + + - =from auth= + + Specify that session may originate from any authenticated user, no matter + the source IP address. + + - =from auth _user_ | <user>= + + Specify that session may originate from authenticated _user_ or user list + user, no matter the source IP address. + + - =from local= + + Specify that session may only originate from a local IP address, or from + the local enqueuer. This is the default, and may be omitted. + + - =from mail-from _sender_ | <sender>= + + Specify that session may originate from _sender_ or table _sender_, no + matter the source IP address. + + - =from rdns= + + Specify that session may only originate from an IP address that resolves + to a reverse DNS. + + - =from rdns _hostname_ | <hostname>= + + Specify that session may only originate from an IP address that resolves + to a reverse DNS matching string or list string _hostname_. + + - =from socket= + + Specify that session may only originate from the local enqueuer. + + - =from src _address_ | <address>= + + Specify that session may only originate from string or list table address + which can be a specific _address_ or a subnet expressed in CIDR-notation. + + - =auth= + + Matches transactions which have been authenticated. + + - =auth _username_ | <username>= + + Matches transactions which have been authenticated for user or user list + _username_. + + - =helo _helo-name_ | <helo-name>= + + Specify that session's HELO / EHLO should match the string or list table + _helo-name_. + + - =mail-from _sender_ | <sender>= + + Specify that transactions's MAIL FROM should match the string or list + table _sender_. + + - =rcpt-to _recipient_ | <recipient>= + + Specify that transaction's RCPT TO should match the string or list table + _recipient_. + + - =tag tag= + Matches transactions tagged with the given _tag_. + + - =tls= + Specify that transaction should take place in a TLS channel. + + Here is a simple example: + #+BEGIN_SRC scheme + (opensmtpd-option-configuration + (not #t) + (regex #f) + (option "for domain") + (data (opensmtpd-table-configuration + (name "domain-table") + (data (list "gnu.org" "dismail.de"))))) + #+END_SRC + + The mail must NOT come from the domains =gnu.org= or =dismail.de=. + + - Data Type: opensmtpd-option-configuration + +- Data Type: opensmtpd-action-local-delivery-configuration + + This data type represents the configuration of an + ~<opensmtpd-action-local-delivery-configuration>~ record. + + - ~name~ (default: ~#f~) + + ~name~ is the string name of the relay action. + + - ~method~ (default: ~"mbox"~) + + The email delivery option. Valid options are: + + - ~"mbox"~ + + Deliver the message to the user's mbox with mail.local(8). + + - ~"expand-only"~ + + Only accept the message if a delivery method was specified in an aliases + or _.forward file_. + + - ~"forward-only"~ + + Only accept the message if the recipient results in a remote address after + the processing of aliases or forward file. + + - ~<opensmtpd-lmtp-configuration>~ + + Deliver the message to an LMTP server at + ~<opensmtpd-lmtp-configuration>~'s fieldname ~destination~. The location + may be expressed as string host:port or as a UNIX socket. Optionally, + ~<opensmtpd-lmtp-configuration>~'s fieldname ~rcpt-to~ might be specified + to use the recipient email address (after expansion) instead of the local + user in the LMTP session as RCPT TO. + + - ~<opensmtpd-maildir-configuration>~ + + Deliver the message to the maildir in + ~<opensmtpd-maildir-configuration>~'s fieldname ~pathname~ if specified, + or by default to =~/Maildir=. + + The pathname may contain format specifiers that are expanded before use + (see the below section about Format Specifiers). + + If ~<opensmtpd-maildir-configuration>~'s record fieldname ~junk~ is ~#t~, + then message will be moved to the ‘Junk’ folder if it contains a positive + ‘X-Spam’ header. This folder will be created under fieldname ~pathname~ if + it does not yet exist. + + - ~<opensmtpd-mda-configuration>~ + + Delegate the delivery to the ~<opensmtpd-mda-configuration>~'s fieldname + ~command~ (type string) that receives the message on its standard input. + + The ~command~ may contain format specifiers that are expanded before use + (see Format Specifiers). + + - ~alias~ (default: ~#f~) + + Use the mapping table for aliases expansion. ~alias~ is an + ~<opensmtpd-table-configuration>~. + + - ~ttl~ (default: ~#f~) + + ~ttl~ is a string specify how long a message may remain in the queue. It's + format is =n{s|m|h|d}=. eg: "4m" is four minutes. + + - ~user~ (default: ~#f~ ) + + ~user~ is the string username for performing the delivery, to be looked up + with getpwnam(3). + + This is used for virtual hosting where a single username is in charge of + handling delivery for all virtual users. + + This option is not usable with the mbox delivery method. + + - ~userbase~ (default: ~#f~) + + ~userbase~ is an ~<opensmtpd-table-configuration>~ record for mapping user + lookups instead of the getpwnam(3) function. + + The fieldnames ~user~ and ~userbase~ are mutually exclusive. + + - ~virtual~ (default: ~#f~) + + ~virtual~ is an ~<opensmtpd-table-configuration>~ record is used for virtual + expansion. + # TODO man 5 smtpd.conf says "The aliasing table format is described in + # table(5)." What is virtual expansion? I do NOT know how to use ~virtual~ + # properly. What sort of <opensmtpd-table-configuration> do I need? does the + # below work? + # (opensmtpd-table (name "virtual") (data '(("joshua" . "jbranso@HIDDEN")))) + + # TODO fix this ~wrapper documentation~. Should it accept an + # <opensmtpd-mda-configuration> ? If so, then I need to write an <opensmtpd-mda-configuration> + # - ~wrapper~ (default: ) + + # TODO double check that these options are all correct + +- Data Type: opensmtpd-action-relay-configuration + + This data type represents the configuration of an + ~<opensmtpd-action-relay-configuration>~ record. + + - ~name~ (default: ~#f~) + + ~name~ is the string name of the relay action. + + - ~backup~ (default: ~#f~) + + When ~#t~, operate as a backup mail exchanger delivering messages to any + mail exchanger with higher priority. + + - ~backup-mx~ (default: ~#f~) + + Operate as a backup mail exchanger delivering messages to any mail exchanger + with higher priority than mail exchanger identified as string name. + + - ~helo~ (default: ~#f~) + + Advertise string heloname as the hostname to other mail exchangers during + the HELO phase. + + - ~helo-src~ (default: ~#f~ ) + + Use the mapping ~<openmstpd-table-configuration>~ to look up a hostname + matching the source address, to advertise during the HELO phase. + + - ~domain~ (default: ~#f~) + + Do not perform MX lookups but look up destination domain in an + ~<opensmtpd-table-configuration>~ and use matching relay url as relay host. + + - ~host~ (default: ~#f~) + + Do not perform MX lookups but relay messages to the relay host described by + the string relay-url. The format for relay-url is + =[proto://[label@]]host[:port]=. The following protocols are available: + + |------------+----------------------------------------------------------------| + | smtp | Normal SMTP session with opportunistic STARTTLS (the default). | + | smtp+tls | Normal SMTP session with mandatory STARTTLS. | + | smtp+notls | Plain text SMTP session without TLS. | + | lmtp | LMTP session. port is required. | + | smtps | SMTP session with forced TLS on connection, default port is | + | | 465. | + |------------+----------------------------------------------------------------| + + Unless noted, port defaults to 25. + + The label corresponds to an entry in a credentials table, as documented in + =table(5)=. It is used with the ="smtp+tls"= and ="smtps"= protocols for + authentication. Server certificates for those protocols are verified by + default. + + - ~pki~ (default: ~#f~) + + For secure connections, use the certificate associated with + ~<opensmtpd-pki-configuration>~ (declared in a pki directive) to prove the + client's identity to the remote mail server. + + - ~srs~ (default: ~#f~) + + If ~#t~, then when relaying a mail resulting from a forward, use the Sender + Rewriting Scheme to rewrite sender address. + + - ~tls~ (default: ~#f~) boolean or string "no-verify" + + When ~#t~, Require TLS to be used when relaying, using mandatory STARTTLS by + default. When used with a smarthost, the protocol must not be + ="smtp+notls://"=. When string ~"no-verify"~, then do not require a valid + certificate. + + - ~auth~ (default: ~#f~) ~<opensmtpd-table-configuration>~ + + Use the alist ~<opensmtpd-table-configuration>~ for connecting to relay-url + using credentials. This option is usable only with fieldname ~host~ option. + + - ~mail-from~ (default: ~#f~) string + + Use the string _mailaddress_ as MAIL FROM address within the SMTP transaction. + + - ~src~ (default: ~#f~) string | ~<opensmtpd-table-configuration>~ + + Use the string or ~<opensmtpd-table-configuration>~ sourceaddr for the + source IP address, which is useful on machines with multiple interfaces. If + the list contains more than one address, all of them are used in such a way + that traffic is routed as efficiently as possible. + +- Data Type: opensmtpd-filter-configuration + + This data type represents the configuration of an + ~<opensmtpd-filter-configuration>~. This is the filter record one should use + if they want to use an external package to filter email eg: rspamd or + spamassassin. + + - ~name~ (default: ~#f~) + + The string name of the filter. + + - ~proc~ (default: ~#f~) + + # TODO let ~proc~ be a gexp + The string command or process name. If ~proc-exec~ is ~#t~, ~proc~ is + treated as a command to execute. Otherwise, it is a process name. + + - ~proc-exec~ (default: ~#f~) + +- Data Type: opensmtpd-filter-phase-configuration + + This data type represents the configuration of an + ~<opensmtpd-filter-phase-configuration>~. + + In a regular workflow, smtpd(8) may accept or reject a message based only on + the content of envelopes. Its decisions are about the handling of the message, + not about the handling of an active session. + + Filtering extends the decision making process by allowing smtpd(8) to stop at + each phase of an SMTP session, check that options are met, then decide if a + session is allowed to move forward. + + With filtering via an ~<opensmtpd-filter-phase-configuration>~ record, a + session may be interrupted at any phase before an envelope is complete. A + message may also be rejected after being submitted, regardless of whether the + envelope was accepted or not. + + - ~name~ (default: ~#f~) + + The string name of the filter phase. + + - ~phase-name~ (default: ~#f~) + + The string name of the phase. Valid values are: + + |-------------+-----------------------------------------------| + | "connect" | upon connection, before a banner is displayed | + |-------------+-----------------------------------------------| + | "helo" | after HELO command is submitted | + |-------------+-----------------------------------------------| + | "ehlo" | after EHLO command is submitted | + |-------------+-----------------------------------------------| + | "mail-from" | after MAIL FROM command is submitted | + |-------------+-----------------------------------------------| + | "rcpt-to" | after RCPT TO command is submitted | + |-------------+-----------------------------------------------| + | "data" | after DATA command is submitted | + |-------------+-----------------------------------------------| + | "commit" | after message is fully is submitted | + |-------------+-----------------------------------------------| + + - ~options~ (default ~#f~) + + A list of unique ~<opensmtpd-option-configuration>~ records. + + At each phase, various options, specified by a list of + ~<opensmtpd-option-configuration>~, may be checked. The + ~<opensmtpd-option-configuration>~'s fieldname 'option' values of: "fcrdns", + "rdns", and "src" data are available in all phases, but other data must have + been already submitted before they are available. Options with a =<table>= + next to them require the ~<opensmtpd-option-configuration>~'s fieldname + ~data~ to be an ~<opensmtpd-table-configuration>~. There are the available + options: + + |-------------------+----------------------------------------| + | fcrdns | forward-confirmed reverse DNS is valid | + |-------------------+----------------------------------------| + | rdns | session has a reverse DNS | + |-------------------+----------------------------------------| + | rdns <table> | session has a reverse DNS in table | + |-------------------+----------------------------------------| + | src <table> | source address is in table | + |-------------------+----------------------------------------| + | helo <table> | helo name is in table | + |-------------------+----------------------------------------| + | auth | session is authenticated | + |-------------------+----------------------------------------| + | auth <table> | session username is in table | + |-------------------+----------------------------------------| + | mail-from <table> | sender address is in table | + |-------------------+----------------------------------------| + | rcpt-to <table> | recipient address is in table | + |-------------------+----------------------------------------| + + These conditions may all be negated by setting + ~<opensmtpd-option-configuration>~'s fieldname ~not~ to ~#t~. + + Any conditions that require a table may indicate that tables include regexs + setting ~<opensmtpd-option-configuration>~'s fieldname ~regex~ to ~#t~. + + - ~decision~ + + A string decision to be taken. Some decisions require an ~message~ or + ~value~. Valid strings are: + + |----------------------+------------------------------------------------| + | "bypass" | the session or transaction bypasses filters | + |----------------------+------------------------------------------------| + | "disconnect" message | the session is disconnected with message | + |----------------------+------------------------------------------------| + | "junk" | the session or transaction is junked, i.e., an | + | | ‘X-Spam: yes’ header is added to any messages | + |----------------------+------------------------------------------------| + | "reject" message | the command is rejected with message | + |----------------------+------------------------------------------------| + | "rewrite" value | the command parameter is rewritten with value | + |----------------------+------------------------------------------------| + + Decisions that involve a message require that the message be RFC valid, + meaning that they should either start with a 4xx or 5xx status code. + Descisions can be taken at any phase, though junking can only happen before + a message is committed. + + - ~message~ (default ~#f~) + + A string message beginning with a 4xx or 5xx status code. + + - ~value~ (default: ~#f~) + + A number value. ~value~ and ~message~ are mutually exclusive. + +- Data Type: opensmtpd-option-configuration + + This data type represents the configuration of an + ~<opensmtpd-option-configuration>~, which is used by + ~<opensmtpd-filter-phase-configuration>~ and ~<opensmtpd-match-configuration>~ + to match various options for email. + + - ~conditition~ (default ~#f~) + + A string option to be taken. Some options require a string or an + ~<opensmtpd-table-configuration>~ via the fieldname data. When the option + record is used inside of an ~<opensmtpd-filter-phase-configuration>~, then + valid strings are: + + At each phase, various options may be matched. The fcrdns, rdns, and src + data are available in all phases, but other data must have been already + submitted before they are available. + + |---------------------+----------------------------------------| + | "fcrdns" | forward-confirmed reverse DNS is valid | + | "rdns" | session has a reverse DNS | + | "rdns" <table> | session has a reverse DNS in table | + | "src" <table> | source address is in table | + | "helo" <table> | helo name is in table | + | "auth" | session is authenticated | + | "auth" <table> | session username is in table | + | "mail-from" <table> | sender address is in table | + | "rcpt-to" <table> | recipient address is in table | + |---------------------+----------------------------------------| + + When ~<opensmtpd-option-configuration>~ is used inside of an + ~<opensmtpd-match-configuration>~, then valid strigs for fieldname ~option~ + are: "for", "for any", "for local", "for domain", "for rcpt-to", "from any" + "from auth", "from local", "from mail-from", "from rdns", "from socket", + "from src", "auth", "helo", "mail-from", "rcpt-to", "tag", or "tls". + + - ~data~ (default ~#f~) ~<opensmtpd-table-configuration>~ + + Some options require a table to be present. One would specify that table + here. + - ~regex~ (default: ~#f~) boolean + + Any options using a table may indicate that tables hold regex by + prefixing the table name with the keyword regex. + - ~not~ (default: ~#f~) boolean + + When ~#t~, this option record is negated. + +- Data Type: opensmtpd-table-configuration + + This data type represents the configuration of an + ~<opensmtpd-table-configuration>~. + + - ~name~ (default ~#f~) + + ~name~ is the name of the ~<opensmtpd-table-configuration>~ record. + + - ~data~ (default: ~#f~) + + ~data~ expects a list of strings or an alist, which is a list of + cons cells. eg: ~(data (list ("james" . "password")))~ OR + ~(data (list ("gnu.org" "fsf.org")))~. + +- Data Type: opensmtpd-pki-configuration + + This data type represents the configuration of an + ~<opensmtpd-pki-configuration>~. + + - ~domain~ (default ~#f~) + + ~domain~ is the string name of the ~<opensmtpd-pki-configuration>~ record. + + - ~cert~ (default: ~#f~) + + ~cert~ (default: ~#f~) + + ~cert~ is the string certificate filename to use for this pki. + + - ~key~ (default: ~#f~) + + ~key~ is the string certificate falename to use for this pki. + + - ~dhe~ (default: ~"none"~) + + Specify the DHE string parameter to use for DHE cipher suites with host + pkiname. Valid parameter values are "none", "legacy", or "auto". For "legacy", a + fixed key length of 1024 bits is used, whereas for "auto", the key length is + determined automatically. The default is "none", which disables DHE cipher + suites. + +- Data Type: opensmtpd-maildir-configuration + + - ~pathname~ (default: ~"~/Maildir"~) + + Deliver the message to the maildir if pathname if specified, or by default + to =~/Maildir=. + + The pathname may contain format specifiers that are expanded before use + (see FORMAT SPECIFIERS). + + - ~junk~ (default: ~#f~) + + If the junk argument is ~#t~, then the message will be moved to the =‘Junk’= + folder if it contains a positive =‘X-Spam’= header. This folder will be + created under pathname if it does not yet exist. + +- Data Type: opensmtpd-mda-configuration + # Do we need a dataypte for mda configuration? + # this could just be a gexp in the fieldname opensmtpd-configuration-mda + + - ~name~ + + The string name for this MDA command. + + - ~command~ + + Delegate the delivery to a command that receives the message on its standard + input. + + The command may contain format specifiers that are expanded before use (see + FORMAT SPECIFIERS). + +- Data Type: opensmtpd-queue-configuration + + - ~compression~ (default ~#f~) + + Store queue files in a compressed format. This may be useful to save disk + space. + - ~encryption~ (default ~#f~) + + Encrypt queue files with EVP_aes_256_gcm(3). If no key is specified, it is + read with getpass(3). If the string stdin or a single dash (‘-’) is given + instead of a key, the key is read from the standard input. + - ~ttl-delay~ (default ~#f~) + + Set the default expiration time for temporarily undeliverable messages, + given as a positive decimal integer followed by a unit s, m, h, or d. The + default is four days ("4d"). + +- Data Type: opensmtpd-smtp-configuration + + Data type representing an ~<opensmtpd-smtp-configuration>~ record. + + - ~ciphers~ (default: ~#f~) + + Set the control string for SSL_CTX_set_cipher_list(3). The default is + "HIGH:!aNULL:!MD5". + - ~limit-max-mails~ (default: ~100~) + + Limit the number of messages to count for each sessio + - ~limit-max-rcpt~ (default: ~1000~) + + Limit the number of recipients to count for each transaction. + - ~max-message-size~ (default: ~35M~) + + Reject messages larger than size, given as a positive number of bytes or as + a string to be parsed with scan_scaled(3). + - ~sub-addr-delim character~ (default: ~+~) + + When resolving the local part of a local email address, ignore the ASCII + character and all characters following it. This is helpful for email + filters. ="admin+bills@HIDDEN"= is the same email address as + ="admin@HIDDEN"=. BUT an email filter can filter emails addressed to first + email address into a 'Bills' email folder. + +- Data Type: opensmtpd-srs-configuration + + - ~key~ (default: ~#f~) + + Set the secret key to use for SRS, the Sender Rewriting Scheme. + + - ~backup-key~ (default: ~#f~) + + Set a backup secret key to use as a fallback for SRS. This can be used to + implement SRS key rotation. + - ~ttl-delay~ (default: ~"4d"~) + + Set the time-to-live delay for SRS envelopes. After this delay, a bounce + reply to the SRS address will be discarded to limit risks of forged + addresses. + +- Format Specifiers + + Some configuration records support expansion of their parameters at + runtime. Such records (for example + ~<opensmtpd-maildir-configuration>~, ~<opensmtpd-mda-configuration>~) may use + format specifiers which are expanded before delivery or relaying. The + following formats are currently supported: + + |---------------------+-------------------------------------------------------| + | =%{sender}= | sender email address, may be empty string | + | =%{sender.user}= | user part of the sender email address, may be empty | + | =%{sender.domain}= | domain part of the sender email address, may be empty | + | =%{rcpt}= | recipient email address | + | =%{rcpt.user}= | user part of the recipient email address | + | =%{rcpt.domain}= | domain part of the recipient email address | + | =%{dest}= | recipient email address after expansion | + | =%{dest.user}= | user part after expansion | + | =%{dest.domain}= | domain part after expansion | + | =%{user.username}= | local user | + | =%{user.directory}= | home directory of the local user | + | =%{mbox.from}= | name used in mbox From separator lines | + | =%{mda}= | mda command, only available for mda wrappers | + |---------------------+-------------------------------------------------------| + + Expansion formats also support partial expansion using the optional bracket notations + with substring offset. For example, with recipient domain =“example.org”=: + + |------------------------+----------------------| + | =%{rcpt.domain[0]}= | expands to “e” | + | =%{rcpt.domain[1]}= | expands to “x” | + | =%{rcpt.domain[8:]}= | expands to “org” | + | =%{rcpt.domain[-3:]}= | expands to “org” | + | =%{rcpt.domain[0:6]}= | expands to “example” | + | =%{rcpt.domain[0:-4]}= | expands to “example” | + |------------------------+----------------------| + + In addition, modifiers may be applied to the token. For example, with recipient + =“User+Tag@HIDDEN”=: + + |--------------------------+-----------------------------------| + | =%{rcpt:lowercase}= | expands to “user+tag@HIDDEN” | + | =%{rcpt:uppercase}= | expands to “USER+TAG@HIDDEN” | + | =%{rcpt:strip}= | expands to “User@HIDDEN” | + | =%{rcpt:lowercasestrip}= | expands to “user@HIDDEN” | + |--------------------------+-----------------------------------| + + For security concerns, expanded values are sanitized and potentially dangerous + characters are replaced with ‘:’. In situations where they are desirable, the + “raw” modifier may be applied. For example, with recipient + =“user+t?g@HIDDEN”=: + + |---------------+-----------------------------------| + | =%{rcpt}= | expands to “user+t:g@HIDDEN” | + | =%{rcpt:raw}= | expands to “user+t?g@HIDDEN” | + |---------------+-----------------------------------| +*** some example ~<opensmtpd-configurations>~ that are probably out of date + + #+BEGIN_SRC scheme + +;;this works! (opensmtpd-configuration->mixed-text-file (opensmtpd-configuration (smtp (opensmtpd-smtp-configuration (limit-max-rcpt 10))))) + +;; (tables (list +;; (opensmtpd-table-configuration +;; (name "aliases") +;; (data +;; (list +;; (cons "webmaster" "root") +;; (cons "postmaster" "root") +;; (cons "abuse" "root")))) +;; +;; (opensmtpd-table-configuration +;; (name "vdoms") +;; (data (list "gnucode.me" +;; "gnu-hurd.com"))) +;; (opensmtpd-table-configuration +;; (name (opensmtpd-table-configuration +;; (name "virtual") +;; (data (list "root" "postmaster@HIDDEN")))) +;; (data (list (cons "joshua@HIDDEN" "joshua") +;; (cons "jbranso@HIDDEN" "joshua") +;; (cons "postmaster@HIDDEN" "joshua")))))) + + ;; (filter-chains + ;; (list + ;; (opensmtpd-filter-chain + ;; (name "dropDumbEmails") + ;; (filter-names (list "nofcrdnsDisconnect" + ;; "nordnsDisconnect"))))) + ;; (filter-phases + ;; (list (opensmtpd-filter-phase-configuration + ;; (name "nofcrdnsDisconnect") + ;; (phase-name "connect") + ;; (options (list "!fcrdns")) + ;; (decision "disconnect") + ;; (message "You have not set up forward confirmed DNS.")) + ;; (opensmtpd-filter-phase-configuration + ;; (name "nordnsDisconnect") + ;; (phase-name "connect") + ;; (options (list "!rdns")) + ;; + ;; (decision "reject") + ;; (message "You have not set up reverse DNS.")))) + ;; +(define example-opensmtpd-config-smaller + (opensmtpd-configuration + (listen-ons + (list + ;; this forum help suggests that I listen on 0.0.0.0 and NOT eth0 + ;; https://serverfault.com/questions/726795/opensmtpd-wont-work-at-reboot + ;; this listens for email from the outside world + ;; this lets local users logged into the system via ssh send email + (opensmtpd-listen-on-configuration + (interface "wlp2s0") + (port 465)))) + (matches (list + (opensmtpd-match-configuration + (name "maildir") + (action (opensmtpd-action-local-delivery-configuration + (method (opensmtpd-maildir-configuration + (pathname "/home/%{rcpt.user}/Maildir") + (junk #t))) + (virtual (opensmtpd-table-configuration + (name "virtual") + (data (list "root" "james@HIDDEN")))))) + (for (opensmtpd-option-configuration + (option "for local")))))))) + +(define example-opensmtpd-config-small + (let ([interface "wlp2s0"] + [creds (opensmtpd-table-configuration + (name "creds") + (data + (list + (cons "joshua" + "$6$Ec4m8FgKjT2F/03Y$k66ABdse9TzCX6qaALB3WBL9GC1rmAWJmaoSjFMpbhzat7DOpFqpnOwpbZ34wwsQYIK8RQlqwM1I/v6vsRq86."))))] + [receive-action (opensmtpd-action-local-delivery-configuration + (name "receive") + (method (opensmtpd-maildir-configuration + (pathname "/home/%{rcpt.user}/Maildir") + (junk #t))) + (virtual (opensmtpd-table-configuration + (name "virtual") + (data (list "root" "james@HIDDEN")))))] + [smtp.gnucode.me (opensmtpd-pki-configuration + (domain "smtp.gnucode.me") + (cert "opensmtpd.scm") + (key "opensmtpd.scm"))]) + (opensmtpd-configuration + (listen-ons + (list + ;; this forum help suggests that I listen on 0.0.0.0 and NOT eth0 + ;; https://serverfault.com/questions/726795/opensmtpd-wont-work-at-reboot + ;; this listens for email from the outside world + (opensmtpd-listen-on-configuration + (interface interface) + (port 25) + (secure-connection "tls") + (pki smtp.gnucode.me)) + ;; this lets local users logged into the system via ssh send email + (opensmtpd-listen-on-configuration + (interface interface) + (port 465) + (secure-connection "smtps") + (pki smtp.gnucode.me) + (auth creds)))) + (matches (list + (opensmtpd-match-configuration + (action receive-action) + (for (opensmtpd-option-configuration + (option "for local"))))))))) + +(define example-opensmtpd-config + (let ([interface "lo"] + [creds (opensmtpd-table-configuration + (name "creds") + (data + (list + (cons "joshua" + "$6$Ec4m8FgKjT2F/03Y$k66ABdse9TzCX6qaALB3WBL9GC1rmAWJmaoSjFMpbhzat7DOpFqpnOwpbZ34wwsQYIK8RQlqwM1I/v6vsRq86."))))] + [receive-action (opensmtpd-action-local-delivery-configuration + (name "receive") + (method (opensmtpd-maildir-configuration + (pathname "/home/%{rcpt.user}/Maildir") + (junk #t))) + (virtual (opensmtpd-table-configuration + (name "virtual") + (data (list "josh" "jbranso@HIDDEN")))))] + [smtp.gnucode.me (opensmtpd-pki-configuration + (domain "smtp.gnucode.me") + (cert "opensmtpd.scm") + (key "opensmtpd.scm"))]) + (opensmtpd-configuration + ;; (mta-max-deferred 50) + ;; (queue + ;; (opensmtpd-queue-configuration + ;; (compression #t))) + ;; (smtp + ;; (opensmtpd-smtp-configuration + ;; (max-message-size "10M"))) + ;; (srs + ;; (opensmtpd-srs-configuration + ;; (ttl-delay "5d"))) + (listen-ons + (list + ;; this forum help suggests that I listen on 0.0.0.0 and NOT eth0 + ;; https://serverfault.com/questions/726795/opensmtpd-wont-work-at-reboot + ;; this listens for email from the outside world + (opensmtpd-listen-on-configuration + (interface interface) + (port 25) + (secure-connection "tls") + (pki smtp.gnucode.me)) + ;; this lets local users logged into the system via ssh send email + (opensmtpd-listen-on-configuration + (interface "lo") + (port 25) + (secure-connection "tls") + (pki smtp.gnucode.me)) + (opensmtpd-listen-on-configuration + (interface interface) + (port 465) + (secure-connection "smtps") + (pki smtp.gnucode.me) + (auth creds) + ;;(filter ) + ) + (opensmtpd-listen-on-configuration + (interface interface) + (port 587) + (secure-connection "tls-require") + (pki smtp.gnucode.me) + (auth creds)))) + (matches (list + (opensmtpd-match-configuration + (action (opensmtpd-action-relay-configuration + (name "send"))) + (for (opensmtpd-option-configuration + (option "for any"))) + (from (opensmtpd-option-configuration + (option "from any"))) + (auth (opensmtpd-option-configuration + (option "auth")))) + (opensmtpd-match-configuration + (action receive-action) + (from (opensmtpd-option-configuration + (option "from any"))) + (for (opensmtpd-option-configuration + (option "for domain") + (value (list "gnucode.me" "gnu-hurd.com"))))) + (opensmtpd-match-configuration + (action receive-action) + (for (opensmtpd-option-configuration + (option "for local"))))))))) + #+END_SRC + +*** some example smtpd.conf configs +*** serving multiple domains with one pki + +source: https://www.reddit.com/r/openbsd/comments/n41wkz/how_to_host_different_domains_for_an_email_server/ +#+BEGIN_EXAMPLE +pki mail.primary.domain certpki mail.primary.domain cert "/etc/ssl/mail.primary.domain.fullchain.pem" + +pki mail.primary.domain key "/etc/ssl/private/mail.primary.domain.key" + + +filter check_dyndns phase connect match rdns regex { '.*\.dyn\..*', '.*\.dsl\..*' } \ + +disconnect "550 no residential connections" + + +filter check_rdns phase connect match !rdns \ + +disconnect "550 no rDNS is so 80s" + + +filter check_fcrdns phase connect match !fcrdns \ + +disconnect "550 no FCrDNS is so 80s" + + +filter senderscore \ + +proc-exec "filter-senderscore -blockBelow 10 -junkBelow 70 -slowFactor 5000" + + +filter rspamd proc-exec "filter-rspamd" + + +table usermap file:/etc/mail/usermap + +table credentials file:/etc/mail/credentials + +table domains { primary.domain, second.domain } + + +listen on all tls pki mail.primary.domain \ + +filter { check_dyndns, check_rdns, check_fcrdns, senderscore, rspamd } + + +listen on egress port 465 smtps pki mail.primary.domain \ + +auth ~<credentials>~ filter rspamd + + +action "inbound" lmtp "/var/dovecot/lmtp" rcpt-to virtual ~<usermap>~ #maildir junk alias <aliases> + +action "outbound" relay helo mail.primary.domain + + +match from any for domain ~<domains>~ action "inbound" + +match for local action "inbound" + + +match from any auth for any action "outbound" + +match for any action "outbound" "/etc/ssl/mail.primary.domain.fullchain.pem" + +pki mail.primary.domain key "/etc/ssl/private/mail.primary.domain.key" + + +filter check_dyndns phase connect match rdns regex { '.*\.dyn\..*', '.*\.dsl\..*' } \ + +disconnect "550 no residential connections" + + +filter check_rdns phase connect match !rdns \ + +disconnect "550 no rDNS is so 80s" + + +filter check_fcrdns phase connect match !fcrdns \ + +disconnect "550 no FCrDNS is so 80s" + + +filter senderscore \ + +proc-exec "filter-senderscore -blockBelow 10 -junkBelow 70 -slowFactor 5000" + + +filter rspamd proc-exec "filter-rspamd" + + +table usermap file:/etc/mail/usermap + +table credentials file:/etc/mail/credentials + +table domains { primary.domain, second.domain } + + +listen on all tls pki mail.primary.domain \ + +filter { check_dyndns, check_rdns, check_fcrdns, senderscore, rspamd } + + +listen on egress port 465 smtps pki mail.primary.domain \ + +auth ~<credentials>~ filter rspamd + + +action "inbound" lmtp "/var/dovecot/lmtp" rcpt-to virtual ~<usermap>~ #maildir junk alias <aliases> + +action "outbound" relay helo mail.primary.domain + + +match from any for domain ~<domains>~ action "inbound" + +match for local action "inbound" + + +match from any auth for any action "outbound" + +match for any action "outbound" + +#+END_EXAMPLE +** PROJ nice things to have [0/9] +*** TODO Should I delete ~<opensmtpd-mda-configuration>~ ? or fieldname 'opensmtpd-configuration-mda-wrapppers'? + +~<opensmtpd-action-local-delivery-configuration>~'s fieldname 'method' allows +for an mda configuration. BUT instead of an mda-configuration record, you could +just use a list of strings and/or gexps. + +#+BEGIN_EXAMPLE + mda wrapper name command + Associate command with the mail delivery agent wrapper named name. When a local + delivery specifies a wrapper, the command associated with the wrapper will be ex‐ + ecuted instead. The command may contain format specifiers (see FORMAT + SPECIFIERS). +#+END_EXAMPLE + +If I choose to NOT delete ~<opensmtpd-mda-configuration>~, then should I delete +'opensmtpd-configuration-mda-wrapppers'? + +Also should I delete the opensmtpd-action-local-delivery-configuration-wrapper? + +*** TODO make the 'auth-optional' and 'auth' fieldnames for ~<opensmtpd-listen-on-configuration>~ autoencrypt passwords. [0/0] +Guix makes it pretty hard to find the openbsd binary file that encrypts +passwords for you. If I can progmatically find this file, it would be nice to +autoencrypt the users's passwords for you. + +What does this mean practically? + +#+BEGIN_SRC scheme +(opensmtpd-table-configuration + (name "credentials") + (data '(("joshua@HIDDEN" . "somePassword") + ("postmaster@HIDDEN") . "anotherSillyPassword"))) +#+END_SRC + +Gets stored in /gnu/store/ in the =smtpd.conf= as something like: + +#+BEGIN_EXAMPLE +table credentials { joshua@HIDDEN = $some$Long$EncrytpedPassword, \ + postmaster@HIDDEN = $some$Long$Other$EncrytpedPassword } +#+END_EXAMPLE + +You would need to encourage users NOT to have passwords in a public git repo. +With guile-git, it might be possible to sanitize the config, to ensure that the +passwords are NOT stored in the git repo. + +Alternatively, we could put the following in the documentation: + +#+BEGIN_SRC scheme +(use-modules (passwords)) + +(opensmtpd-table-configuration + (name "credentials") + (data %passwords)) +#+END_SRC + +*** PROJ Why does (opensmtpd-configuration) take so long to initialize? [0/1] + +For example, try to initialize this bit of code. It takes almost 5 seconds. +#+BEGIN_SRC scheme +(let ([interface "lo"] + [creds-table (opensmtpd-table-configuration + (name "creds") + (data + (list + (cons "joshua" + "$6$Ec4m8FgKjT2F/03Y$k66ABdse9TzCX6qaALB3WBL9GC1rmAWJmaoSjFMpbhzat7DOpFqpnOwpbZ34wwsQYIK8RQlqwM1I/v6vsRq86."))))] + [receive-action (opensmtpd-action-local-delivery-configuration + (name "receive") + (method (opensmtpd-maildir-configuration + (pathname "/home/%{rcpt.user}/Maildir") + (junk #t))) + (virtual (opensmtpd-table-configuration + (name "virtual") + (data (list "josh" "jbranso@HIDDEN")))))] + [filter-dkimsign (opensmtpd-filter + (name "dkimsign") + (exec #t) + (proc (string-append "/path/to/dkimsign -d gnucode.me -s 2021-09-22 -c relaxed/relaxed -k " + "/path/to/dkimsign-key user nobody group nobody")))] + [smtp.gnucode.me (opensmtpd-pki-configuration + (domain "smtp.gnucode.me") + (cert "opensmtpd.scm") + (key "opensmtpd.scm"))]) + (opensmtpd-configuration + (mta-max-deferred 50) + (queue + (opensmtpd-queue-configuration + (compression #t))) + (smtp + (opensmtpd-smtp-configuration + (max-message-size "10M"))) + (srs + (opensmtpd-srs-configuration + (ttl-delay "5d"))) + (listen-ons + (list + (opensmtpd-listen-on-configuration + (interface interface) + (port 25) + (secure-connection "tls") + (filters (list (opensmtpd-filter-phase-configuration + (name "noFRDNS") + (phase "commit") + (options (list (opensmtpd-option-configuration + (option "fcrdns") + (not #t)))) + (decision "disconnect") + (message "No FCRDNS")))) + (pki smtp.gnucode.me)) + ;; this lets local users logged into the system via ssh send email + (opensmtpd-listen-on-configuration + (interface interface) + (port 465) + (secure-connection "smtps") + (pki smtp.gnucode.me) + (auth creds-table) + (filters (list filter-dkimsign))) + (opensmtpd-listen-on-configuration + (interface interface) + (port 587) + (secure-connection "tls-require") + (pki smtp.gnucode.me) + (auth creds-table) + (filters (list filter-dkimsign))))) + (matches (list + (opensmtpd-match-configuration + (action (opensmtpd-action-relay-configuration + (name "relay"))) + (for (opensmtpd-option-configuration + (option "for any"))) + (from (opensmtpd-option-configuration + (option "from any"))) + (auth (opensmtpd-option-configuration + (option "auth")))) + (opensmtpd-match-configuration + (action receive-action) + (from (opensmtpd-option-configuration + (option "from any"))) + (for (opensmtpd-option-configuration + (option "for domain") + (value (opensmtpd-table-configuration + (name "domain-table") + (data (list "gnucode.me" "gnu-hurd.com"))))))) + (opensmtpd-match-configuration + (action receive-action) + (for (opensmtpd-option-configuration + (option "for local")))))))) +#+END_SRC +**** TODO one area to look for speed up improvements would be in the sanitize function of =(opensmtpd-listen-on-configuration-filters)=. +*** PROJ check the code base for places to use apply, map, fold, eval, or remove [3/4] + +**** DONE string-in-list would be a good place. maybe is-value-right-type + +#+BEGIN_SRC scheme +(define (string-in-list? string list) + (if (null? list) + #f + (if (and (string? (car list)) (string=? string (car list))) + #t + (string-in-list? string (cdr list))))) + +(define (string-in-list? string list) + (primitive-eval (cons 'or (map (lambda (var) (string=? string var)) list)))) + +#+END_SRC + +**** DONE contains-duplicate +#+BEGIN_SRC scheme +(define (contains-duplicate? list) + (if (null? list) + #f + (or + ;;<check whether (first list) is in (rest list)> + (let loop ([list (cdr list)] + [1st (car list)]) + (if (null? list) + #f + (if (equal? 1st (car list)) + (data #t 1st) + (loop (cdr list) 1st)))) + ;;<check where (rest list) contains a duplicate> + (contains-duplicate? (cdr list))))) + +(define (contains-duplicate? list) + (if (null? list) + #f + (or (primitive-eval (cons 'or ; check if (car list) is in (cdr list) + (map (lambda (var) (equal? var (car list))) + (cdr list)))) + ;; check if (cdr list) contains duplicate + (contains-duplicate? (cdr list))))) +#+END_SRC + +**** DONE using remove and flatten and map +#+BEGIN_SRC scheme +(define (get-opensmtpd-table-configurations value) + (delete-duplicates + (let loop ([list (flatten + (cond ((opensmtpd-table-configuration? value) + value) + ((record? value) + (let* ([<record-type> (record-type-descriptor value)] + [list-of-record-fieldnames (record-type-fields <record-type>)]) + (map (lambda (fieldname) + (get-opensmtpd-table-configurations ((record-accessor <record-type> fieldname) value))) + list-of-record-fieldnames))) + ((and (list? value) (not (null? list))) + (map (lambda (element-in-list) + (if (record? element-in-list) + (get-opensmtpd-table-configurations element-in-list) + #f)) + value))))]) + (if (null? list) + '() + (if (opensmtpd-table-configuration? (car list)) + (cons (car list) (loop (cdr list))) + (loop (cdr list))))))) + +(define (get-opensmtpd-table-configurations value) + (let loop ([list (flatten ;; turn (list '(1) '(2 '(3))) -> '(1 2 3) + (cond ((opensmtpd-table-configuration? value) + value) + ((record? value) + (let* ([<record-type> (record-type-descriptor value)] + [list-of-record-fieldnames (record-type-fields <record-type>)]) + (map (lambda (fieldname) + (get-opensmtpd-table-configurations ((record-accessor <record-type> fieldname) value))) + list-of-record-fieldnames))) + ((and (list? value) (not (null? list))) + (map (lambda (element-in-list) + (if (record? element-in-list) + (get-opensmtpd-table-configurations element-in-list) + #f)) + value))))]) + (delete-duplicates (partition opensmtpd-table-configuration? list)))) + +#+END_SRC +**** TODO using map, apply, and fold is certainly awesome, but is it less efficient? + +For example, list-of-type? using a named let is pretty efficient. It loops +through the list once. + +#+BEGIN_SRC scheme +(define (list-of-type? list proc?) + (if (and (list? list) + (not (null? list))) + (let loop ([list list]) + (if (null? list) + #t + (if (proc? (car list)) + (loop (cdr list)) + #f))) + #f)) +#+END_SRC + +BUT when I using map on this, it is slightly less efficient. It has to apply a +simple procedure to each element in the list. Then it has to return the list of +booleans. Then it has to build the primitive eval list, then it has to eval it. +#+BEGIN_SRC scheme +(define (list-of-type? list proc?) + (if (and (list? list) + (not (null? list))) + (primitive-eval (cons 'and + (map (lambda (var) + (if (proc? var) + #t + #f)) + list))) + #f)) +#+END_SRC + +*** PROJ improve <opensmtpd-table-configuration> [0/2] +**** TODO it would be nice if ~<opensmtpd-table-configuration>~ supported aliasing tables, as described in man 5 table + +#+BEGIN_EXAMPLE + Aliasing tables + Aliasing tables are mappings that associate a recipient to one or many destinations. They can be + used in two contexts: primary domain aliases and virtual domain mapping. + + action name method alias <table> + action name method virtual <table> + + In a primary domain context, the key is the user part of the recipient address, whilst the value + is one or many recipients as described in aliases(5): + + user1 otheruser + user2 otheruser1,otheruser2 + user3 otheruser@HIDDEN + + In a virtual domain context, the key is either a user part, a full email address or a catch all, + following selection rules described in smtpd.conf(5), and the value is one or many recipients as + described in aliases(5): + + user1 otheruser + user2@HIDDEN otheruser1,otheruser2 + @example.org otheruser@HIDDEN + @ catchall@HIDDEN + +#+END_EXAMPLE + +Currently opensmtpd-table-configuration, does not support mapping a user to 5 email addresses. +For example, if user 'dave' can email as 'postmaster@HIDDEN', and +'other@HIDDEN', and 5 other email addresses...<opensmtpd-table-configuration> does not +support this kind of mapping. To support it, I may be able to just embed a +table in smtpd.conf, or I may need to create an /etc/aliases table as man 5 +aliases describes. +**** TODO make an <opensmtpd-table-configuration> with file-db #t, auto convert the table into a berkley database via makemap + +See man 5 table +and man smtpd.conf +*** TODO writing out the pkis when there are no pkis gives the string "\n"...it might be better to give "" instead + +=(opensmtpd-configuration-fieldname->string example-opensmtpd-with-0-pkis opensmtpd-configuration-pkis opensmtpd-pki-configuration->string)= +*** PROJ Can I make some improvements to my/sanitize procedure? [0/5] +**** how does my/sanitize procedure work? + +~(my/sanitize var "record-name" "fieldname" '(string? boolean? number?)')~ + +It is essentially asking? are you any of the following: string?, boolean?, +number? If not, then error out with a helpful error message. + +**** How does my hard-coded sanitized procedure work? eg [[file:opensmtpd-records.scm::(filters opensmtpd-listen-on-configuration-filters][opensmtpd-listen-on-configuration-filters]] + +This hard coded sanitize is a little different than the my/sanitize procedure. I +designed the thunk ~my/sanitize~, such that each thunk (string?, false?, +boolean?) has a corresponding entry in the procedure [[file:opensmtpd-records.scm::define (list-of-procedures->string + procedures][~(list-of-procedures->string procedures)~]]. + +However, it would be nice to have the sanitize invocation in +opensmtpd-listen-on-configuration-filters use a my/sanitize invocation like so. + +#+BEGIN_SRC scheme +(my/sanitize var "opensmtpd-listen-on-configuration" "filters" + (list false? + '(list-has-duplicates-or-non-filters + "is a list in which each unique element is of type <opensmtpd-filter-configuration>\n" + "or <opensmtpd-filter-phase-configuration>.") + '(some-filters-in-list-need-message? + "<opensmtpd-filter-phase-configuration> fieldname: 'decision' options " + "\"disconnect\" and \"reject\" require fieldname 'message'\n" + "to have a string.\n") + '(some-filters-in-list-need-value? + "<opensmtpd-filter-phase-configuration> fieldname: 'decision' option " + "\"rewrite\" requires fieldname 'value'\n" + "to have a string.\n"))) +#+END_SRC + +**** PROJ better error messages for my/sanitize calls that use a lambda instead of a defined function [0/4] + +THIS IS HARD TO DO... NOT DOING IT! I just chose to use a hard-coded error +message baked into the lambda. I tried making my/sanitize better...but I could +not get it to work. The hard-coded method just works: + +#+BEGIN_SRC scheme +(phase-name opensmtpd-filter-phase-configuration-phase-name ;; string + (default #f) + (sanitize (lambda (var) + (if (and (string? var) + (or (string=? "connect" var) + (string=? "helo" var) + (string=? "mail-from" var) + (string=? "rcpt-to" var) + (string=? "data" var) + (string=? "commit" var))) + var + (begin + (display (string-append "<opensmtpd-filter-phase-configuration> fieldname: 'phase-name' is of type " + "string. The string can be either 'connect'," + " 'helo', 'mail-from', 'rcpt-to', 'data', or 'commit.'\n ")) + (throw 'bad! var)))))) +#+END_SRC + +Why? ~<opensmtpd-filter-phase-configuration>~ fieldnames only accept certain strings. I want to +sanitize each fieldname to make sure that it's strings is one of those strings. +How would I do this? + +For example, ~<opensmtpd-filter-phase-configuration>~ fieldname 'decision' uses +a lambda to sanitize itself. This will result in an error message that is +descriptive enough to solve the problem. If I decide to do this, then I probably +should create a non-exported record. + +#+BEGIN_SRC scheme +(opensmtpd-filter-phase-configuration (name "cat") (phase-name "connect") (options "fcrdns") (decision "bypasse")) +#+END_SRC + +#+RESULTS: +: (opensmtpd-filter-phase-configuration (name "cat") (phase-name "connect") (options "fcrdns") (decision "bypasse")) +: <opensmtpd-filter-phase> fieldname 'bypasse' is of type. +: #<procedure 3ec75a8 at <unknown port>:972:0 (var)> +: ice-9/boot-9.scm:1685:16: In procedure raise-exception: +: Throw to key `bad!' with args `(#<procedure 3ec75a8 at <unknown port>:972:0 (var)>)'. + +A solution may be to modify the my/sanitize procedure to accept something like + +#+BEGIN_SRC scheme +(my/sanitize var "<record>" "'fieldname'" (list ((lambda (var) ...) . "list of unique numbers or strings"))) +#+END_SRC + +I have some example code [[file:opensmtpd-records.scm::;; TODO add in some code that accepts a (cons . cell) that of (proc . "error message") here.][here.]] It probably won't work, but it is a rough sketch +of what could work. +#+BEGIN_SRC scheme +[(eq? (cons? (car procedures))) +(cdr (car procedures))] +#+END_SRC + +Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue. + +***** TODO Now make all the other sanitize sections that use a lambda use this new functionality: + +eg: + +#+BEGIN_SRC scheme +(family opensmtpd-listen-on-configuration-family + (default #f) + (sanitize (lambda (var) + (cond + [(eq? #f var) ;; var == #f + var] + [(and (string? var) + (or (string=? "inet4" var) + (string=? "inet6" var))) + var] + [else + (begin + (display "<opensmtpd-listen-on-configuration> fieldname 'family' must be string \"inet4\" or \"inet6\".\n") + (throw 'bad! var))])))) +#+END_SRC + +***** TODO perhaps I can create an unexported record <sanitize-proc-configuration> [0/2] + +fieldnames: 'procedure', 'error message'. +***** TODO Perhaps I could try to make my/sanitize work more like the opensmtpd-listen-on-configuration-filters does. + +It looks like tiny errors first. And shows you those relevent errors. When you +fix those tiny errors it starts looking for harder errors. + +This is nice because when you get something wrong in the config, you get the +specific error message. + +The way my/sanitize currently works, if you get one thing wrong, then you get 4 +reasons for what you might have done wrong. +***** TODO the <opensmtpd-match-configuration> has lost of hard coded error checking. +It would be nice to hook this up to my/sanitize. Along with other bits of the code. +**** TODO rework my/sanitize to be like opensmtpd-listen-on-configuration-filters + +opensmtpd-listen-on-configuration-filters works like so: + +Is the variable (not (false? var)) + +Is the variable (need-some-messages?) + +Does the variable (need-some-value) ? + +else var. + +For example, this +#+BEGIN_SRC scheme +(define-record-type* <opensmtpd-table-configuration> + opensmtpd-table-configuration make-opensmtpd-table-configuration + opensmtpd-table-configuration? + this-record + (name opensmtpd-table-configuration-name ;; string + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-table-configuration" "name" (list string?))))) + (file-db opensmtpd-table-configuration-file-db + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-table-configuration" "file-db" + (list boolean?))))) +#+END_SRC + +would become: +#+BEGIN_SRC scheme +(define-record-type* <opensmtpd-table-configuration> + opensmtpd-table-configuration make-opensmtpd-table-configuration + opensmtpd-table-configuration? + this-record + (name opensmtpd-table-configuration-name ;; string + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-table-configuration" "name" (list not-string?))))) + (file-db opensmtpd-table-configuration-file-db + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-table-configuration" "file-db" + (list not-boolean?))))) +#+END_SRC + +This +#+BEGIN_SRC scheme +(secure-connection opensmtpd-listen-on-configuration-secure-connection + (default #f) + (sanitize (lambda (var) + (cond [(boolean? var) + var] + [(and (string? var) + (string-in-list? var + (list "smtps" "tls" + "tls-require" + "tls-require-verify"))) + var] + [else + (begin + (display (string-append "<opensmtd-listen-on> fieldname 'secure-connection' can be " + "one of the following strings: \n'smtps', 'tls', 'tls-require', " + "or 'tls-require-verify'.\n")) + (throw 'bad! var))])))) +#+END_SRC + +would become (finish this thought exercise.) +#+BEGIN_SRC scheme +(secure-connection opensmtpd-listen-on-configuration-secure-connection + (default #f) + (sanitize (lambda (var) + (list not-boolean + (sanitize-proc-configuration + string-in-list? + (list "smtps" "tls" + "tls-require" + "tls-require-verify")) + [else + (begin + (display (string-append "<opensmtd-listen-on> fieldname 'secure-connection' can be " + "one of the following strings: \n'smtps', 'tls', 'tls-require', " + "or 'tls-require-verify'.\n")) + (throw 'bad! var))])))) +#+END_SRC + +**** PROJ can we merge my/sanitize syntax into (guix records)? + +#+BEGIN_SRC scheme +(define-record-type* <opensmtpd-option-configuration> + opensmtpd-option-configuration make-opensmtpd-option-configuration + opensmtpd-option-configuration? + (documentation (list "<opensmtpd-match-configuration> uses <opensmtpd-option-configuration> to\n" + "tweak various options.")) + (sanitize (sanitize-configuration ; this sanitizes the whole <opensmtpd-option-configuration> record. + (list (lambda (value) + ... + )))) + (option opensmtpd-option-configuration-option + (default #f) + (sanitize (sanitize-configuration + (list string?)))) + (not opensmtpd-option-configuration-not + (default #f) + (sanitize (sanitize-configuration + (list boolean?)) )) + (regex opensmtpd-option-configuration-regex + (default #f) + (sanitize (sanitize-configuration '(boolean?)))) + (value opensmtpd-option-configuration-value + (default #f) + (sanitize (sanitize-configuration + ;; note that it is smart enough to realize that opensmtpd-table-configuration? is a record, + ;; so the error message it returns is something like "<opensmtpd-match-configuration> fieldname is of + ;; type <opensmtpd-table-configuration>." + (list false? string? opensmtpd-table-configuration?))))) +#+END_SRC +**** TODO maybe sanitize the ~<opensmtpd-configuration>~ fieldname 'matches' better + +#+BEGIN_SRC scheme +(opensmtpd-configuration (matches (list + (opensmtpd-match-configuration + (action + (opensmtpd-action-relay-configuration + (name "relay")))) + 345 + (opensmtpd-match-configuration + (for (opensmtpd-option-configuration + (option "for local"))) + (action + (opensmtpd-action-relay-configuration + (name "relay"))))))) +#+END_SRC + +#+BEGIN_EXAMPLE +<opensmtpd-configuration> fieldname: 'matches' is of type a list of unique <opensmtpd-match-configuration> records. + +ice-9/boot-9.scm:1685:16: In procedure raise-exception: +Throw to key `bad!' with args `((#<<opensmtpd-match-configuration> action: #<<opensmtpd-action-relay-configuration> name: "relay" backup: #f backup-mx: #f helo: #f domain: #f host: #f pki: #f srs: #f tls: #f protocols: #f ciphers: #f auth: #f mail-from: #f src: #f> for: #f from: #f auth: #f helo: #f mail-from: #f rcpt-to: #f tag: #f tls: #f> 345 #<<opensmtpd-match-configuration> action: #<<opensmtpd-action-relay-configuration> name: "relay" backup: #f backup-mx: #f helo: #f domain: #f host: #f pki: #f srs: #f tls: #f protocols: #f ciphers: #f auth: #f mail-from: #f src: #f> for: #<<opensmtpd-option-configuration> option: "for local" not: #f regex: #f value: #f> from: #f auth: #f helo: #f mail-from: #f rcpt-to: #f tag: #f tls: #f>))'. + +Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue. +scheme@(opensmtpd-records) [24]> ,bt +In current input: + 2358:0 2 (_) +In /home/joshua/prog/gnu/guix/guix-config/linode-guix-system-configuration/opensmtpd-records.scm: + 278:8 1 (my/sanitize (#<<opensmtpd-match-configuration> action: #<<opensmtpd-action-relay-configuration> name: "relay" backup: #f backup-mx: #f helo: #f domain…> …) …) +In ice-9/boot-9.scm: + 1685:16 0 (raise-exception _ #:continuable? _) +#+END_EXAMPLE + +It is not obvious from the error message what is wrong. The error message +should say + +#+BEGIN_SRC org +~<opensmtpd-configuration>= fieldname 'matches' is a list of unique =<opensmtpd-match-configuration>~. One of the items in the list +is '345'. +Throw to key `bad! with args 345 +#+END_SRC + +Alternatively, we could define a guix specific record printer to make it easier +to see the problem. + +#+BEGIN_SRC scheme +<opensmtpd-configuration> fieldname: 'matches' is of type a list of unique <opensmtpd-match-configuration> records. + +ice-9/boot-9.scm:1685:16: In procedure raise-exception: +Throw to key `bad!' with args: +`(list + (opensmtpd-match-configuration + (action + (opensmtpd-action-relay-configuration + (name "relay")))) + 345 + (opensmtpd-match-configuration + (for (opensmtpd-option-configuration + (option "for local"))) + (action + (opensmtpd-action-relay-configuration + (name "relay")))))'. + +Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue. +scheme@(opensmtpd-records) [24]> ,bt +In current input: + 2358:0 2 (_) +In /home/joshua/prog/gnu/guix/guix-config/linode-guix-system-configuration/opensmtpd-records.scm: + 278:8 1 (my/sanitize (#<<opensmtpd-match-configuration> action: #<<opensmtpd-action-relay-configuration> name: "relay" backup: #f backup-mx: #f helo: #f domain…> …) …) +In ice-9/boot-9.scm: + 1685:16 0 (raise-exception _ #:continuable? _) + +#+END_SRC +**** TODO some of the error messages say "bad var #f". This is not very helpful. + +Where it is useful I should do a ~(throw 'bad! record)~ instead of +~(throw `bad! #f)~ +*** PROJ add support for ~<listen-on>= fieldname 'senders': syntax "senders =<users>~ [masquerade]" [0/4] +:LOGBOOK: +- State "TODO" from [2021-11-02 Tue 04:08] +:END: +**** TODO add a record type <opensmtpd-senders-configuration> + +fieldnames: 'table (accepts <opensmtpd-table-configuration>'), and 'masquerade' (accepts boolean). +**** TODO change the sanitize portion of the fieldname 'senders' in the <opensmtpd-listen-on-configuration> + +The below code does work in a REPL. +#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/ +(add-to-load-path (dirname (current-filename))) +(use-modules (opensmtpd-records)) + +(opensmtpd-listen-on-configuration + (auth + (opensmtpd-table-configuration + (name "My-table") + (data '(("joshua" . "$some$Long$EncrytpedPassword")))))) +#+END_SRC + +#+RESULTS: + +AND the below code will correctly result in an error! +#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/ +(add-to-load-path (dirname (current-filename))) +(use-modules (opensmtpd-records)) + +(opensmtpd-listen-on-configuration + (auth + (opensmtpd-table-configuration + (name "My-table") + (data '("joshua" "$some$Long$EncrytpedPassword"))))) +#+END_SRC + +#+RESULTS: +: ~<opensmtpd-listen-on-configuration>= fieldname: 'auth' is of type boolean, or an =<opensmtpd-table-configuration>~ record whose fieldname 'values' are an assoc-list +: (eg: (opensmtpd-table-configuration (name "table") (data '("joshua" . "$encrypted$password")))). + +**** TODO change relevant portions in opensmtpd-listen-on-configuration->string +This bit of code works in the repl too! +#+BEGIN_SRC scheme :dir ~/prog/gnu/guix-config/linode-guix-system-configuration/ +(add-to-load-path (dirname (current-filename))) +(use-modules (opensmtpd-records)) + +((@@ (opensmtpd-records) opensmtpd-listen-on-configuration->string) + (opensmtpd-listen-on-configuration + (auth + (opensmtpd-table-configuration + (name "credentials") + (data '(("joshua" . "$someLongEncrytpedPassword"))))))) +#+END_SRC + +**** TODO support the masquerade option + +Right now, senders just accepts an <opensmtpd-table-configuration>, but I am not allowing the +user to turn on or off the masquerade option. +*** TODO write out of examples of ~<opensmtpd-configuration>~ records that will fail to start or do not make sense. +Provide appropriate error messages. + +There is a trend of guix services that "work" but are not dummy proof. For +example, the XMPP service wants a cert in the format of "</path/to/cert.key". If +you omit that leading "<", then the service will not start, and you will NOT +have any error messages telling you why. It's not easy to figure out what went +wrong with the service. + +Shepherd will not tell where the XMPP configuration file can be found. You have +to manually go searching for the config file. Then you have to manually check +the configuration syntax of the file, though the command to start the service +may have a flag to check the syntax. Anyway it's annoying. Guix services should +be able to get a ~<service-configuration>~ record and just by looking at the +record tell, if you have done something silly that will make the service refuse +to start or behave in a weird way, and provide you appropriate error messages so +you don't have to go syntax hunting. + +Examples: + +- could be a filter that is defined but never used, which won't +be possible once [[id:89603b3f-7580-4531-8aee-2c115c97adfe][remove opensmtpd-configuration-filters]] is done. + +- (listen-on (interface "doesNotExist")) + +- (smtp-configuration (smtp-max-message-size "10G")) Are you sure you + want emails that large? + +- (pki (domain "name") (key "notAKeyfile.txt") (cert + "notACertFile.txt") + +- (ca (file "NotACaFile.txt")) + +- (opensmtpd-filter-phase-configuration (name "filter") (phase "helo") (decision "bypass")) + There is no fieldname =options= here. This has to be sanitized by + ~<opensmtpd-filter-phase-configuration>~'s fieldname 'filters'. + + +#+BEGIN_SRC scheme +(opensmtpd-listen-on-configuration + (filters + (list + (opensmtpd-filter-phase-configuration + (name "noFRDNS") + (phase "commit") + (options (list (opensmtpd-option-configuration + (option "fcrdns") + ))) + (decision "junk"))))) +#+END_SRC +* Some notes on working on the service workflows and such + +disabling centaur-tabs-mode seems to help. and NOT working in the console helps too. + +I think that having the geiser repl running via + +M-x geiser +M-x geiser-load-file RET opensmtpd-records.scm +,m (opensmtpd-records) + +May be causing Emacs to move slowly after a while. + + +It may be better to instead do: + +cd prog/gnu/guix-config/linode-system-configuration; +guile -L . --listen=9999 + +And then in Emacs (as described here: https://www.nongnu.org/geiser/geiser_3.html) +connect to the external repl via +M-x geiser-connect -- 2.36.1
guix-patches@HIDDEN
:bug#56046
; Package guix-patches
.
Full text available.Received: (at submit) by debbugs.gnu.org; 17 Jun 2022 21:46:52 +0000 From debbugs-submit-bounces <at> debbugs.gnu.org Fri Jun 17 17:46:52 2022 Received: from localhost ([127.0.0.1]:47127 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>) id 1o2Jno-00023a-ME for submit <at> debbugs.gnu.org; Fri, 17 Jun 2022 17:46:52 -0400 Received: from lists.gnu.org ([209.51.188.17]:51032) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from <jbranso@HIDDEN>) id 1o2Jnn-00023T-DK for submit <at> debbugs.gnu.org; Fri, 17 Jun 2022 17:46:51 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:44682) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <jbranso@HIDDEN>) id 1o2Jnn-0004BL-2g for guix-patches@HIDDEN; Fri, 17 Jun 2022 17:46:51 -0400 Received: from mx1.dismail.de ([78.46.223.134]:19624) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <jbranso@HIDDEN>) id 1o2Jni-0001bn-9e for guix-patches@HIDDEN; Fri, 17 Jun 2022 17:46:50 -0400 Received: from mx1.dismail.de (localhost [127.0.0.1]) by mx1.dismail.de (OpenSMTPD) with ESMTP id 1163a9ce for <guix-patches@HIDDEN>; Fri, 17 Jun 2022 23:46:41 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed; d=dismail.de; h=from:to:cc :subject:date:message-id:mime-version:content-type :content-transfer-encoding; s=20190914; bh=SQgq7o2pu02VqUs/ao+T2 MjVUMMZsDKuMzICIDcHnhQ=; b=DSot/WU1fp8mTLSQ3LD+vZVAEcesdxK8MVe76 SMkawGBl75EzATp+3trxWrgJNfxqbh7UZQ4sQAH9DSHU2NoPXVJ51N47wsDgQJAl Mi7jYMKu0xkgM4P89HCKDWxqActhtCzILfbnxyFGfn2zTGcozcOuh4XEv5JMbq95 95q5oKI0HrDsyT3vn2ZMOcFL3YosxltlVjFcKkfjdsoBG7ooMkXFQZFpF+EXid5J IifDb74DxY4MB8d1WrIPEhJmXrr6zaA3/nNpjUl6BgwecpkwZwXURa4tljtI7+w7 eubVhYxPnJ5zc2EmChwhSyabwdH3bOakftZgSFV7f6x7f0kKA== Received: from smtp1.dismail.de (<unknown> [10.240.26.11]) by mx1.dismail.de (OpenSMTPD) with ESMTP id e4448c81 for <guix-patches@HIDDEN>; Fri, 17 Jun 2022 23:46:40 +0200 (CEST) Received: from smtp1.dismail.de (localhost [127.0.0.1]) by smtp1.dismail.de (OpenSMTPD) with ESMTP id 719b5bf9 for <guix-patches@HIDDEN>; Fri, 17 Jun 2022 23:46:40 +0200 (CEST) Received: by dismail.de (OpenSMTPD) with ESMTPSA id ec7c0fd7 (TLSv1.3:AEAD-AES256-GCM-SHA384:256:NO); Fri, 17 Jun 2022 23:46:38 +0200 (CEST) From: Joshua Branson <jbranso@HIDDEN> To: guix-patches@HIDDEN Subject: [PATCH] services: mail: add opensmtpd records to enhance opensmtpd-configuration. Date: Fri, 17 Jun 2022 17:46:18 -0400 Message-Id: <20220617214618.12377-1-jbranso@HIDDEN> X-Mailer: git-send-email 2.36.1 MIME-Version: 1.0 Content-Type: text/plain; charset=y Content-Transfer-Encoding: 8bit Received-SPF: pass client-ip=78.46.223.134; envelope-from=jbranso@HIDDEN; helo=mx1.dismail.de X-Spam_score_int: -3 X-Spam_score: -0.4 X-Spam_bar: / X-Spam_report: (-0.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, MIME_CHARSET_FARAWAY=2.45, RCVD_IN_DNSWL_LOW=-0.7, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-Debbugs-Envelope-To: submit Cc: Joshua Branson <jbranso@HIDDEN> X-BeenThere: debbugs-submit <at> debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: <debbugs-submit.debbugs.gnu.org> List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe> List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/> List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org> List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help> List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe> Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org> Openmstpd-configuration may only be configured by a config-file. This patch, enables one to configure opensmtpd by using some guile record types (defined via define-record-type*). This patch is mostly complete, but I could use some guidance on what else needs to be done for it to be accepted in to guix properly. I do have some documentation written for the opensmtpd-service, but it is probably not complete and is currently written in the org-mode format. * gnu/services/mail.scm (opensmtpd-table-configuration): New record. * gnu/services/mail.scm (opensmtpd-ca-configuration): New record. * gnu/services/mail.scm (opensmtpd-pki-configuration): New record. * gnu/services/mail.scm (opensmtpd-action-local-delivery-configuration): New record. * gnu/services/mail.scm (opensmtpd-maildir-configuration): New record. * gnu/services/mail.scm (opensmtpd-mda-configuration): New record. * gnu/services/mail.scm (opensmtpd-action-relay-configuration): New record. * gnu/services/mail.scm (opensmtpd-option-configuration): New record. * gnu/services/mail.scm (opensmtpd-filter-phase-configuration): New record. * gnu/services/mail.scm (opensmtpd-filter-configuration): New record. * gnu/services/mail.scm (opensmtpd-listen-on-configuration): New record. * gnu/services/mail.scm (opensmtpd-listen-on-socket-configuration): New record. * gnu/services/mail.scm (opensmtpd-match-configuration): New record. * gnu/services/mail.scm (opensmtpd-smtp-configuration): New record. * gnu/services/mail.scm (opensmtpd-srs-configuration): New record. * gnu/services/mail.scm (opensmtpd-queue-configuration): New record. * gnu/services/mail.scm (opensmtpd-configuration): New record. --- gnu/services/mail.scm | 2016 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 2013 insertions(+), 3 deletions(-) diff --git a/gnu/services/mail.scm b/gnu/services/mail.scm index d99743ac31..bdc0ee3bf7 100644 --- a/gnu/services/mail.scm +++ b/gnu/services/mail.scm @@ -57,8 +57,143 @@ (define-module (gnu services mail) mailbox-configuration namespace-configuration + opensmtpd-table-configuration + opensmtpd-table-configuration? + opensmtpd-table-configuration-name + opensmtpd-table-configuration-file-db + opensmtpd-table-configuration-data + + opensmtpd-ca-configuration + opensmtpd-ca-configuration? + opensmtpd-ca-configuration-name + opensmtpd-ca-configuration-file + + opensmtpd-pki-configuration + opensmtpd-pki-configuration? + opensmtpd-pki-configuration-domain + opensmtpd-pki-configuration-cert + opensmtpd-pki-configuration-key + opensmtpd-pki-configuration-dhe + + opensmtpd-action-local-delivery-configuration + opensmtpd-action-local-delivery-configuration? + opensmtpd-action-local-delivery-configuration-method + opensmtpd-action-local-delivery-configuration-alias + opensmtpd-action-local-delivery-configuration-ttl + opensmtpd-action-local-delivery-configuration-user + opensmtpd-action-local-delivery-configuration-userbase + opensmtpd-action-local-delivery-configuration-virtual + opensmtpd-action-local-delivery-configuration-wrapper + + opensmtpd-maildir-configuration + opensmtpd-maildir-configuration? + opensmtpd-maildir-configuration-pathname + opensmtpd-maildir-configuration-junk + + opensmtpd-mda-configuration + opensmtpd-mda-configuration-name + opensmtpd-mda-configuration-command + + opensmtpd-action-relay-configuration + opensmtpd-action-relay-configuration? + opensmtpd-action-relay-configuration-backup + opensmtpd-action-relay-configuration-backup-mx + opensmtpd-action-relay-configuration-helo + opensmtpd-action-relay-configuration-domain + opensmtpd-action-relay-configuration-host + opensmtpd-action-relay-configuration-pki + opensmtpd-action-relay-configuration-srs + opensmtpd-action-relay-configuration-tls + opensmtpd-action-relay-configuration-auth + opensmtpd-action-relay-configuration-mail-from + opensmtpd-action-relay-configuration-src + + opensmtpd-option-configuration + opensmtpd-option-configuration? + opensmtpd-option-configuration-option + opensmtpd-option-configuration-not + opensmtpd-option-configuration-regex + opensmtpd-option-configuration-data + + opensmtpd-filter-phase-configuration + opensmtpd-filter-phase-configuration? + opensmtpd-filter-phase-configuration-name + opensmtpd-filter-phase-configuration-phase-name + opensmtpd-filter-phase-configuration-options + opensmtpd-filter-phase-configuration-decision + opensmtpd-filter-phase-configuration-message + opensmtpd-filter-phase-configuration-value + + opensmtpd-filter-configuration + opensmtpd-filter-configuration? + opensmtpd-filter-configuration-name + opensmtpd-filter-configuration-proc + + opensmtpd-listen-on-configuration + opensmtpd-listen-on-configuration? + opensmtpd-listen-on-configuration-interface + opensmtpd-listen-on-configuration-family + opensmtpd-listen-on-configuration-auth + opensmtpd-listen-on-configuration-auth-optional + opensmtpd-listen-on-configuration-filters + opensmtpd-listen-on-configuration-hostname + opensmtpd-listen-on-configuration-hostnames + opensmtpd-listen-on-configuration-mask-src + opensmtpd-listen-on-configuration-disable-dsn + opensmtpd-listen-on-configuration-pki + opensmtpd-listen-on-configuration-port + opensmtpd-listen-on-configuration-proxy-v2 + opensmtpd-listen-on-configuration-received-auth + opensmtpd-listen-on-configuration-senders + opensmtpd-listen-on-configuration-secure-connection + opensmtpd-listen-on-configuration-tag + + opensmtpd-listen-on-socket-configuration + opensmtpd-listen-on-socket-configuration? + opensmtpd-listen-on-socket-configuration-filters + opensmtpd-listen-on-socket-configuration-mask-src + opensmtpd-listen-on-socket-configuration-tag + + opensmtpd-match-configuration + opensmtpd-match-configuration? + opensmtpd-match-configuration-action + opensmtpd-match-configuration-options + + opensmtpd-smtp-configuration + opensmtpd-smtp-configuration? + opensmtpd-smtp-configuration-ciphers + opensmtpd-smtp-configuration-limit-max-mails + opensmtpd-smtp-configuration-limit-max-rcpt + opensmtpd-smtp-configuration-max-message-size + opensmtpd-smtp-configuration-sub-addr-delim character + + opensmtpd-srs-configuration + opensmtpd-srs-configuration? + opensmtpd-srs-configuration-key + opensmtpd-srs-configuration-backup-key + opensmtpd-srs-configuration-ttl-delay + + opensmtpd-queue-configuration + opensmtpd-queue-configuration? + opensmtpd-queue-configuration-compression + opensmtpd-queue-configuration-encryption + opensmtpd-queue-configuration-ttl-delay + opensmtpd-configuration opensmtpd-configuration? + opensmtpd-package + opensmtpd-config-file + opensmtpd-configuration-bounce + opensmtpd-configuration-listen-ons + opensmtpd-configuration-listen-on-socket + opensmtpd-configuration-includes + opensmtpd-configuration-matches + opensmtpd-configuration-mda-wrappers + opensmtpd-configuration-mta-max-deferred + opensmtpd-configuration-srs + opensmtpd-configuration-smtp + opensmtpd-configuration-queue + opensmtpd-service-type %default-opensmtpd-config-file @@ -1651,13 +1786,1888 @@ (define (generate-dovecot-documentation) ;;; OpenSMTPD. ;;; +;; some fieldnames have a default value of #f, which is ok. They cannot have a value of #t. +;; for example opensmtpd-table-configuration-data can be #f, BUT NOT true. +;; my/sanitize procedure tests values to see if they are of the right kind. +;; procedure false? is needed to allow fields like 'values' to be blank, (empty), or #f BUT also +;; have a value like a list of strings. +(define (false? var) + (eq? #f var)) + +;; this procedure takes in a var and a list of procedures. It loops through list of procedures passing in var to each. +;; if one procedure returns #t, the function returns true. Otherwise #f. +;; TODO for fun rewrite this using map +;; If I rewrote it in map, then it may help with sanitizing. +;; eg: I could then potentially easily sanitize vars with lambda procedures. +(define (is-value-right-type? var list-of-procedures record fieldname) + (if (null? list-of-procedures) + #f + (cond [(procedure? (car list-of-procedures)) + (if ((car list-of-procedures) var) + #t + (is-value-right-type? var (cdr list-of-procedures) record fieldname))] + [(and (sanitize-configuration? (car list-of-procedures)) + (sanitize-configuration-error-if-proc-fails (car list-of-procedures)) + (if ((sanitize-configuration-proc (car list-of-procedures)) var) + #t + (begin + (apply string-append + (sanitize-configuration-error-message (car list-of-procedures))) + (throw 'bad! var))))] + [else (if ((sanitize-configuration-proc (car list-of-procedures)) var) + #t + (is-value-right-type? var (cdr list-of-procedures) record fieldname))]))) + +;; converts strings like this: +;; "apple, ham, cherry" -> "apple, ham, or cherry" +;; "pineapple" -> "pinneapple". +;; "cheese, grapefruit, or jam" -> "cheese, grapefruit, or jam" +(define (add-comma-or string) + (define last-comma-location (string-rindex string #\,)) + (if last-comma-location + (if (string-contains string ", or" last-comma-location) + string + (string-replace string ", or" last-comma-location + (+ 1 last-comma-location))) + string)) + +;; I could test for read-ability of a file, but then I would have to +;; test the program as root everytime instead of as a normal user... +(define (file-exists? file) +(if (string? file) + (access? file F_OK) + #f)) + +(define (list-of-procedures->string procedures) + (define string + (let loop ([procedures procedures]) + (if (null? procedures) + "" + (begin + (string-append + (cond [(eq? false? (car procedures)) + "#f , "] + [(eq? boolean? (car procedures)) + "boolean, "] + [(eq? string? (car procedures)) + "string, "] + [(eq? integer? (car procedures)) + "integer, "] + [(eq? list-of-strings? (car procedures)) + "list of strings, "] + [(eq? assoc-list? (car procedures)) + "an association list, "] + [(eq? opensmtpd-pki-configuration? (car procedures)) + "an <opensmtpd-pki-configuration> record, "] + [(eq? opensmtpd-table-configuration? (car procedures)) + "an <opensmtpd-table-configuration> record, "] + [(eq? list-of-unique-opensmtpd-match-configuration? (car procedures)) + "a list of unique <opensmtpd-match-configuration> records, "] + [(eq? table-whose-data-are-assoc-list? (car procedures)) + (string-append + "an <opensmtpd-table-configuration> record whose fieldname 'values' are an assoc-list \n" + "(eg: (opensmtpd-table-configuration (name \"table\") (data '(\"joshua\" . \"$encrypted$password\")))), ")] + [(eq? file-exists? (car procedures)) + "file, "] + [else "has an incorrect value, "]) + (loop (cdr procedures))))))) + (add-comma-or (string-append (string-drop-right string 2) ".\n"))) + +;; TODO can I M-x raise-sexp (string=? string var) in this procedure? and get rid of checking +;; if the var is a string? The previous string-in-list? had that check. +;; (string-in-list? '("hello" 5 "cat")) currently works. If I M-x raise-sexp (string=? string var) +;; then it will no longer work. +(define (string-in-list? string list) + (primitive-eval (cons 'or (map (lambda (var) (and (string? var) (string=? string var))) list)))) + +(define (my/sanitize var record fieldname list-of-procedures) + (if (is-value-right-type? var list-of-procedures record fieldname) + var + (begin + (display (string-append "<" record "> fieldname: '" fieldname "' is of type " + (list-of-procedures->string list-of-procedures) "\n")) + (throw 'bad! var)))) + +;; Some example opensmtpd-table-configurations: +;; +;; (opensmtpd-table-configuration (name "root accounts") (data '(("joshua" . "root@HIDDEN") ("joshua" . "postmaster@HIDDEN")))) +;; (opensmtpd-table-configuration (name "root accounts") (data (list "mysite.me" "your-site.com"))) +;; TODO should <opensmtpd-table-configuration> support have a fieldname 'file'? +;; Or should I change name to name-or-file ? +(define-record-type* <opensmtpd-table-configuration> + opensmtpd-table-configuration make-opensmtpd-table-configuration + opensmtpd-table-configuration? + this-record + (name opensmtpd-table-configuration-name ;; string + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-table-configuration" "name" (list string?))))) + (file-db opensmtpd-table-configuration-file-db + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-table-configuration" "file-db" + (list boolean?))))) + ;; FIXME support an aliasing table as described here: + ;; https://man.openbsd.org/table.5 + ;; One may have to use the record file for this. I don't think tables support a table like this: + ;; table "name" { joshua = joshua@HIDDEN,joshua@HIDDEN,joshua@HIDDEN, root = root@HIDDEN } + ;; If values is an absolute filename, then it will use said filename to house the table info. + ;; filename must be an absolute filename. + (data opensmtpd-table-configuration-data + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-table-configuration" "values" + (list file-exists? list-of-strings? assoc-list?))))) + ;; is a list of values or key values + ;; eg: (list "mysite.me" "your-site.com") + ;; eg: (list ("joshua" . "joshua@HIDDEN") ("james" . "james@HIDDEN")) + ;; I am currently making these values be as assocation list of strings only. + ;; FIXME should I allow a var like this? + ;; (list (cons "gnucode.me" 234.949.392.23)) + ;; can be of type: (quote list-of-strings) or (quote assoc-list) + ;; (opensmtpd-table-configuration-type record) returns the values' type. The user SHOULD NEVER set the type. + ;; TODO jpoiret: on irc reccomends that I just use an outside function to determine fieldname 'values', type. + ;; it would be "simpler" and possibly easier for the next person working on this code to understand what is happening. + (type opensmtpd-table-configuration-type + (default #f) + (thunked) + (sanitize (lambda (var) + (cond [(opensmtpd-table-configuration-data this-record) + (if (list-of-strings? (opensmtpd-table-configuration-data this-record)) + (quote list-of-strings) + (quote assoc-list))] + [(file-exists? (opensmtpd-table-configuration-data this-record)) + (if (opensmtpd-table-configuration-file-db this-record) + (quote db) + (quote file))] + [else + (display "opensmtpd-table-configuration-type is broke\n") + (throw 'bad! var)]))))) + +(define-record-type* <opensmtpd-ca-configuration> + opensmtpd-ca-configuration make-opensmtpd-ca-configuration + opensmtpd-ca-configuration? + (name opensmtpd-ca-configuration-name + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-ca-configuration" "name" (list string?))))) + (file opensmtpd-ca-configuration-file + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-ca-configuration" "file" (list file-exists?)))))) + +(define-record-type* <opensmtpd-pki-configuration> + opensmtpd-pki-configuration make-opensmtpd-pki-configuration + opensmtpd-pki-configuration? + (domain opensmtpd-pki-configuration-domain + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-pki-configuration" "domain" (list string?))))) + ;; TODO/FIXME this should probably be a list of files. The opensmtpd documentation says + ;; that you could have a list of files: + ;; + ;; pki pkiname cert certfile + ;; Associate certificate file certfile with host pkiname, and use that file to prove + ;; the identity of the mail server to clients. pkiname is the server's name, de‐ + ;; rived from the default hostname or set using either + ;; /gnu/store/2d13sdz76ldq8zgwv4wif0zx7hkr3mh2-opensmtpd-6.8.0p2/etc/mailname or us‐ + ;; ing the hostname directive. If a fallback certificate or SNI is wanted, the ‘*’ + ;; wildcard may be used as pkiname. + + ;; A certificate chain may be created by appending one or many certificates, includ‐ + ;; ing a Certificate Authority certificate, to certfile. The creation of certifi‐ + ;; cates is documented in starttls(8). + (cert opensmtpd-pki-configuration-cert + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-pki-configuration" "cert" (list file-exists?))))) + (key opensmtpd-pki-configuration-key + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-pki-configuration" "key" (list file-exists?))))) + ; todo sanitize this. valid parameters are "none", "legacy", or "auto". + (dhe opensmtpd-pki-configuration-dhe + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-dhe" "dhe" (list false? string?)))))) + +(define-record-type* <opensmtpd-lmtp-configuration> + opensmtpd-lmtp-configuration make-opensmtpd-lmtp-configuration + opensmtpd-lmtp-configuration? + (destination opensmtpd-lmtp-configuration-destination + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-lmtp-configuration" "destination" + (list string?))))) + (rcpt-to opensmtpd-lmtp-configuration-rcpt-to + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-lmtp-configuration" "rcpt-to" + (list false? string?)))))) + +(define-record-type* <opensmtpd-mda-configuration> + opensmtpd-mda-configuration make-opensmtpd-mda-configuration + opensmtpd-mda-configuration? + (name opensmtpd-mda-configuration-name + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-mda-configuration" "name" + (list string?))))) + ;; TODO should I allow this command to be a gexp? + (command opensmtpd-mda-configuration-command + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-mda-configuration" "command" + (list string?)))))) + +(define-record-type* <opensmtpd-maildir-configuration> + opensmtpd-maildir-configuration make-opensmtpd-maildir-configuration + opensmtpd-maildir-configuration? + (pathname opensmtpd-maildir-configuration-pathname + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-maildir-configuration" "pathname" + (list false? string?))))) + (junk opensmtpd-maildir-configuration-junk + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-maildir-configuration" "junk" + (list boolean?)))))) + +(define-record-type* <opensmtpd-action-local-delivery-configuration> + opensmtpd-action-local-delivery-configuration make-opensmtpd-action-local-delivery-configuration + opensmtpd-action-local-delivery-configuration? + (name opensmtpd-action-local-delivery-configuration-name + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-local-delivery-configuration" "name" + (list string?))))) + (method opensmtpd-action-local-delivery-configuration-method + (default "mbox") + (sanitize (lambda (var) + (cond + [(or (opensmtpd-lmtp-configuration? var) + (opensmtpd-maildir-configuration? var) + (opensmtpd-mda-configuration? var) + (string=? var "mbox") + (string=? var "expand-only") + (string=? var "forward-only")) + var] + [else + (begin + (display (string-append "<opensmtpd-action-local-delivery-configuration> fieldname 'method' must be of type \n" + "\"mbox\", \"expand-only\", \"forward-only\" \n" + "<opensmtpd-lmtp-configuration>, <opensmtpd-maildir-configuration>, \n" + "or <opensmtpd-mda-configuration>.\n")) + (throw 'bad! var))])))) + (alias opensmtpd-action-local-delivery-configuration-alias + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-local-delivery-configuration" "alias" + (list false? opensmtpd-table-configuration?))))) + (ttl opensmtpd-action-local-delivery-configuration-ttl + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-local-delivery-configuration" "ttl" + (list false? string?))))) + (user opensmtpd-action-local-delivery-configuration-user + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-local-delivery-configuration" "user" + (list false? string?))))) + (userbase opensmtpd-action-local-delivery-configuration-userbase + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-local-delivery-configuration" "userbase" + (list false? opensmtpd-table-configuration?))))) + (virtual opensmtpd-action-local-delivery-configuration-virtual + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-local-delivery-configuration" "virtual" + (list false? opensmtpd-table-configuration?))))) + (wrapper opensmtpd-action-local-delivery-configuration-wrapper + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-local-delivery-configuration" "wrapper" + (list false? string?)))))) + +;; FIXME/TODO this is a valid opensmtpd-relay record +;; (opensmtpd-action-relay-configuration +;; (pki (opensmtpd-pki-configuration +;; (domain "gnucode.me") +;; (cert "opensmtpd.scm") +;; (key "opensmtpd.scm")))) +;; BUT how does it relay the email? What host does it use? +;; I think opensmtpd-relay-configuration needs "method" field. +;; the method field might need to be another record...BUT basically the relay has to have a 'backup', 'backup-mx', +;; or 'domain', or 'host' defined. +(define-record-type* <opensmtpd-action-relay-configuration> + opensmtpd-action-relay-configuration make-opensmtpd-action-relay-configuration + opensmtpd-action-relay-configuration? + (name opensmtpd-action-relay-configuration-name + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "name" + (list string?)))) + (default #f)) + (backup opensmtpd-action-relay-configuration-backup ;; boolean + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "backup" + (list boolean?))))) + (backup-mx opensmtpd-action-relay-configuration-backup-mx ;; string mx name + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "backup-mx" + (list false? string?))))) + (helo opensmtpd-action-relay-configuration-helo + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "helo" + (list false? string? opensmtpd-table-configuration?)))) + (default #f)) + (helo-src opensmtpd-action-relay-configuration-helo-src + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "helo-src" + (list false? string? opensmtpd-table-configuration?)))) + (default #f)) + (domain opensmtpd-action-relay-configuration-domain + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "domain" + (list false? opensmtpd-table-configuration?)))) + (default #f)) + (host opensmtpd-action-relay-configuration-host + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "host" + (list false? string?)))) + (default #f)) + (pki opensmtpd-action-relay-configuration-pki + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "pki" + (list false? opensmtpd-pki-configuration?))))) + (srs opensmtpd-action-relay-configuration-srs + (default #f) + (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "srs" + (list boolean?)))) + (tls opensmtpd-action-relay-configuration-tls + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "tls" + (list false? string?))))) + (auth opensmtpd-action-relay-configuration-auth + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "auth" + (list false? opensmtpd-table-configuration?)))) + (default #f)) + (mail-from opensmtpd-action-relay-configuration-mail-from + (default #f)) + ;; string "127.0.0.1" or "<interface>" or "<table of IP addresses>" + ;; TODO should I do some sanitizing to make sure that the string? here is actually an IP address or a valid interface? + (src opensmtpd-action-relay-configuration-src + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-action-relay-configuration" "src" + (list false? string? opensmtpd-table-configuration?)))) + (default #f))) + +;; this record is used by <opensmtpd-filter-phase-configuration> & +;; <opensmtpd-match-configuration> +(define-record-type* <opensmtpd-option-configuration> + opensmtpd-option-configuration make-opensmtpd-option-configuration + opensmtpd-option-configuration? + (option opensmtpd-option-configuration-option + (default #f) + (sanitize (lambda (var) + (if (and (string? var) + (or (string-in-list? var (list "fcrdns" "rdns" + "src" "helo" + "auth" "mail-from" + "rcpt-to" + "for" + "for any" "for local" + "for domain" "for rcpt-to" + "from any" "from auth" + "from local" "from mail-from" + "from rdns" "from socket" + "from src" "auth" + "helo" "mail-from" + "rcpt-to" "tag" "tls" + )))) + var + (begin + (display (string-append "<opensmtpd-option-configuration> fieldname: 'option' is of type \n" + "string. The string can be either 'fcrdns', \n" + " 'rdns', 'src', 'helo', 'auth', 'mail-from', or 'rcpt-to', \n" + "'for', 'for any', 'for local', 'for domain', 'for rcpt-to', \n" + "'from any', 'from auth', 'from local', 'from mail-from', 'from rdns', 'from socket', \n" + "'from src', 'auth helo', 'mail-from', 'rcpt-to', 'tag', or 'tls' \n" + )) + (throw 'bad! var)))))) + (not opensmtpd-option-configuration-not + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-option-configuration" "not" + (list boolean?))))) + (regex opensmtpd-option-configuration-regex + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-option-configuration" "regex" + (list boolean?))))) + (data opensmtpd-option-configuration-data + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-option-configuration" "data" + (list false? string? opensmtpd-table-configuration?)))))) + +(define-record-type* <opensmtpd-filter-phase-configuration> + opensmtpd-filter-phase-configuration make-opensmtpd-filter-phase-configuration + opensmtpd-filter-phase-configuration? + (name opensmtpd-filter-phase-configuration-name ;; string chain-name + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-filter-phase-configuration" "name" + (list string?))))) + (phase opensmtpd-filter-phase-configuration-phase ;; string + (default #f) + (sanitize (lambda (var) + ;;(my/sanitize var "opensmtpd-filter-phase-configuration" "phase" + ;; (list (sanitize-configuration + ;; (proc (lambda (value) + ;; (and (string? var) + ;; (string-in-list? var (list "connect" + ;; "helo" + ;; "mail-from" + ;; "rcpt-to" + ;; "data" + ;; "commit"))))) + ;; (error-message (list + ;; "<opensmtpd-filter-phase-configuration> fieldname: 'phase' is of type \n" + ;; "string. The string can be either 'connect'," + ;; " 'helo', 'mail-from', 'rcpt-to', 'data', or 'commit.'\n "))))) + (if (and (string? var) + (string-in-list? var (list "connect" + "helo" + "mail-from" + "rcpt-to" + "data" + "commit"))) + var + (begin + (display (string-append "<opensmtpd-filter-phase-configuration> fieldname: 'phase' is of type \n" + "string. The string can be either 'connect'," + " 'helo', 'mail-from', 'rcpt-to', 'data', or 'commit.'\n " + )) + (throw 'bad! var))) + ))) + + (options opensmtpd-filter-phase-configuration-options + (default #f) + (sanitize (lambda (var) + ;; returns #t if list is a unique list of <opensmtpd-option-configuration> + (define (list-of-opensmtpd-option-configuration? list) + (and (list-of-type? list opensmtpd-option-configuration?) + (not (contains-duplicate? list)))) + + (define (list-has-duplicates-or-non-opensmtpd-option-configuration list) + (not (list-of-opensmtpd-option-configuration? list))) + + ;; input <opensmtpd-option-configuration> + ;; return #t if <opensmtpd-option-configuration> fieldname 'option' + ;; that needs a corresponding table has one. Otherwise #f + (define (opensmtpd-option-configuration-has-table? record) + (define decision (opensmtpd-option-configuration-option record)) + (and (string? decision) + ;; if option needs a table, check for a table + (if (string-in-list? decision (list "src" + "helo" + "mail-from" + "rcpt-to")) + (opensmtpd-table-configuration? (opensmtpd-option-configuration-data record)) + #t))) + + (define (list-of-opensmtpd-option-configuration-has-table? list) + (list-of-type? list opensmtpd-option-configuration-has-table?)) + + (define (some-opensmtpd-option-configuration-in-list-lack-table? list) + (not (list-of-opensmtpd-option-configuration-has-table? list))) + + ;;each element in list is of type <opensmtpd-option-configuration> + (cond [(list-has-duplicates-or-non-opensmtpd-option-configuration var) + (begin + (display (string-append "<opensmtpd-filter-phase-configuration> fieldname: 'options' is a list of unique \n" + "<opensmtpd-option-configuration> records.\n")) + (throw 'bad! var))] + ;; if fieldname 'option' is of string 'src', 'helo', 'mail-from', 'rcpt-to', then there should be a table + [(some-opensmtpd-option-configuration-in-list-lack-table? var) + (begin + (display (string-append "<opensmtpd-option-configuration>'s fieldname 'option' values of \n" + "'src', 'helo', 'mail-from', or 'rcpt-to' need a corresponding 'table' \n" + " of type <opensmtpd-table-configuration>. eg: \n" + "(opensmtpd-option-configuration \n" + " (option \"src\")\n" + " (table (opensmtpd-table-configuration \n" + " (name \"src-table\")\n" + " (data (list \"hello\" \"cat\")))))\n")) + ;; TODO it would be nice if the var this error message throws in the bad + ;; <opensmtpd-option-configuration>, instead of the list of records. + (throw 'bad! var))] + [else var])))) + (decision opensmtpd-filter-phase-configuration-decision + (default #f) + (sanitize (lambda (var) + (if (and (string? var) + (string-in-list? var (list "bypass" "disconnect" + "reject" "rewrite" "junk"))) + var + (begin + (display (string-append "<opensmtpd-filter-decision> fieldname: 'decision' is of type \n" + "string. The string can be either 'bypass'," + " 'disconnect', 'reject', 'rewrite', or 'junk'.\n")) + (throw 'bad! var)))))) + (message opensmtpd-filter-phase-configuration-message + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-filter-phase-configuration" "message" + (list false? string?))))) + (value opensmtpd-filter-phase-configuration-value + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-filter-phase-configuration" "value" + (list false? number?)))))) + +(define-record-type* <opensmtpd-filter-configuration> + opensmtpd-filter-configuration make-opensmtpd-filter-configuration + opensmtpd-filter-configuration? + (name opensmtpd-filter-configuration-name + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-filter" "name" + (list string?))))) + (exec opensmtpd-filter-exec + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-filter" "exec" + (list boolean?))))) + (proc opensmtpd-filter-configuration-proc ; a string like "rspamd" or the command to start it like "/path/to/rspamd --option=arg --2nd-option=arg2" + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-filter" "proc" + (list string?)))))) + +;; There is another type of filter that opensmtpd supports, which is a filter chain. +;; A filter chain is a list of <opensmtpd-filter-phase-configuration> and <opensmtpd-filter-configuration>. +;; This lets you apply several filters under one filter name. I could have defined +;; a record type for it, but the record would only have had two fields: name and list-of-filters. +;; Why write that as a record? That's too simple. +;; returns #t if list is a unique list of <opensmtpd-filter-configuration> or <opensmtpd-filter-phase-configuration> +;; returns # otherwise +(define (opensmtpd-filter-chain? %filters) + (and (list-of-unique-filter-or-filter-phase? %filters) + (< 1 (length %filters)))) + +(define-record-type* <opensmtpd-listen-on-configuration> + opensmtpd-listen-on-configuration make-opensmtpd-listen-on-configuration + opensmtpd-listen-on-configuration? + ;; interface may be an IP address, interface group, or domain name + (interface opensmtpd-listen-on-configuration-interface + (default "lo")) + (family opensmtpd-listen-on-configuration-family + (default #f) + (sanitize (lambda (var) + (cond + [(eq? #f var) ;; var == #f + var] + [(and (string? var) + (string-in-list? var (list "inet4" "inet6"))) + var] + [else + (begin + (display "<opensmtpd-listen-on-configuration> fieldname 'family' must be string \"inet4\" or \"inet6\".\n") + (throw 'bad! var))])))) + (auth opensmtpd-listen-on-configuration-auth + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-listen-on-configuration" "auth" + (list boolean? table-whose-data-are-assoc-list?))))) + (auth-optional opensmtpd-listen-on-configuration-auth-optional + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-listen-on-configuration" "auth-optional" + (list boolean? + table-whose-data-are-assoc-list?))))) + ;; TODO add a ca entry? + ;; string FIXME/TODO sanitize this to support a gexp. That way way the + ;; includes directive can include my hacky scheme code that I use for opensmtpd-dkimsign. + (filters opensmtpd-listen-on-configuration-filters + (default #f) + (sanitize (lambda (var) + (sanitize-filters var)))) + (hostname opensmtpd-listen-on-configuration-hostname + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-listen-on-configuration" "hostname" + (list false? string?))))) + (hostnames opensmtpd-listen-on-configuration-hostnames + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-listen-on-configuration" "hostnames" + (list false? table-whose-data-are-assoc-list?))))) + (mask-src opensmtpd-listen-on-configuration-mask-src + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-listen-on-configuration" "mask-src" + (list boolean?))))) + (disable-dsn opensmtpd-listen-on-configuration-disable-dsn + (default #f)) + (pki opensmtpd-listen-on-configuration-pki + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-listen-on-configuration" "pki" + (list false? opensmtpd-pki-configuration?))))) + (port opensmtpd-listen-on-configuration-port + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-listen-on-configuration" "port" + (list false? integer?))))) + (proxy-v2 opensmtpd-listen-on-configuration-proxy-k2 + (default #f)) + (received-auth opensmtpd-listen-on-configuration-received-auth + (default #f)) + ;; TODO add in a senders option! + ;; string or <opensmtpd-senders> record + ;; (senders opensmtpd-listen-on-configuration-senders + ;; (sanitize (lambda (var) + ;; (my/sanitize var "opensmtpd-listen-on-configuration" "port" (list false? integer?)))) + ;; (default #f)) + (secure-connection opensmtpd-listen-on-configuration-secure-connection + (default #f) + (sanitize (lambda (var) + (cond [(boolean? var) + var] + [(and (string? var) + (string-in-list? var + (list "smtps" "tls" + "tls-require" + "tls-require-verify"))) + var] + [else + (begin + (display (string-append "<opensmtd-listen-on> fieldname 'secure-connection' can be \n" + "one of the following strings: \n'smtps', 'tls', 'tls-require', \n" + "or 'tls-require-verify'.\n")) + (throw 'bad! var))])))) + (tag opensmtpd-listen-on-configuration-tag + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-listen-on-configuration" "tag" + (list false? string?)))) + (default #f))) + +(define-record-type* <opensmtpd-listen-on-socket-configuration-configuration> + opensmtpd-listen-on-socket-configuration-configuration make-opensmtpd-listen-on-socket-configuration-configuration + opensmtpd-listen-on-socket-configuration-configuration? + ;; false or <opensmtpd-filter-configuration> or list of <opensmtpd-filter-configuration> + (filters opensmtpd-listen-on-socket-configuration-configuration-filters + (sanitize (lambda (var) + (sanitize-filters var))) + (default #f)) + (mask-src opensmtpd-listen-on-socket-configuration-configuration-mask-src + (default #f)) + (tag opensmtpd-listen-on-socket-configuration-configuration-tag + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-listen-on-configuration" "tag" + (list false? string?)))) + (default #f))) + + +(define-record-type* <opensmtpd-match-configuration> + opensmtpd-match-configuration make-opensmtpd-match-configuration + opensmtpd-match-configuration? + ;;TODO? Perhaps I should add in a reject fieldname. If reject + ;;is #t, then the match record will be a reject match record. + ;; (opensmtpd-match (reject #t)) vs. (opensmtpd-match (action 'reject)) + ;; To do this, I will also have to 'reject' mutually exclusive. AND an match with 'reject' can have no action defined. + (action opensmtpd-match-configuration-action + (default #f) + (sanitize (lambda (var) + (if (or (opensmtpd-action-relay-configuration? var) + (opensmtpd-action-local-delivery-configuration? var) + (eq? (quote reject) var)) + var + (begin + (display + (string-append "<opensmtpd-match-configuration> fieldname 'action' is of type <opensmtpd-action-relay-configuration>, \n" + "<opensmtpd-action-local-delivery-configuration>, or (quote reject).\n" + "If its var is (quote reject), then the match rejects the incoming message\n" + "during the SMTP dialogue.\n")) + (throw 'bad! var)))))) + (options opensmtpd-match-configuration-options + (default #f) + (sanitize (lambda (var) + (cond ((not var) + #f) + ((not (list-of-unique-opensmtpd-option-configuration? var)) + (throw-error var '("<opensmtpd-match-configuration> fieldname 'options' is a list of unique \n" + "<opensmtpd-option-configuration> records. \n"))) + (else (sanitize-list-of-options-for-match-configuration var))))))) + +(define-record-type* <opensmtpd-smtp-configuration> + opensmtpd-smtp-configuration make-opensmtpd-smtp-configuration + opensmtpd-smtp-configuration? + (ciphers opensmtpd-smtp-configuration-ciphers + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-smtp-configuration" "ciphers" + (list false? string?))))) + (limit-max-mails opensmtpd-smtp-configuration-limit-max-mails + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-smtp-configuration" "limit-max-mails" + (list false? integer?))))) + (limit-max-rcpt opensmtpd-smtp-configuration-limit-max-rcpt + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-smtp-configuration" "limit-max-rcpt" + (list false? integer?))))) + (max-message-size opensmtpd-smtp-configuration-max-message-size + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-smtp-configuration" "max-message-size" + (list false? integer? string?))))) + ;; FIXME/TODO the sanitize function of sub-addr-delim should accept a string of length one not string? + (sub-addr-delim opensmtpd-smtp-configuration-sub-addr-delim + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-smtp-configuration" "sub-addr-delim" + (list false? integer? string?)))))) + +(define-record-type* <opensmtpd-srs-configuration> + opensmtpd-srs-configuration make-opensmtpd-srs-configuration + opensmtpd-srs-configuration? + ;; TODO should this be a file? + (key opensmtpd-srs-configuration-key + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-srs-configuration" "key" + (list false? boolean? string?))))) + ;; TODO should this also be a file? + (backup-key opensmtpd-srs-configuration-backup-key + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-srs-configuration" "backup-key" + (list false? integer?))))) + (ttl-delay opensmtpd-srs-configuration-ttl-delay + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-srs-configuration" "ttl-delay" + (list false? string?)))))) + +(define-record-type* <opensmtpd-queue-configuration> + opensmtpd-queue-configuration make-opensmtpd-queue-configuration + opensmtpd-queue-configuration? + (compression opensmtpd-queue-configuration-compression + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-queue-configuration" "compression" + (list boolean?))))) + (encryption opensmtpd-queue-configuration-encryption + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-queue-configuration" "encryption" + (list boolean? file-exists? string?))))) + (ttl-delay opensmtpd-queue-configuration-ttl-delay + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-queue-configuration" "ttl-delay" + (list false? string?)))))) + (define-record-type* <opensmtpd-configuration> opensmtpd-configuration make-opensmtpd-configuration opensmtpd-configuration? - (package opensmtpd-configuration-package - (default opensmtpd)) + (package opensmtpd-configuration-package + (default opensmtpd)) (config-file opensmtpd-configuration-config-file - (default %default-opensmtpd-config-file))) + (default #f)) + ;; FIXME/TODO should I include a admd authservid entry? + + ;; TODO sanitize this properly with perhaps a <sanitize-configuration>. + (bounce opensmtpd-configuration-bounce + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-configuration" "bounce" + (list false? list?))))) + (cas opensmtpd-configuration-cas + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-configuration" "cas" + (list false? list-of-opensmtpd-ca-configuration?))))) + ;; list of many records of type opensmtpd-listen-on-configuration + (listen-ons opensmtpd-configuration-listen-ons + (default (list (opensmtpd-listen-on-configuration))) + (sanitize (lambda (var) + (if (list-of-opensmtpd-listen-on-configuration? var) + var + (begin + (display "<opensmtpd-configuration> fieldname 'listen-ons' expects a list of records ") + (display "of one or more unique <opensmtpd-listen-on-configuration> records.\n") + (throw 'bad! var)))))) + ;; accepts type <opensmtpd-listen-on-socket-configuration-configuration> + (listen-on-socket opensmtpd-configuration-listen-on-socket + (default (opensmtpd-listen-on-socket-configuration-configuration))) + (includes opensmtpd-configuration-includes ;; list of strings of absolute path names + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-configuration" "includes" + (list false? list-of-strings?))))) + (matches opensmtpd-configuration-matches + (default (list (opensmtpd-match-configuration + (action (opensmtpd-action-local-delivery-configuration + (name "local") + (method "mbox"))) + (options (list + (opensmtpd-option-configuration + (option "for local"))))) + (opensmtpd-match-configuration + (action (opensmtpd-action-relay-configuration + (name "outbound"))) + (options (list + (opensmtpd-option-configuration + (option "from local")) + (opensmtpd-option-configuration + (option "for any"))))))) + ;; TODO perhaps I should sanitize this function like I sanitized the 'filters'. + ;; I definitely should sanitize this function a bit more. For example, you could have two different + ;; actions, one for local delivery and one for remote, with the same name. I should make sure that + ;; I have no two different actions with the same name. + (sanitize (lambda (var) + ;; Should we do more sanitizing here? eg: "from socket" should NOT have a table or value + var + (my/sanitize var "opensmtpd-configuration" "matches" + (list list-of-unique-opensmtpd-match-configuration?))))) + ;; list of many records of type mda-wrapper + ;; TODO/FIXME support using gexps here + ;; eg (list "name" gexp) + (mda-wrappers opensmtpd-configuration-mda-wrappers + (default #f) + (sanitize (lambda (var) + (my/sanitize var + "opensmtpd-configuration" + "mda-wrappers" + (list false? string?))))) + (mta-max-deferred opensmtpd-configuration-mta-max-deferred + (default 100) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-configuration" "mta-max-deferred" + (list number?))))) + + ;; TODO should I add a fieldname proc _proc-name_ _command_ as found in the man 5 smtpd.conf ? + + (queue opensmtpd-configuration-queue + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-configuration" "queue" + (list false? opensmtpd-queue-configuration?))))) + (smtp opensmtpd-configuration-smtp + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-configuration" "smtp" + (list false? opensmtpd-smtp-configuration?))))) + (srs opensmtpd-configuration-srs + (default #f) + (sanitize (lambda (var) + (my/sanitize var "opensmtpd-configuration" "srs" + (list false? opensmtpd-srs-configuration?)))))) + +;; This is a non-exported record for passing around sanitize procedures. +;; As of 5/2/2022 I am not using it. I should probably just delete it. +(define-record-type* <sanitize-configuration> + sanitize-configuration make-sanitize-configuration + sanitize-configuration? + (proc sanitize-configuration-proc + (default #f) + ;;(sanitize (lambda (var) (procedure? var))) + ) + (args sanitize-configuration-args + (default #f) + ;;(sanitize (lambda (var) (lambda (var) (list? var)))) + ) + (error-message sanitize-configuration-error-message + (default #f) + ;;(sanitize (lambda (var) (list? var))) + ) + (error-if-proc-fails sanitize-configuration-error-if-proc-fails + (default #f))) + +;; this help procedure is used 3 or 4 times by sanitize-list-of-options-for-match-configuration +(define (throw-error-duplicate-option option error-arg) + (throw-error error-arg + (list "<opensmtpd-match-configuration>'s fieldname 'options' has two\n" + (string-append "<opensmtpd-option-configuration> records with fieldname 'option' with value '" option "'. \n") + (string-append "You can only have one option with value '" option "' in the options list.\n")))) + +;; this procedure sanitizes the fieldname opensmtpd-match-configuration-options +(define* (sanitize-list-of-options-for-match-configuration %options) + (let loop ([%traversing-options %options] + [%sanitized-options '()]) + (if (null? %traversing-options) + (remove false? + (list + (assoc-ref %sanitized-options "for") + (assoc-ref %sanitized-options "from") + (assoc-ref %sanitized-options "auth") + (assoc-ref %sanitized-options "helo") + (assoc-ref %sanitized-options "mail-from") + (assoc-ref %sanitized-options "rcpt-to") + (assoc-ref %sanitized-options "tag") + (assoc-ref %sanitized-options "tls"))) + (let* ((option-record (car %traversing-options)) + (option-string (opensmtpd-option-configuration-option option-record))) + (cond [(string=? "auth" option-string) + (if (assoc-ref %sanitized-options "auth") + (throw-error-duplicate-option "auth" %traversing-options) + (loop (cdr %traversing-options) (alist-cons "auth" option-record %sanitized-options)))] + [(string=? "helo" option-string) + (cond [(assoc-ref %sanitized-options "helo") + (throw-error-duplicate-option "helo" %traversing-options)] + [(not (opensmtpd-option-configuration-data option-record)) + (throw-error option-record + (list "<opensmtpd-option-configuration> with fieldname 'option' with value 'helo' \n" + "must have a 'data' of type string or <opensmtpd-table-configuration>.\n"))] + [else (loop (cdr %traversing-options) (alist-cons "helo" option-record %sanitized-options))])] + [(string=? "mail-from" option-string) + (cond ((assoc-ref %sanitized-options "mail-from") + (throw-error-duplicate-option "mail-from" %traversing-options)) + ((not (opensmtpd-option-configuration-data option-record)) + (throw-error option-record + (list "<opensmtpd-option-configuration> with fieldname 'option' with value 'mail-from' \n" + "must have a 'data' of type string or <opensmtpd-table-configuration>.\n"))) + (else (loop (cdr %traversing-options) (alist-cons "mail-from" option-record %sanitized-options))))] + [(string=? "rcpt-to" option-string) + (cond [(assoc-ref %sanitized-options "rcpt-to") + (throw-error-duplicate-option "rcpt-to" %traversing-options)] + [(not (opensmtpd-option-configuration-data option-record)) + (throw-error option-record + (list "<opensmtpd-option-configuration> with fieldname 'option' with value 'rcpt-to' \n" + "must have a 'data' of type string or <opensmtpd-table-configuration>.\n"))] + [else (loop (cdr %traversing-options) (alist-cons "rcpt-to" option-record %sanitized-options))])] + [(string=? "tag" option-string) + (cond ((assoc-ref %sanitized-options "tag") + (throw-error-duplicate-option "tag" %traversing-options)) + ((not (string? (opensmtpd-option-configuration-data option-record))) + (throw-error option-record + (list "<opensmtpd-option-configuration> with fieldname 'option' with value 'tag' \n" + "must have a 'data' of type string.\n"))) + (else (loop (cdr %traversing-options) (alist-cons "tag" option-record %sanitized-options))))] + [(string=? "tls" option-string) + (cond [(assoc-ref %sanitized-options "tls") + (throw-error-duplicate-option "tls" %traversing-options)] + [(or (opensmtpd-option-configuration-data option-record) + (opensmtpd-option-configuration-regex option-record)) + (throw-error option-record + (list "<opensmtpd-option-configuration> with fieldname 'option' with value 'tls' \n" + "cannot have a string or table 'data'.\n"))] + [else (loop (cdr %traversing-options) (alist-cons "tls" option-record %sanitized-options))])] + [(string=? "for" (substring option-string 0 3)) + (cond ((assoc-ref %sanitized-options "for") + (throw-error %options + `("<opensmtpd-match-configuration>'s fieldname 'options' can only have one 'for' option. \n" + "But '" ,option-string "' and '" + ,(opensmtpd-option-configuration-option (assoc-ref %sanitized-options "for")) "' are present.\n"))) + ((and (string-in-list? option-string (list "for any" "for local")) ; for any cannot have a data field. + (or (opensmtpd-option-configuration-data option-record) + (opensmtpd-option-configuration-regex option-record))) + (throw-error option-record + (list "When <openmstpd-option-configuration>'s fieldname 'options' value is 'for any' \n" + "or 'for local', then its 'data' and 'regex' field must be #f. \n"))) + ((and (string-in-list? option-string (list "for domain" "for rcpt-to")) ; for domain must have a data field. + (not (opensmtpd-option-configuration-data option-record))) + (throw-error option-record + (list "When <openmstpd-option-configuration>'s fieldname 'options' value is 'for domain' \n" + "or 'for rcpt-to', then its 'data' field must be a string or an \n" + "<opensmtpd-table-configuration> record.\n"))) + (else (loop (cdr %traversing-options) (alist-cons "for" option-record %sanitized-options))))] + [(string=? "from" (substring option-string 0 4)) + (cond ((assoc-ref %sanitized-options "from") + (throw-error %options + `("<opensmtpd-match-configuration>'s fieldname 'options' can only have one 'from' option. \n" + "But '" ,option-string "' and '" + ,(opensmtpd-option-configuration-option (assoc-ref %sanitized-options "from")) "' are present.\n"))) + ((and (string-in-list? option-string (list "from any" "from local" "from socket")) ; for any cannot have a data field. + (or (opensmtpd-option-configuration-data option-record) + (opensmtpd-option-configuration-regex option-record))) + (throw-error option-record + (list "When <openmstpd-option-configuration>'s fieldname 'options' value is 'from any', \n" + " 'from local', or 'from socket', then its 'data' and 'regex' field must be #f. \n"))) + ((and (string-in-list? option-string (list "from mail-from" "from src")) ; for domain must have a data field. + (not (opensmtpd-option-configuration-data option-record))) + (throw-error option-record + (list "When <openmstpd-option-configuration>'s fieldname 'options' value is 'from mail-from' \n" + "or 'from src', then its 'data' field must be a string or an \n" + "<opensmtpd-table-configuration> record.\n"))) + (else (loop (cdr %traversing-options) (alist-cons "from" option-record %sanitized-options))))]))))) + +;; some procedures for <opensmtpd-listen-on-configuration> and +;; <opensmtpd-listen-on-socket-configuration-configuration>. +(define (sanitize-filters %list) + ;; the order of the first two tests in this cond is important. + ;; (false?) has to be 1st and (list-has-duplicates-or-non-filters?) has to be second. + ;; You may optionally re-order the other alternates in the cond. + (cond [(false? %list) + #f] + [(list-has-duplicates-or-non-filters? %list) + (begin + (display (string-append "<opensmtpd-listen-on-configuration> fieldname: 'filters' is a list, in which each unique element \n" + "is of type <opensmtpd-filter-configuration> or <opensmtpd-filter-phase-configuration>.\n")) + (throw 'bad! %list))] + [else + (let loop ([%traversing-list %list] + [%original-list %list]) + (if (null? %traversing-list) + %original-list + (cond + [(opensmtpd-filter-configuration? (car %traversing-list)) + (loop (cdr %traversing-list) %original-list)] + [(filter-phase-has-message-and-value? (car %traversing-list)) + (begin + (display (string-append "<opensmtpd-filter-phase-configuration> cannot have defined fieldnames 'value' \n" + "and 'message'.\n")) + (throw 'bad! (car %traversing-list)))] + [(filter-phase-decision-lacks-proper-message? (car %traversing-list)) + (begin + (display (string-append "<opensmtpd-filter-phase-configuration> fieldname: 'decision' options \n" + "\"disconnect\" and \"reject\" require fieldname 'message' to have a string.\n" + "The 'message' string must be RFC commpliant, which means that the string \n" + "must begin with a 4xx or 5xx status code.\n")) + (throw 'bad! (car %traversing-list)))] + [(filter-phase-lacks-proper-value? (car %traversing-list)) + (begin + (display (string-append "<opensmtpd-filter-phase-configuration> fieldname: 'decision' option \n" + "\"rewrite\" requires fieldname 'value' to have a number.\n")) + (throw 'bad! (car %traversing-list)))] + [(filter-phase-has-incorrect-junk-or-bypass? (car %traversing-list)) + (begin + (display (string-append "<opensmtpd-filter-phase-configuration> fieldname 'decision' option \n" + "\"junk\" or 'bypass' cannot have a defined fieldnames 'message' or 'value'.\n")) + (throw 'bad! (car %traversing-list)))] + [(filter-phase-junks-after-commit? (car %traversing-list)) + (begin + (display (string-append "<opensmtpd-filter-phase-configuration> fieldname 'decision' option \n" + "\"junk\" cannot junk an email during 'phase' \"commit\".\n")) + (throw 'bad! (car %traversing-list)))] + [else (loop (cdr %traversing-list) %original-list)])))])) + +(define (list-has-duplicates-or-non-filters? list) + (not (list-of-unique-filter-or-filter-phase? list))) + +(define (filter-phase-has-message-and-value? record) + (and (opensmtpd-filter-phase-configuration-message record) + (opensmtpd-filter-phase-configuration-value record))) + +;; return #t if phase needs a message. Or if the message did not start with a 4xx or 5xx status code. +;; otherwise #f +(define (filter-phase-decision-lacks-proper-message? record) + (define decision (opensmtpd-filter-phase-configuration-decision record)) + (if (string-in-list? decision (list "disconnect" "reject")) + ;; this message needs to be RFC compliant, meaning + ;; that it need to start with 4xx or 5xx status code + (cond [(eq? #f (opensmtpd-filter-phase-configuration-message record)) + #t] + [(string? (opensmtpd-filter-phase-configuration-message record)) + (let ((number (string->number + (substring + (opensmtpd-filter-phase-configuration-message record) 0 3)))) + (if (and (number? number) + (and (< number 600) (> number 399))) + #f + #t))]) + #f)) + +;; 'decision' "rewrite" requires 'value' to be a number. +(define (filter-phase-lacks-proper-value? record) + (define decision (opensmtpd-filter-phase-configuration-decision record)) + (if (string=? "rewrite" decision) + (if (and (number? (opensmtpd-filter-phase-configuration-value record)) + (eq? #f (opensmtpd-filter-phase-configuration-message record))) + #f + #t) + #f)) + +;; 'decision' "junk" or "bypass" cannot have a message or a value. +(define (filter-phase-has-incorrect-junk-or-bypass? record) + (and + (string-in-list? + (opensmtpd-filter-phase-configuration-decision record) + (list "junk" "bypass")) + (or + (opensmtpd-filter-phase-configuration-value record) + (opensmtpd-filter-phase-configuration-message record)))) + +(define (filter-phase-junks-after-commit? record) + (and (string=? (opensmtpd-filter-phase-configuration-decision record) "junk") + (string=? (opensmtpd-filter-phase-configuration-phase record) "commit"))) + +;; returns #t if list is a unique list of <opensmtpd-filter-configuration> or <opensmtpd-filter-phase-configuration> +;; returns # otherwise +(define (list-of-unique-filter-or-filter-phase? %filters) + (and (list? %filters) + (not (null? %filters)) + ;; this list is made up of only <opensmtpd-filter-phase-configuration> or <opensmtpd-filter-configuration> + (primitive-eval + (cons 'and (map (lambda (filter) + (or (opensmtpd-filter-configuration? filter) + (opensmtpd-filter-phase-configuration? filter))) + %filters))) + (not (contains-duplicate? %filters)))) + +(define (throw-error var %strings) + (display (apply string-append %strings)) + (throw 'bad! var)) + +;; this is used for sanitizing <opensmtpd-filter-phase-configuration> fieldname 'options' +(define (contains-duplicate? list) + (if (null? list) + #f + (or + ;; check if (car list) is in (cdr list) + (primitive-eval (cons 'or + (map (lambda (var) (equal? var (car list))) + (cdr list)))) + ;; check if (cdr list) contains duplicate + (contains-duplicate? (cdr list))))) + +;; given a list and procedure, this tests that each element of list is of type +;; ie: (list-of-type? list string?) tests each list is of type string. +(define (list-of-type? list proc?) + (if (and (list? list) + (not (null? list))) + (let loop ([list list]) + (if (null? list) + #t + (if (proc? (car list)) + (loop (cdr list)) + #f))) + #f)) + +(define (list-of-strings? list) + (list-of-type? list string?)) + +(define (list-of-unique-opensmtpd-option-configuration? list) + (and (list-of-type? + list opensmtpd-option-configuration?) + (not (contains-duplicate? list)))) + +(define (list-of-opensmtpd-ca-configuration? list) + (list-of-type? list opensmtpd-ca-configuration?)) + +(define (list-of-opensmtpd-pki-configuration? list) + (list-of-type? list opensmtpd-pki-configuration?)) + +(define (list-of-opensmtpd-listen-on-configuration? list) + (and (list-of-type? list opensmtpd-listen-on-configuration?) + (not (contains-duplicate? list)))) + +(define (list-of-unique-opensmtpd-match-configuration? list) + (and (list-of-type? list opensmtpd-match-configuration?) + (not (contains-duplicate? list)))) + +(define* (list-of-strings->string list + #:key + (string-delimiter ", ") + (postpend "") + (append "") + (drop-right-number 2)) + (string-drop-right + (string-append (let loop ([list list]) + (if (null? list) + "" + (string-append append (car list) postpend + string-delimiter + (loop (cdr list))))) + append) + drop-right-number)) + +;; at the moment I cannot define this by using list-of-type? +;; the first (not (null? assoc-list)) prevents that. +(define (assoc-list? assoc-list) + (list-of-type? assoc-list (lambda (pair) + (if (and (pair? pair) + (string? (car pair)) + (string? (cdr pair))) + #t + #f)))) + +(define* (variable->string var #:key (append "") (postpend " ")) + (let ([var (if (number? var) + (number->string var) + var)]) + (if var + (string-append append var postpend) + ""))) + +;; this procedure takes in one argument. +;; if that argument is an <opensmtpd-table-configuration> whose fieldname 'values' is an assoc-list, then it returns +;; #t, #f if otherwise. +;; TODO should I remove these two functions? And instead use the (opensmtpd-table-configuration-type) procedure? +(define (table-whose-data-are-assoc-list? table) + (if (not (opensmtpd-table-configuration? table)) + #f + (assoc-list? (opensmtpd-table-configuration-data table)))) + +;; this procedure takes in one argument +;; if that argument is an <opensmtpd-table-configuration> whose fieldname 'values' is a list of strings, then it returns +;; #t, #f if otherwise. +(define (table-whose-data-are-a-list-of-strings? table) + (if (not (opensmtpd-table-configuration? table)) + #f + (list-of-strings? (opensmtpd-table-configuration-data table)))) + +;; these next few functions help me to turn <table>s +;; into strings suitable to fit into "opensmtpd.conf". +(define (assoc-list->string assoc-list) + (string-drop-right + (let loop ([assoc-list assoc-list]) + (if (null? assoc-list) + "" + ;; pair is (cons "hello" "world") -> ("hello" . "world") + (let ([pair (car assoc-list)]) + (string-append + "\"" (car pair) "\"" + " = " + "\"" (cdr pair) "\"" + ", " + (loop (cdr assoc-list)))))) + 2)) + +;; can be of type: (quote list-of-strings) or (quote assoc-list) +(define (opensmtpd-table-configuration->string table) + (string-append "table " (opensmtpd-table-configuration-name table) " " + (let ([type (opensmtpd-table-configuration-type table)]) + (cond [(eq? type (quote list-of-strings)) + (string-append "{ " (list-of-strings->string (opensmtpd-table-configuration-data table) + #:append "\"" + #:drop-right-number 3 + #:postpend "\"") " }")] + [(eq? type (quote assoc-list)) + (string-append "{ " (assoc-list->string (opensmtpd-table-configuration-data table)) " }")] + [(eq? type (quote db)) + (string-append "db:" (opensmtpd-table-configuration-data table))] + [(eq? type (quote file)) + (string-append "file:" (opensmtpd-table-configuration-data table))] + [else (throw 'youMessedUp table)])) + " \n")) + +;; The following functions convert various records into strings. + +(define (opensmtpd-listen-on-configuration->string record) + (string-append "listen on " + (opensmtpd-listen-on-configuration-interface record) " " + (let* ([hostname (opensmtpd-listen-on-configuration-hostname record)] + [hostnames (if (opensmtpd-listen-on-configuration-hostnames record) + (opensmtpd-table-configuration-name (opensmtpd-listen-on-configuration-hostnames record)) + #f)] + [filters (opensmtpd-listen-on-configuration-filters record)] + [filter-name (if filters + (if (< 1 (length filters)) + (generate-filter-chain-name filters) + (if (opensmtpd-filter-configuration? (car filters)) + (opensmtpd-filter-configuration-name (car filters)) + (opensmtpd-filter-phase-configuration-name (car filters)))) + #f)] + [mask-src (opensmtpd-listen-on-configuration-mask-src record)] + [tag (opensmtpd-listen-on-configuration-tag record)] + [secure-connection (opensmtpd-listen-on-configuration-secure-connection record)] + [port (opensmtpd-listen-on-configuration-port record)] + [pki (opensmtpd-listen-on-configuration-pki record)] + [auth (opensmtpd-listen-on-configuration-auth record)] + [auth-optional (opensmtpd-listen-on-configuration-auth-optional record)]) + (string-append + (if mask-src + (string-append "mask-src ") + "") + (variable->string hostname #:append "hostname ") + (variable->string hostnames #:append "hostnames <" #:postpend "> ") + (variable->string filter-name #:append "filter \"" #:postpend "\" ") + (variable->string tag #:append "tag \"" #:postpend "\" ") + (if secure-connection + (cond [(string=? "smtps" secure-connection) + "smtps "] + [(string=? "tls" secure-connection) + "tls "] + [(string=? "tls-require" secure-connection) + "tls-require "] + [(string=? "tls-require-verify" secure-connection) + "tls-require verify "]) + "") + (variable->string port #:append "port " #:postpend " ") + (if pki + (variable->string (opensmtpd-pki-configuration-domain pki) #:append "pki ") + "") + (if auth + (string-append "auth " + (if (opensmtpd-table-configuration? auth) + (string-append "<" (opensmtpd-table-configuration-name auth) "> ") + "")) + "") + (if auth-optional + (string-append "auth-optional " + (if (opensmtpd-table-configuration? auth-optional) + (string-append "<" (opensmtpd-table-configuration-name auth-optional) "> ") + "")) + "") + "\n")))) + +(define (opensmtpd-listen-on-socket-configuration->string record) + (string-append "listen on socket " + (let* ([filters (opensmtpd-listen-on-socket-configuration-configuration-filters record)] + [filter-name (if filters + (if (< 1 (length filters)) + (generate-filter-chain-name filters) + (if (opensmtpd-filter-configuration? (car filters)) + (opensmtpd-filter-configuration-name (car filters)) + (opensmtpd-filter-phase-configuration-name (car filters)))) + #f)] + [mask-src (opensmtpd-listen-on-socket-configuration-configuration-mask-src record)] + [tag (opensmtpd-listen-on-socket-configuration-configuration-tag record)]) + (string-append + (if mask-src + (string-append "mask-src ") + "") + (variable->string filter-name #:append "filter \"" #:postpend "\" ") + (variable->string tag #:append "tag \"" #:postpend "\" ") + "\n")))) + +(define (opensmtpd-action-relay-configuration->string record) + (let ([backup (opensmtpd-action-relay-configuration-backup record)] + [backup-mx (opensmtpd-action-relay-configuration-backup-mx record)] + [helo (opensmtpd-action-relay-configuration-helo record)] + ;; helo-src can either be a string IP address or an <opensmtpd-table-configuration> + [helo-src (if (opensmtpd-action-relay-configuration-helo-src record) + (if (string? (opensmtpd-action-relay-configuration-helo-src record)) + (opensmtpd-action-relay-configuration-helo-src record) + (string-append "<\"" + (opensmtpd-table-configuration-name + (opensmtpd-action-relay-configuration-src record)) + "\">")) + #f)] + [domain (if (opensmtpd-action-relay-configuration-domain record) + (opensmtpd-table-configuration-name + (opensmtpd-action-relay-configuration-domain record)) + #f)] + [host (opensmtpd-action-relay-configuration-host record)] + [name (opensmtpd-action-relay-configuration-name record)] + [pki (if (opensmtpd-action-relay-configuration-pki record) + (opensmtpd-pki-configuration-domain (opensmtpd-action-relay-configuration-pki record)) + #f)] + [srs (opensmtpd-action-relay-configuration-srs record)] + [tls (opensmtpd-action-relay-configuration-tls record)] + [auth (if (opensmtpd-action-relay-configuration-auth record) + (opensmtpd-table-configuration-name + (opensmtpd-action-relay-configuration-auth record)) + #f)] + [mail-from (opensmtpd-action-relay-configuration-mail-from record)] + ;; src can either be a string IP address or an <opensmtpd-table-configuration> + [src (if (opensmtpd-action-relay-configuration-src record) + (if (string? (opensmtpd-action-relay-configuration-src record)) + (opensmtpd-action-relay-configuration-src record) + (string-append "<\"" + (opensmtpd-table-configuration-name + (opensmtpd-action-relay-configuration-src record)) + "\">")) + #f)] + ) + (string-append + "\"" + name + "\" " "relay " + ;;FIXME should I always quote the host fieldname? do I need to quote localhost via "localhost" ? + (variable->string host #:append "host \"" #:postpend "\" ") + (variable->string backup) + (variable->string backup-mx #:append "backup mx ") + (variable->string helo #:append "helo ") + (variable->string helo-src #:append "helo-src ") + (variable->string domain #:append "domain <\"" #:postpend "\"> ") + (variable->string host #:append "host ") + (variable->string pki #:append "pki ") + (variable->string srs) + (variable->string tls #:append "tls ") + (variable->string auth #:append "auth <" #:postpend "> ") + (variable->string mail-from #:append "mail-from ") + (variable->string src #:append "src ") + "\n"))) + +(define (opensmtpd-lmtp-configuration->string record) + (string-append "lmtp " + (opensmtpd-lmtp-configuration-destination record) + (if (opensmtpd-lmtp-configuration-rcpt-to record) + (begin + " " (opensmtpd-lmtp-configuration-rcpt-to record)) + ""))) + +(define (opensmtpd-mda-configuration->string record) + (string-append "mda " + (opensmtpd-mda-configuration-command record) " ")) + +(define (opensmtpd-maildir-configuration->string record) + (string-append "maildir " + "\"" + (if (opensmtpd-maildir-configuration-pathname record) + (opensmtpd-maildir-configuration-pathname record) + "~/Maildir") + "\"" + (if (opensmtpd-maildir-configuration-junk record) + " junk " + " "))) + +(define (opensmtpd-action-local-delivery-configuration->string record) + (let ([name (opensmtpd-action-local-delivery-configuration-name record)] + [method (opensmtpd-action-local-delivery-configuration-method record)] + [alias (if (opensmtpd-action-local-delivery-configuration-alias record) + (opensmtpd-table-configuration-name + (opensmtpd-action-local-delivery-configuration-alias record)) + #f)] + [ttl (opensmtpd-action-local-delivery-configuration-ttl record)] + [user (opensmtpd-action-local-delivery-configuration-user record)] + [userbase (if (opensmtpd-action-local-delivery-configuration-userbase record) + (opensmtpd-table-configuration-name + (opensmtpd-action-local-delivery-configuration-userbase record)) + #f)] + [virtual (if (opensmtpd-action-local-delivery-configuration-virtual record) + (opensmtpd-table-configuration-name + (opensmtpd-action-local-delivery-configuration-virtual record)) + #f)] + [wrapper (opensmtpd-action-local-delivery-configuration-wrapper record)]) + (string-append + "\"" name "\" " + (cond [(string? method) + (string-append method " ")] + [(opensmtpd-mda-configuration? method) + (opensmtpd-mda-configuration->string method)] + [(opensmtpd-lmtp-configuration? method) + (opensmtpd-lmtp-configuration->string method)] + [(opensmtpd-maildir-configuration? method) + (opensmtpd-maildir-configuration->string method)]) + ;; FIXME/TODO support specifying alias file:/path/to/alias-file ? + ;; I do not think that is something that I can do... + (variable->string alias #:append "alias <\"" #:postpend "\"> ") + (variable->string ttl #:append "ttl ") + (variable->string user #:append "user ") + (variable->string userbase #:append "userbase <\"" #:postpend "\"> ") + (variable->string virtual #:append "virtual <" #:postpend "> ") + (variable->string wrapper #:append "wrapper ")))) + +;; this function turns both opensmtpd-action-local-delivery-configuration and +;; opensmtpd-action-relay-configuration into strings. +(define (opensmtpd-action->string record) + (string-append "action " + (cond [(opensmtpd-action-local-delivery-configuration? record) + (opensmtpd-action-local-delivery-configuration->string record)] + [(opensmtpd-action-relay-configuration? record) + (opensmtpd-action-relay-configuration->string record)]) + " \n")) + +;; this turns option records found in <opensmtpd-match-configuration> into strings. +(define* (opensmtpd-option-configuration->string record + #:key + (space-after-! #f)) + (let ([not (opensmtpd-option-configuration-not record)] + [option (opensmtpd-option-configuration-option record)] + [regex (opensmtpd-option-configuration-regex record)] + [data (opensmtpd-option-configuration-data record)]) + (string-append + (if not + (if space-after-! + "! " + "!") + "") + option " " + (if regex + "regex " + "") + (if data + (if (opensmtpd-table-configuration? data) + (string-append "<" (opensmtpd-table-configuration-name data) "> ") + (string-append data " ")) + "")))) + +(define (opensmtpd-match-configuration->string record) + (string-append "match " + (let* ([action (opensmtpd-match-configuration-action record)] + [name (cond [(opensmtpd-action-relay-configuration? action) + (opensmtpd-action-relay-configuration-name action)] + [(opensmtpd-action-local-delivery-configuration? action) + (opensmtpd-action-local-delivery-configuration-name action)] + [else 'reject])] + [options (opensmtpd-match-configuration-options record)]) + (string-append + (if options + (apply string-append + (map opensmtpd-option-configuration->string options)) + "") + (if (string? name) + (string-append "action " "\"" name "\" ") + "reject ") + "\n")))) + +(define (opensmtpd-ca-configuration->string record) + (string-append "ca " (opensmtpd-ca-configuration-name record) " " + "cert \"" (opensmtpd-ca-configuration-file record) "\"\n")) + +(define (opensmtpd-pki-configuration->string record) + (let ([domain (opensmtpd-pki-configuration-domain record)] + [cert (opensmtpd-pki-configuration-cert record)] + [key (opensmtpd-pki-configuration-key record)] + [dhe (opensmtpd-pki-configuration-dhe record)]) + (string-append "pki " domain " " "cert \"" cert "\" \n" + "pki " domain " " "key \"" key "\" \n" + (if dhe + (string-append + "pki " domain " " "dhe " dhe "\n") + "")))) + +(define (generate-filter-chain-name list-of-filters) + (string-drop-right (apply string-append + (flatten + (map (lambda (filter) + (list + (if (opensmtpd-filter-configuration? filter) + (opensmtpd-filter-configuration-name filter) + (opensmtpd-filter-phase-configuration-name filter)) + "-")) + list-of-filters))) + 1)) + +;; this procedure takes in a list of <opensmtpd-filter-configuration> and <opensmtpd-filter-phase-configuration>, +;; returns a string of the form: +;; filter "uniquelyGeneratedName" chain chain { "filter-name", "filter-name2" [, ...]} +(define (opensmtpd-filter-chain->string list-of-filters) + (string-append "filter \"" + (generate-filter-chain-name list-of-filters) + "\" " + "chain {" + (string-drop-right + (apply string-append + (flatten + (map (lambda (filter) + (list + "\"" + (if (opensmtpd-filter-configuration? filter) + (opensmtpd-filter-configuration-name filter) + (opensmtpd-filter-phase-configuration-name filter)) + "\", ")) + list-of-filters)) + ) 2) + "}\n")) + +(define (opensmtpd-filter-phase-configuration->string record) + (let ([name (opensmtpd-filter-phase-configuration-name record)] + [phase (opensmtpd-filter-phase-configuration-phase record)] + [decision (opensmtpd-filter-phase-configuration-decision record)] + [options (opensmtpd-filter-phase-configuration-options record)] + [message (opensmtpd-filter-phase-configuration-message record)] + [value (opensmtpd-filter-phase-configuration-value record)]) + (string-append "filter " + "\"" name "\" " + "phase " phase " " + "match " + (apply string-append ; turn the options into a string + (flatten + (map (lambda (option) + (opensmtpd-option-configuration->string option #:space-after-! #f)) + options))) + " " + decision " " + (if (string-in-list? decision (list "reject" "disconnect")) + (string-append "\"" message "\"") + "") + (if (string=? "rewrite" decision) + (string-append "rewrite " (number->string value)) + "") + "\n"))) + +;; filters elements may be <opensmtpd-filter-configuration>, <opensmtpd-filter-phase-configuration>, +;; and lists that look like (list (opensmtpd-filter-configuration...) (opensmtpd-filter-phase-configuration ...) +;; ...) +;; this function converts it to a string. +;; Consider if a user passed in a valid <opensmtpd-configuration>, whose total valid filters +;; so that (get-opensmtpd-filters (opensmtpd-configuration)) returns +;; look like this: (we will call this list "total filters"): +;; (list (opensmtpd-filter +;; (name "rspamd") +;; (proc "rspamd")) +;; (list (opensmtpd-filter-phase-configuration ; this is a listen-on, with a filter-chain. +;; (name "dkimsign") +;; ...) +;; (opensmtpd-filter +;; (name "rspamd") +;; (proc "rspamd")))) +;; +;; did you notice that filter "rspamd" is listed twice? How do you make sure that it is NOT +;; printed twice in smtpd.conf? +;; 1st flatten "total filters", then remove its duplicates. Then print all of those filters. +;; 2nd now we go through "total filters", and we only print the non-filter-chains. +(define (opensmtpd-filters->string filters) + ;; first display the unique <opensmtpd-filter-configuration>s. and <opensmtpd-filter-phase-configuration>s. + ;; to do this: flatten filters, then remove duplicates. + (string-append + (apply string-append + (map (lambda (filter) + (cond ((opensmtpd-filter-phase-configuration? filter) + (opensmtpd-filter-phase-configuration->string filter)) + (else ; you are a <opensmtpd-filter-configuration> + (string-append "filter " + "\"" (opensmtpd-filter-configuration-name filter) "\" " + (if (opensmtpd-filter-exec filter) + "proc-exec " + "proc ") + "\"" (opensmtpd-filter-configuration-proc filter) "\"" + "\n")))) + (delete-duplicates (flatten filters)))) + ;; now we have to print the filter chains. + (apply string-append + (remove boolean? + (map (lambda (filter) + (cond ((list? filter) + (opensmtpd-filter-chain->string filter)) + (else ; you are a <opensmtpd-filter-configuration> + #f))) + filters))))) + +(define (opensmtpd-configuration-listen->string string) + (string-append + "include \"" string "\"\n")) + +(define (opensmtpd-configuration-srs->string record) + (let ([key (opensmtpd-srs-configuration-key record)] + [backup-key (opensmtpd-srs-configuration-backup-key record)] + [ttl-delay (opensmtpd-srs-configuration-ttl-delay record)]) + (string-append + (variable->string key #:append "srs key " #:postpend "\n") + (variable->string backup-key #:append "srs key backup " #:postpend "\n") + (variable->string ttl-delay #:append "srs ttl " #:postpend "\n") + "\n"))) + +;; TODO make sure all options here work! I just fixed limit-max-rcpt! +(define (opensmtpd-smtp-configuration->string record) + (let ([ciphers (opensmtpd-smtp-configuration-ciphers record)] + [limit-max-mails (opensmtpd-smtp-configuration-limit-max-mails record)] + [limit-max-rcpt (opensmtpd-smtp-configuration-limit-max-rcpt record)] + [max-message-size (opensmtpd-smtp-configuration-max-message-size record)] + [sub-addr-delim (opensmtpd-smtp-configuration-sub-addr-delim record)]) + (string-append + (variable->string ciphers #:append "smtp ciphers " #:postpend "\n") + (variable->string limit-max-mails #:append "smtp limit max-mails " #:postpend "\n") + (variable->string limit-max-rcpt #:append "smtp limit max-rcpt " #:postpend "\n") + (variable->string max-message-size #:append "smtp max-message-size " #:postpend "\n") + (variable->string sub-addr-delim #:append "smtp sub-addr-delim " #:postpend "\n") + "\n"))) + +(define (opensmtpd-configuration-queue->string record) + (let ([compression (opensmtpd-queue-configuration-compression record)] + [encryption (opensmtpd-queue-configuration-encryption record)] + [ttl-delay (opensmtpd-queue-configuration-ttl-delay record)]) + (string-append + (if compression + "queue compression\n" + "") + (if encryption + (string-append + "queue encryption " + (if (not (boolean? encryption)) + encryption + "") + "\n") + "") + (if ttl-delay + (string-append "queue ttl" ttl-delay "\n") + "")))) + +;; build a list of <opensmtpd-action> from +;; opensmtpd-configuration-matches, which is a list of <opensmtpd-match-configuration>. +;; Each <opensmtpd-match-configuration> has a fieldname 'action', which accepts an <opensmtpd-action>. +(define (get-opensmtpd-actions record) + (define opensmtpd-actions + (let loop ([list (opensmtpd-configuration-matches record)]) + (if (null? list) + '() + (cons (opensmtpd-match-configuration-action (car list)) + (loop (cdr list)))))) + (delete-duplicates (append opensmtpd-actions))) + +;; build a list of opensmtpd-pki-configurations from +;; opensmtpd-configuration-listen-ons and +;; get-opensmtpd-actions +(define (get-opensmtpd-pki-configurations record) + ;; TODO/FIXME/maybe/wishlist could get-opensmtpd-actions -> NOT have an opensmtpd-action-relay-configuration? + ;; I think so. And if it did NOT have a relay configuration, then action-pkis would be '() when + ;; it needs to be #f. because if the opensmtpd-configuration has NO pkis, then this function will + ;; return '(), when it should return #f. If it returns '(), then opensmtpd-configuration-fieldname->string will + ;; print the string "\n" instead of "" + (define action-pkis + (let loop1 ([list (get-opensmtpd-actions record)]) + (if (null? list) + '() + (if (and (opensmtpd-action-relay-configuration? (car list)) + (opensmtpd-action-relay-configuration-pki (car list))) + (cons (opensmtpd-action-relay-configuration-pki (car list)) + (loop1 (cdr list))) + (loop1 (cdr list)))))) + ;; FIXME/TODO/maybe/wishlist + ;; this could be #f aka left blank. aka there are no listen-ons records with pkis. + ;; aka there are no lines in the configuration like: + ;; listen on eth0 tls pki smtp.gnucode.me in that case the smtpd.conf will have an extra "\n" + (define listen-on-pkis + (let loop2 ([list (opensmtpd-configuration-listen-ons record)]) + (if (null? list) + '() + (if (opensmtpd-listen-on-configuration-pki (car list)) + (cons (opensmtpd-listen-on-configuration-pki (car list)) + (loop2 (cdr list))) + (loop2 (cdr list)))))) + (delete-duplicates (append action-pkis listen-on-pkis))) + +;; takes in a <opensmtpd-configuration> and returns a list whose elements are <opensmtpd-filter-configuration>, +;; <opensmtpd-filter-phase-configuration>, and a filter-chain. +;; It returns a list of <opensmtpd-filter-configuration> and/or <opensmtpd-filter-phase-configuration> +;; here's an example of what this procedure might return: +;; (list (opensmtpd-filter-configuration...) (opensmtpd-filter-phase-configuration ...) +;; (openmstpd-filter ...) (opensmtpd-filter-phase-configuration ...) +;; ;; this next list is a filter-chain. +;; (list (opensmtpd-filter-phase-configuration ...) (opensmtpd-filter-configuration...))) +;; +;; This procedure handles filter chains a little odd. +(define (get-opensmtpd-filters record) + (define list-of-listen-on-records (if (opensmtpd-configuration-listen-ons record) + (opensmtpd-configuration-listen-ons record) + '())) + + (define listen-on-socket-filters + (if (opensmtpd-listen-on-socket-configuration-configuration-filters (opensmtpd-configuration-listen-on-socket record)) + (opensmtpd-listen-on-socket-configuration-configuration-filters (opensmtpd-configuration-listen-on-socket record)) + '())) + + (delete-duplicates + (append (remove boolean? + (map-in-order (lambda (listen-on-record) ; get the filters found in the <listen-on-record>s + (if (and (opensmtpd-listen-on-configuration-filters listen-on-record) + (= 1 (length (opensmtpd-listen-on-configuration-filters + listen-on-record)))) + (car (opensmtpd-listen-on-configuration-filters listen-on-record)) + (opensmtpd-listen-on-configuration-filters listen-on-record))) + list-of-listen-on-records)) + listen-on-socket-filters))) + +(define (flatten . lst) + "Return a list that recursively concatenates all sub-lists of LST." + (define (flatten1 head out) + (if (list? head) + (fold-right flatten1 out head) + (cons head out))) + (fold-right flatten1 '() lst)) + +;; This function takes in a record, or list, or anything, and returns +;; a list of <opensmtpd-table-configuration>s assuming the thing you passed into it had +;; any <opensmtpd-table-configuration>s. +;; +;; is object record? call func on it's fieldnames +;; is object list? loop through it's fieldnames calling func on it's records +;; is object #f or string? or '()? -> #f +(define (get-opensmtpd-tables value) + (delete-duplicates + (remove boolean? (flatten ;; turn (list '(1) '(2 '(3))) -> '(1 2 3) + (cond ((opensmtpd-table-configuration? value) + value) + ((record? value) + (let* ([record-type (record-type-descriptor value)] + [list-of-record-fieldnames (record-type-fields record-type)]) + (map (lambda (fieldname) + (get-opensmtpd-tables ((record-accessor record-type fieldname) value))) + list-of-record-fieldnames))) + ((and (list? value) (not (null? value))) + (map get-opensmtpd-tables value)) + (else #f)))))) + +(define (opensmtpd-configuration-fieldname->string record fieldname-accessor record->string) + (if (fieldname-accessor record) + (begin + (string-append + (list-of-records->string (fieldname-accessor record) record->string) "\n")) + "")) + +(define (list-of-records->string list-of-records record->string) + (string-append + (cond [(not (list? list-of-records)) + (record->string list-of-records)] + [else + (let loop ([list list-of-records]) + (if (null? list) + "" + (string-append + (record->string (car list)) + (loop (cdr list)))))]))) + + +;; FIXME/TODO should I use format here srfi-28 ? +;; web.scm nginx does a (format #f "string" "another string") +;; this could be a list like (list (file-append opensmtpd-dkimsign "/libexec/filter") "-d gnucode.me -s /path/to/selector.cert") +;; Then opensmtpd-configuration->mixed-text-file could be rewritten to be something like +;; (mixed-text-file (eval `(string-append (opensmtpd-configuration-fieldname->string ...)) (gnu services mail))) +(define (opensmtpd-configuration->mixed-text-file record) + ;; should I use this named let, or should I give this a name, or not use it at all... + ;; eg: (write-all-fieldnames (list (cons fieldname fieldname->string) (cons fieldname2 fieldname->string))) + ;; (let loop ([list (list (cons opensmtpd-configuration-includes (lambda (string) + ;; (string-append + ;; "include \"" string "\"\n"))) + ;; (cons opensmtpd-configuration-smtp opensmtpd-smtp->string) + ;; (cons opensmtpd-configuration-srs opensmtpd-srs->string))]) + ;; (if (null? list) + ;; "" + ;; (string-append (opensmtpd-configuration-fieldname->string record + ;; (caar list) + ;; (cdar list)) + ;; (loop (cdr list))))) + + ;;(mixed-text-file "opensmtpd.conf") + (string-append + ;; write out the includes + (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-includes + opensmtpd-configuration-listen->string) + (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-bounce + (lambda (%bounce) + (if %bounce + (list-of-strings->string %bounce) + ""))) + (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-smtp + opensmtpd-smtp-configuration->string) + (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-srs + opensmtpd-configuration-srs->string) + (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-queue + opensmtpd-configuration-queue->string) + ;; write out the mta-max-deferred + (opensmtpd-configuration-fieldname->string + record opensmtpd-configuration-mta-max-deferred + (lambda (var) + (string-append "mta max-deferred " + (number->string (opensmtpd-configuration-mta-max-deferred record)) "\n"))) + ;;write out all the tables + (opensmtpd-configuration-fieldname->string record get-opensmtpd-tables opensmtpd-table-configuration->string) + ;; TODO should I change the below line of code into these two lines of code? + ;;(opensmtpd-configuration-fieldname->string record get-opensmtpd-filters-and-filter-phases opensmtpd-filter-and-filter-phase->string) + ;;(opensmtpd-configuration-fieldname->string record get-opensmtpd-filter-chains opensmtpd-filter-chain->string) + ;; write out all the filters + (opensmtpd-filters->string (get-opensmtpd-filters record)) + ;; write out all the cas + (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-cas opensmtpd-ca-configuration->string) + ;; write out all the pkis + (opensmtpd-configuration-fieldname->string record get-opensmtpd-pki-configurations opensmtpd-pki-configuration->string) + ;; write all of the listen-on-records + (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-listen-ons + opensmtpd-listen-on-configuration->string) + (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-listen-on-socket + opensmtpd-listen-on-socket-configuration->string) + ;; write all the actions + (opensmtpd-configuration-fieldname->string record get-opensmtpd-actions + opensmtpd-action->string) + ;; write all of the matches + (opensmtpd-configuration-fieldname->string record opensmtpd-configuration-matches opensmtpd-match-configuration->string))) + (define %default-opensmtpd-config-file (plain-file "smtpd.conf" " -- 2.36.1
Joshua Branson <jbranso@HIDDEN>
:guix-patches@HIDDEN
.
Full text available.guix-patches@HIDDEN
:bug#56046
; Package guix-patches
.
Full text available.
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997 nCipher Corporation Ltd,
1994-97 Ian Jackson.