]> git.ipfire.org Git - thirdparty/mlmmj.git/commitdiff
subrelease: finish the implementation started in 2012
authorBaptiste Daroussin <bapt@FreeBSD.org>
Sat, 14 Mar 2026 21:35:04 +0000 (22:35 +0100)
committerBaptiste Daroussin <bapt@FreeBSD.org>
Sat, 14 Mar 2026 22:34:56 +0000 (23:34 +0100)
README.listtexts.md
include/listcontrol.h
src/listcontrol.c
src/mlmmj-process.c
tests/mlmmj-receive.in

index 5f103d7f3d952c1041eae8f3080e8c70c320b03d..df895ea39b53f2f0700fa43fcc5aacced2162557 100644 (file)
@@ -464,6 +464,11 @@ Unformatted substitutions that are available are:
   listname+subscribe-digest@domain.tld
   DEPRECATED: use $list+$subscribe-digest@$domain$ instead
 
+- $digestsubreleaseaddr$
+  (available only in deny-post-subonlypost and wait-post-modnonsubposts)
+  the address to which to send mail to simultaneously subscribe to the digest
+  version of the list and release the post in question
+
 - $digestthreads$
   (available only in digest)
   the formatted list of threads included in the digest
@@ -508,6 +513,11 @@ Unformatted substitutions that are available are:
   listname+subscribe@domain.tld
   DEPRECATED: use $list+$subscribe@$domain$ instead
 
+- $listsubreleaseaddr$
+  (available only in deny-post-subonlypost and wait-post-modnonsubposts)
+  the address to which to send mail to simultaneously subscribe to the list
+  and release the post in question
+
 - $listunsubaddr$
   listname+unsubscribe@domain.tld
   DEPRECATED: use $list+$unsubscribe@$domain$ instead
@@ -536,6 +546,11 @@ Unformatted substitutions that are available are:
   listname+subscribe-nomail@domain.tld
   DEPRECATED: use $list+$subscribe-nomail@$domain$ instead
 
+- $nomailsubreleaseaddr$
+  (available only in deny-post-subonlypost and wait-post-modnonsubposts)
+  the address to which to send mail to simultaneously subscribe to the no-mail
+  version of the list and release the post in question
+
 - $nomailunsubaddr$
   listname+unsubscribe-nomail@domain.tld
   DEPRECATED: use $list+$unsubscribe-nomail@$domain$ instead
