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.