index 98d53ce739681b7f1266b4ee9abd2ce756ed36c8..c559c58433e025a55885b790e5a20549d9a9ba1f 100644 (file)
@@ -42,6 +42,9 @@ enum ctrl_e {
        CTRL_CONFUNSUB_NOMAIL,
        CTRL_CONFUNSUB,
        CTRL_BOUNCES,
+       CTRL_SUBRELEASE,
+       CTRL_DIGESTSUBRELEASE,
+       CTRL_NOMAILSUBRELEASE,
        CTRL_RELEASE,
        CTRL_REJECT,
        CTRL_PERMIT,
index 96cbb1c8d39e148b63e03697c400a3ad4c5d5208..6ea9e01339cdbf3810fd85acaec3853d2cb75bfb 100644 (file)
@@ -72,6 +72,9 @@ static struct ctrl_command ctrl_commands[] = {
        { "confunsub-nomail",   true , false, true , CTRL_CONFUNSUB_NOMAIL },
        { "confunsub",          true , false, true , CTRL_CONFUNSUB },
        { "bounces",            true , true , true , CTRL_BOUNCES },
+       { "subrelease",         true , true , true , CTRL_SUBRELEASE },
+       { "digestsubrelease",   true , true , true , CTRL_DIGESTSUBRELEASE },
+       { "nomailsubrelease",   true , true , true , CTRL_NOMAILSUBRELEASE },
        { "release",            true , true , true , CTRL_RELEASE },
        { "reject",             true , true , true , CTRL_REJECT },
        { "permit",             true , true , true , CTRL_PERMIT },
@@ -400,6 +403,20 @@ int listcontrol(strlist *fromemails, struct ml *ml, const char *controlstr,
                exit(EXIT_SUCCESS);
                break;
 
+       /* listname+subrelease-COOKIE@domain.tld */
+       case CTRL_SUBRELEASE:
+               ts = SUB_NORMAL;
+               /* fallthrough */
+       /* listname+digestsubrelease-COOKIE@domain.tld */
+       case CTRL_DIGESTSUBRELEASE:
+               if (ts == SUB_NONE)
+                       ts = SUB_DIGEST;
+               /* fallthrough */
+       /* listname+nomailsubrelease-COOKIE@domain.tld */
+       case CTRL_NOMAILSUBRELEASE:
+               if (ts == SUB_NONE)
+                       ts = SUB_NOMAIL;
+               /* fallthrough */
        /* listname+release-COOKIE@domain.tld */
        case CTRL_RELEASE:
        /* DEPRECATED: listname+moderate-COOKIE@domain.tld */
@@ -437,8 +454,15 @@ int listcontrol(strlist *fromemails, struct ml *ml, const char *controlstr,
                        unlinkat(ml->fd, omitfilename, 0);
                free(omitfilename);
                free(moderatefilename);
-               bool autosubscribe = statctrl(ml->ctrlfd, "autosubscribe");
-               if (autosubscribe) {
+               if (ts != SUB_NONE) {
+                       /* subrelease: always subscribe with requested type */
+                       int mfd = openat(ml->fd, sendfilename, O_RDONLY);
+                       if (mfd == -1) {
+                               free(sendfilename);
+                               return (-1);
+                       }
+                       autosubscribe_sender(ml, mfd, ts);
+               } else if (statctrl(ml->ctrlfd, "autosubscribe")) {
                        int mfd = openat(ml->fd, sendfilename, O_RDONLY);
                        if (mfd == -1) {
                                free(sendfilename);
index 741359dfa3be79c8b199932ff6817fe736c637be..099f12dd54778c2331c2be5f741ab5b315f6506f 100644 (file)
@@ -222,7 +222,31 @@ static void newmoderated(struct ml *ml, const char *mailfilename,
                register_formatted(txt, "moderators",
                                rewind_memory_lines, get_memory_line, mls);
                register_originalmail(txt, mailfilename);
-               qfname = prepstdreply(txt, ml, "$listowner$", efromsender, NULL);
+
+               char *subreplyto = NULL;
+               if (modreason == MODNONSUBPOSTS &&
+                   !statctrl(ml->ctrlfd, "closedlist") &&
+                   !statctrl(ml->ctrlfd, "closedlistsub")) {
+                       char *listsubreleaseaddr;
+                       char *digestsubreleaseaddr;
+                       char *nomailsubreleaseaddr;
+                       gen_addr_cookie(listsubreleaseaddr, ml,
+                           "subrelease-", mailbasename);
+                       gen_addr_cookie(digestsubreleaseaddr, ml,
+                           "digestsubrelease-", mailbasename);
+                       gen_addr_cookie(nomailsubreleaseaddr, ml,
+                           "nomailsubrelease-", mailbasename);
+                       register_unformatted(txt,
+                           "listsubreleaseaddr", listsubreleaseaddr);
+                       register_unformatted(txt,
+                           "digestsubreleaseaddr", digestsubreleaseaddr);
+                       register_unformatted(txt,
+                           "nomailsubreleaseaddr", nomailsubreleaseaddr);
+                       subreplyto = listsubreleaseaddr;
+               }
+
+               qfname = prepstdreply(txt, ml, "$listowner$", efromsender,
+                   subreplyto);
                MY_ASSERT(qfname);
                close_text(txt);
 
@@ -817,11 +841,52 @@ int main(int argc, char **argv)
                            register_unformatted(txt, "subject", subject);
                            register_unformatted(txt, "posteraddr", testaddr);
                            register_originalmail(txt, donemailname);
+
+                           bool closedlist = statctrl(ml.ctrlfd, "closedlist");
+                           bool closedlistsub = statctrl(ml.ctrlfd, "closedlistsub");
+                           char *subreplyto = NULL;
+
+                           if (subonlypost && !closedlist && !closedlistsub) {
+                               char *modname;
+                               char *listsubreleaseaddr;
+                               char *digestsubreleaseaddr;
+                               char *nomailsubreleaseaddr;
+                               xasprintf(&modname, "%s/moderation/%s",
+                                   ml.dir, randomstr);
+                               if (rename(donemailname, modname) != 0) {
+                                       log_error(LOG_ARGS,
+                                           "could not rename(%s,%s)",
+                                           donemailname, modname);
+                                       free(modname);
+                                       close_text(txt);
+                                       free_parsed_hdrs(readhdrs, fromemails,
+                                           originalfromemails, toemails,
+                                           ccemails, rpemails, dtemails,
+                                           allheaders);
+                                       exit(EXIT_FAILURE);
+                               }
+                               free(modname);
+                               gen_addr_cookie(listsubreleaseaddr, &ml,
+                                   "subrelease-", randomstr);
+                               gen_addr_cookie(digestsubreleaseaddr, &ml,
+                                   "digestsubrelease-", randomstr);
+                               gen_addr_cookie(nomailsubreleaseaddr, &ml,
+                                   "nomailsubrelease-", randomstr);
+                               register_unformatted(txt,
+                                   "listsubreleaseaddr", listsubreleaseaddr);
+                               register_unformatted(txt,
+                                   "digestsubreleaseaddr", digestsubreleaseaddr);
+                               register_unformatted(txt,
+                                   "nomailsubreleaseaddr", nomailsubreleaseaddr);
+                               subreplyto = listsubreleaseaddr;
+                           }
+
                            queuefilename = prepstdreply(txt, &ml,
-                                   "$listowner$", testaddr, NULL);
+                                   "$listowner$", testaddr, subreplyto);
                            MY_ASSERT(queuefilename)
                            close_text(txt);
-                           unlink(donemailname);
+                           if (subreplyto == NULL)
+                               unlink(donemailname);
                            free(donemailname);
                            send_help(&ml, queuefilename, testaddr);
                        }
index ec76c2aa6ef80341391fc0ae445cdbdddf650505..7c43ffb5b9d5e8cfd0e2c976e98894e4554f657d 100644 (file)
@@ -34,7 +34,8 @@ tests_init \
        customheaders_with_subst \
        verp \
        normal_email_with_dot \
-       multi_line_headers
+       multi_line_headers \
+       subrelease
 
 mlmmjreceive=$(command -v mlmmj-receive)
 
@@ -3372,3 +3373,90 @@ QUIT
 EOF
        atf_check -o file:expected-2.txt sed -e "/^Message-ID:/d; /^Date:/d;" mail-2.txt
 }
+
+subrelease_body()
+{
+       init_ml list
+       rmdir list/text
+       ln -s ${top_srcdir}/listtexts/en list/text
+       echo test@mlmmjtest > list/control/listaddress
+       start_fakesmtp list
+       echo "heloname" > list/control/smtphelo
+
+       touch list/control/subonlypost
+       printf "user@test\nuser2@test" > list/subscribers.d/u
+
+cat > first <<EOF
+From: bob@test
+To: test@mlmmjtest
+Return-path: bob@test
+Subject: yeah
+
+Let's go, first email
+EOF
+       atf_check -s exit:0 $mlmmjreceive -L list -F <first
+
+       # Verify the mail was held in moderation
+       var=$(ls list/moderation/)
+       if [ -z "$var" ]; then
+               atf_fail "No file in moderation directory"
+       fi
+
+       # Verify the deny-post mail contains subrelease addresses
+       atf_check -o match:"test\+subrelease-${var}@mlmmjtest" cat mail-1.txt
+       atf_check -o match:"test\+digestsubrelease-${var}@mlmmjtest" cat mail-1.txt
+       atf_check -o match:"test\+nomailsubrelease-${var}@mlmmjtest" cat mail-1.txt
+       # Verify Reply-To is set to subrelease address
+       atf_check -o match:"Reply-To: test\+subrelease-${var}@mlmmjtest" cat mail-1.txt
+
+       # Now send to the subrelease address to subscribe and release
+cat > subrel <<EOF
+From: bob@test
+To: test+subrelease-${var}@mlmmjtest
+Return-path: bob@test
+EOF
+       atf_check -s exit:0 $mlmmjreceive -L list -F <subrel
+
+       # Verify the sender is subscribed
+       atf_check -o match:"bob@test" cat list/subscribers.d/b
+
+       # Verify the post was released (archived)
+       atf_check -s exit:0 test -f list/archive/1
+
+       # Verify the released mail was sent to subscribers (including bob who just subscribed)
+       cat > expected-release.txt <<EOF
+EHLO heloname\r
+MAIL FROM:<test+bounces-1-user=test@mlmmjtest>\r
+RCPT TO:<user@test>\r
+DATA\r
+From: bob@test\r
+To: test@mlmmjtest\r
+Subject: yeah\r
+\r
+Let's go, first email\r
+\r
+.\r
+MAIL FROM:<test+bounces-1-user2=test@mlmmjtest>\r
+RCPT TO:<user2@test>\r
+DATA\r
+From: bob@test\r
+To: test@mlmmjtest\r
+Subject: yeah\r
+\r
+Let's go, first email\r
+\r
+.\r
+MAIL FROM:<test+bounces-1-bob=test@mlmmjtest>\r
+RCPT TO:<bob@test>\r
+DATA\r
+From: bob@test\r
+To: test@mlmmjtest\r
+Subject: yeah\r
+\r
+Let's go, first email\r
+\r
+.\r
+QUIT\r
+EOF
+       atf_check -o file:expected-release.txt sed -e "/^Message-ID:/d; /^Date:/d;" mail-2.txt
+}