changeset 338:60da8d3ef63e

added digest support
author mortenp
date Tue, 14 Sep 2004 09:01:32 +1000
parents 30a300660a9a
children 6ec796e27f54
files README TUNABLES include/mlmmj-maintd.h include/mlmmj-sub.h include/mlmmj-unsub.h include/mlmmj.h include/send_digest.h listtexts/notifysub-digest listtexts/notifyunsub-digest listtexts/sub-confirm-digest listtexts/sub-ok-digest listtexts/unsub-confirm-digest listtexts/unsub-ok-digest src/Makefile.am src/listcontrol.c src/log_error.c src/mlmmj-maintd.c src/mlmmj-make-ml.sh src/mlmmj-send.c src/mlmmj-sub.c src/mlmmj-unsub.c src/send_digest.c src/subscriberfuncs.c
diffstat 23 files changed, 853 insertions(+), 164 deletions(-) [+]
line wrap: on
line diff
--- a/README	Tue Sep 14 08:47:55 2004 +1000
+++ b/README	Tue Sep 14 09:01:32 2004 +1000
@@ -16,6 +16,7 @@
  · Regular expression access control
  · Functionality to retrieve old posts
  · Web-interface
+ · Digests
 
 To use mlmmj, do the following:
 
--- a/TUNABLES	Tue Sep 14 08:47:55 2004 +1000
+++ b/TUNABLES	Tue Sep 14 09:01:32 2004 +1000
@@ -78,6 +78,17 @@
    If this file is present, the owner(s) will get a mail with the address of
    someone sub/unsubscribing to a mailinglist.
 
+ · digestinterval		(normal)
+
+   This file specifies how many seconds will pass before the next digest is
+   sent. Defaults to 604800 seconds, which is 7 days.
+
+ · digestmaxmails		(normal)
+
+   This file specifies how many mails can accumulate before digest sending is
+   triggered. Defaults to 50 mails, meaning that if 50 mails arrive to the list
+   before digestinterval have passed, the digest is delivered.
+
  · bouncelife			(normal)
 
    Here is specified for how long time in seconds an address can bounce before
--- a/include/mlmmj-maintd.h	Tue Sep 14 08:47:55 2004 +1000
+++ b/include/mlmmj-maintd.h	Tue Sep 14 09:01:32 2004 +1000
@@ -34,6 +34,7 @@
 int clean_nolongerbouncing(const char *listdir);
 int probe_bouncers(const char *listdir, const char *mlmmjbounce);
 int unsub_bouncers(const char *listdir, const char *mlmmjunsub);
+int run_digests(const char *listdir, const char *mlmmjsend);
 
 /* I know the below is nasty, but it requires C99 to have multiple
  * argument macros, and this would then be the only thing needing
--- a/include/mlmmj-sub.h	Tue Sep 14 08:47:55 2004 +1000
+++ b/include/mlmmj-sub.h	Tue Sep 14 09:01:32 2004 +1000
@@ -25,8 +25,9 @@
 #define MLMMJ_SUBSCRIBE_H
 
 void confirm_sub(const char *listdir, const char *listaddr,
-		 const char *subaddr, const char *mlmmjsend);
+		 const char *subaddr, const char *mlmmjsend, int digest);
 void generate_subconfirm(const char *listdir, const char *listadr,
-		const char *subaddr, const char *mlmmjsend);
+		const char *subaddr, const char *mlmmjsend,
+		int digest);
 
 #endif /* MLMMJ_SUBSCRIBE_H */
--- a/include/mlmmj-unsub.h	Tue Sep 14 08:47:55 2004 +1000
+++ b/include/mlmmj-unsub.h	Tue Sep 14 09:01:32 2004 +1000
@@ -27,9 +27,10 @@
 #include <sys/types.h>
 
 void confirm_unsub(const char *listdir, const char *listaddr,
-		   const char *subaddr, const char *mlmmj);
+		   const char *subaddr, const char *mlmmj, int digest);
 ssize_t unsubscribe(int subreadfd, int subwritefd, const char *address);
 void generate_unsubconfirm(const char *listdir, const char *listaddr,
-			   const char *subaddr, const char *mlmmjsend);
+			   const char *subaddr, const char *mlmmjsend,
+			   int digest);
 
 #endif /* MLMMJ_UNSUBSCRIBE_H */
--- a/include/mlmmj.h	Tue Sep 14 08:47:55 2004 +1000
+++ b/include/mlmmj.h	Tue Sep 14 09:01:32 2004 +1000
@@ -28,7 +28,7 @@
 
 #define RELAYHOST "127.0.0.1"
 #define READ_BUFSIZE 2048
-#define RECIPDELIM '+'
+#define RECIPDELIM '+'  /* XXX Warning: not changable at the moment */
 #define MODREQLIFE 604800 /* How long time will moderation requests be kept?
 			   * 604800s is 7 days */
 #define DISCARDEDLIFE 604800 /* How long time will discarded mails be kept?
@@ -47,6 +47,12 @@
 
 #define MEMORYMAILSIZE 16384  /* How big can a mail be before we don't want to
 			         it in memory? control/memorymailsize */
+#define DIGESTINTERVAL 604800  /* How long do we collect mails for digests
+				* 604800s is 7 days */
+#define DIGESTMAXMAILS 50 /* How many mails can accumulate before we send the
+			   * digest */
+#define DIGESTMIMETYPE "digest" /* Which sub-type of multipart to use when
+				 * sending digest mails */
 
 struct strlist {
 	int count;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/include/send_digest.h	Tue Sep 14 09:01:32 2004 +1000
@@ -0,0 +1,30 @@
+/* Copyright (C) 2004 Morten K. Poulsen <morten at afdelingp.dk>
+ *
+ * $Id$
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef SEND_DIGEST_H
+#define SEND_DIGEST_H
+
+int send_digest(const char *listdir, int lastindex, int index,
+		const char *addr, const char *mlmmjsend);
+
+#endif /* SEND_DIGEST_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/listtexts/notifysub-digest	Tue Sep 14 09:01:32 2004 +1000
@@ -0,0 +1,10 @@
+Hi, this is the mlmmj program managing the mailinglist
+
+*LSTADDR*
+
+
+The following address has just subscribed to the digest of the
+mailinglist:
+
+*SUBADDR*
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/listtexts/notifyunsub-digest	Tue Sep 14 09:01:32 2004 +1000
@@ -0,0 +1,10 @@
+Hi, this is the mlmmj program managing the mailinglist
+
+*LSTADDR*
+
+
+The following address has just unsubscribed from the digest of
+mailinglist:
+
+*SUBADDR*
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/listtexts/sub-confirm-digest	Tue Sep 14 09:01:32 2004 +1000
@@ -0,0 +1,21 @@
+Hi, this is the mlmmj program managing the mailinglist
+
+*LSTADDR*
+
+
+To confirm you want the address
+
+*SUBADDR*
+
+
+added to the digest of this list, please send a reply to
+
+*CNFADDR*
+
+
+Your mailer probably automatically replies to this address, when you hit
+the reply button.
+
+This confirmation serves two purposes. It tests that mail can be sent to your
+address. Second, it makes sure someone else did not try and subscribe your
+emailaddress.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/listtexts/sub-ok-digest	Tue Sep 14 09:01:32 2004 +1000
@@ -0,0 +1,6 @@
+WELCOME! You have been subscribed to the digest of the
+
+*LSTADDR*
+
+
+mailinglist.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/listtexts/unsub-confirm-digest	Tue Sep 14 09:01:32 2004 +1000
@@ -0,0 +1,21 @@
+Hi, this is the mlmmj program managing the mailinglist
+
+*LSTADDR*
+
+
+To confirm you want the address
+
+*SUBADDR*
+
+
+removed from the digest of this list, please send a reply to
+
+*CNFADDR*
+
+
+Your mailer probably automatically replies to this address, when you hit
+the reply button.
+
+If you're not subscribed with this list, you will recieve no reply. You can
+see in the From header of a mail to the mailinglist which mail you're sub-
+scribed with.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/listtexts/unsub-ok-digest	Tue Sep 14 09:01:32 2004 +1000
@@ -0,0 +1,6 @@
+GOODBYE! You have been removed from the digest of the
+
+*LSTADDR*
+
+
+mailinglist.
--- a/src/Makefile.am	Tue Sep 14 08:47:55 2004 +1000
+++ b/src/Makefile.am	Tue Sep 14 09:01:32 2004 +1000
@@ -47,4 +47,5 @@
 
 mlmmj_maintd_SOURCES = mlmmj-maintd.c print-version.c log_error.c mygetline.c \
 		       strgen.c random-int.c chomp.c writen.c memory.c \
-		       ctrlvalue.c
+		       ctrlvalue.c send_digest.c getlistaddr.c dumpfd2fd.c \
+		       mylocking.c
--- a/src/listcontrol.c	Tue Sep 14 08:47:55 2004 +1000
+++ b/src/listcontrol.c	Tue Sep 14 09:01:32 2004 +1000
@@ -43,9 +43,13 @@
 #include "memory.h"
 
 enum ctrl_e {
+	CTRL_SUBSCRIBE_DIGEST,
 	CTRL_SUBSCRIBE,
+	CTRL_CONFSUB_DIGEST,
 	CTRL_CONFSUB,
+	CTRL_UNSUBSCRIBE_DIGEST,
 	CTRL_UNSUBSCRIBE,
+	CTRL_CONFUNSUB_DIGEST,
 	CTRL_CONFUNSUB,
 	CTRL_BOUNCES,
 	CTRL_MODERATE,
@@ -61,11 +65,17 @@
 	unsigned int accepts_parameter;
 };
 
-/* must match the enum */
+/* Must match the enum. CAREFUL when using commands that are substrings
+ * of other commands. In that case the longest one have to be listed
+ * first to match correctly. */
 static struct ctrl_command ctrl_commands[] = {
+	{ "subscribe-digest",   0 },
 	{ "subscribe",   0 },
+	{ "confsub-digest",     1 },
 	{ "confsub",     1 },
+	{ "unsubscribe-digest", 0 },
 	{ "unsubscribe", 0 },
+	{ "confunsub-digest",   1 },
 	{ "confunsub",   1 },
 	{ "bounces",     1 },
 	{ "moderate",    1 },
@@ -122,45 +132,94 @@
 						" discarding mail");
 					exit(EXIT_SUCCESS);
 				}
-				myfree(controlstr);
-				break;
 			} else if (!ctrl_commands[ctrl].accepts_parameter &&
 					(controlstr[cmdlen] == '\0')) {
 				param = NULL;
+			} else {
+				/* malformed request, ignore and clean up */
+				unlink(mailname);
+				exit(EXIT_SUCCESS);
+			}
+
 				myfree(controlstr);
 				break;
-			} else {
-				log_error(LOG_ARGS, "Received a malformed"
-					" list control request");
-				myfree(controlstr);
-				return -1;
-			}
 
 		}
 	}
 
 	switch (ctrl) {
 
+	/* listname+subscribe-digest@domain.tld */
+	case CTRL_SUBSCRIBE_DIGEST:
+		unlink(mailname);
+		if (closedlist)
+			exit(EXIT_SUCCESS);
+		if (!strchr(fromemails->emaillist[0], '@'))
+			/* Not a valid From: address, silently ignore */
+			exit(EXIT_SUCCESS);
+		execlp(mlmmjsub, mlmmjsub,
+				"-L", listdir,
+				"-a", fromemails->emaillist[0],
+				"-d",
+				"-C", NULL);
+		log_error(LOG_ARGS, "execlp() of '%s' failed",
+					mlmmjsub);
+		exit(EXIT_FAILURE);
+		break;
+
+	/* listname+subscribe@domain.tld */
 	case CTRL_SUBSCRIBE:
 		unlink(mailname);
-		if (closedlist) exit(EXIT_SUCCESS);
-		if(strchr(fromemails->emaillist[0], '@')) {
+		if (closedlist)
+			exit(EXIT_SUCCESS);
+		if (!strchr(fromemails->emaillist[0], '@'))
+			/* Not a valid From: address, silently ignore */
+			exit(EXIT_SUCCESS);
 			execlp(mlmmjsub, mlmmjsub,
 					"-L", listdir,
 					"-a", fromemails->emaillist[0],
 					"-C", NULL);
-			log_error(LOG_ARGS, "execlp() of '%s' failed", mlmmjsub);
+		log_error(LOG_ARGS, "execlp() of '%s' failed",
+					mlmmjsub);
 			exit(EXIT_FAILURE);
-		} else /* Not a valid From: address, so we silently ignore */
-			exit(EXIT_SUCCESS);
 		break;
 
+	/* listname+subconf-digest-COOKIE@domain.tld */
+	case CTRL_CONFSUB_DIGEST:
+		unlink(mailname);
+		if (closedlist)
+			exit(EXIT_SUCCESS);
+		conffilename = concatstr(3, listdir, "/subconf/", param);
+		myfree(param);
+		if((tmpfd = open(conffilename, O_RDONLY)) < 0) {
+			/* invalid COOKIE, silently ignore */
+			exit(EXIT_SUCCESS);
+		}
+		tmpstr = mygetline(tmpfd);
+		chomp(tmpstr);
+		close(tmpfd);
+		unlink(conffilename);
+		execlp(mlmmjsub, mlmmjsub,
+				"-L", listdir,
+				"-a", tmpstr,
+				"-d",
+				"-c", NULL);
+		log_error(LOG_ARGS, "execlp() of '%s' failed",
+				mlmmjsub);
+		exit(EXIT_FAILURE);
+		break;
+
+	/* listname+subconf-COOKIE@domain.tld */
 	case CTRL_CONFSUB:
 		unlink(mailname);
-		if (closedlist) exit(EXIT_SUCCESS);
+		if (closedlist)
+			exit(EXIT_SUCCESS);
 		conffilename = concatstr(3, listdir, "/subconf/", param);
 		myfree(param);
-		if((tmpfd = open(conffilename, O_RDONLY)) >= 0) {
+		if((tmpfd = open(conffilename, O_RDONLY)) < 0) {
+			/* invalid COOKIE, silently ignore */
+			exit(EXIT_SUCCESS);
+		}
 			tmpstr = mygetline(tmpfd);
 			chomp(tmpstr);
 			close(tmpfd);
@@ -172,14 +231,34 @@
 			log_error(LOG_ARGS, "execlp() of '%s' failed",
 					mlmmjsub);
 			exit(EXIT_FAILURE);
-		} else /* Not a confirm so silently ignore */
-			exit(EXIT_SUCCESS);
 		break;
 
+	/* listname+unsubscribe-digest@domain.tld */
+	case CTRL_UNSUBSCRIBE_DIGEST:
+		unlink(mailname);
+		if (closedlist)
+			exit(EXIT_SUCCESS);
+		if (!strchr(fromemails->emaillist[0], '@'))
+			/* Not a valid From: address, silently ignore */
+			exit(EXIT_SUCCESS);
+		execlp(mlmmjunsub, mlmmjunsub,
+				"-L", listdir,
+				"-a", fromemails->emaillist[0],
+				"-d",
+				"-C", NULL);
+		log_error(LOG_ARGS, "execlp() of '%s' failed",
+				mlmmjunsub);
+		exit(EXIT_FAILURE);
+		break;
+
+	/* listname+unsubscribe@domain.tld */
 	case CTRL_UNSUBSCRIBE:
 		unlink(mailname);
-		if (closedlist) exit(EXIT_SUCCESS);
-		if(strchr(fromemails->emaillist[0], '@')) {
+		if (closedlist)
+			exit(EXIT_SUCCESS);
+		if (!strchr(fromemails->emaillist[0], '@'))
+			/* Not a valid From: address, silently ignore */
+			exit(EXIT_SUCCESS);
 			execlp(mlmmjunsub, mlmmjunsub,
 					"-L", listdir,
 					"-a", fromemails->emaillist[0],
@@ -187,16 +266,44 @@
 			log_error(LOG_ARGS, "execlp() of '%s' failed",
 					mlmmjunsub);
 			exit(EXIT_FAILURE);
-		} else /* Not a valid From: address, so we silently ignore */
-			exit(EXIT_SUCCESS);
 		break;
 
+	/* listname+unsubconf-digest-COOKIE@domain.tld */
+	case CTRL_CONFUNSUB_DIGEST:
+		unlink(mailname);
+		if (closedlist)
+			exit(EXIT_SUCCESS);
+		conffilename = concatstr(3, listdir, "/unsubconf/", param);
+		myfree(param);
+		if((tmpfd = open(conffilename, O_RDONLY)) < 0) {
+			/* invalid COOKIE, silently ignore */
+			exit(EXIT_SUCCESS);
+		}
+		tmpstr = mygetline(tmpfd);
+		close(tmpfd);
+		chomp(tmpstr);
+		unlink(conffilename);
+		execlp(mlmmjunsub, mlmmjunsub,
+				"-L", listdir,
+				"-a", tmpstr,
+				"-d",
+				"-c", NULL);
+		log_error(LOG_ARGS, "execlp() of '%s' failed",
+				mlmmjunsub);
+		exit(EXIT_FAILURE);
+		break;
+
+	/* listname+unsubconf-COOKIE@domain.tld */
 	case CTRL_CONFUNSUB:
 		unlink(mailname);
-		if (closedlist) exit(EXIT_SUCCESS);
+		if (closedlist)
+			exit(EXIT_SUCCESS);
 		conffilename = concatstr(3, listdir, "/unsubconf/", param);
 		myfree(param);
-		if((tmpfd = open(conffilename, O_RDONLY)) >= 0) {
+		if((tmpfd = open(conffilename, O_RDONLY)) < 0) {
+			/* invalid COOKIE, silently ignore */
+			exit(EXIT_SUCCESS);
+		}
 			tmpstr = mygetline(tmpfd);
 			close(tmpfd);
 			chomp(tmpstr);
@@ -208,10 +315,9 @@
 			log_error(LOG_ARGS, "execlp() of '%s' failed",
 					mlmmjunsub);
 			exit(EXIT_FAILURE);
-		} else /* Not a confirm so silently ignore */
-			exit(EXIT_SUCCESS);
 		break;
 
+	/* listname+bounces-user=example.tld-INDEX@domain.tld */
 	case CTRL_BOUNCES:
 		bouncenr = strrchr(param, '-');
 		if (!bouncenr) { /* malformed bounce, ignore and clean up */
@@ -228,6 +334,7 @@
 		exit(EXIT_FAILURE);
 		break;
 
+	/* listname+moderate-COOKIE@domain.tld */
 	case CTRL_MODERATE:
 		/* TODO Add accept/reject parameter to moderate */
 		unlink(mailname);
@@ -236,15 +343,16 @@
 		if(stat(moderatefilename, &stbuf) < 0) {
 			myfree(moderatefilename);
 			exit(EXIT_SUCCESS); /* just exit, no mail to moderate */
-		} else {
+		}
 			execlp(mlmmjsend, mlmmjsend,
 					"-L", listdir,
 					"-m", moderatefilename, NULL);
-			log_error(LOG_ARGS, "execlp() of %s failed", mlmmjsend);
+		log_error(LOG_ARGS, "execlp() of '%s' failed",
+					mlmmjsend);
 			exit(EXIT_FAILURE);
-		}
 		break;
 
+	/* listname+help@domain.tld */
 	case CTRL_HELP:
 		unlink(mailname);
 		if(strchr(fromemails->emaillist[0], '@'))
@@ -252,12 +360,10 @@
 				  mlmmjsend);
 		break;
 
+	/* listname+get-INDEX@domain.tld */
 	case CTRL_GET:
-		errno = 0;
 		unlink(mailname);
-		if(!param) /* malformed get, ignore silently */
-			exit(EXIT_SUCCESS);
-		else { /* sanity check--is it all digits? */
+		/* sanity check--is it all digits? */
 			for(c = param; *c != '\0'; c++) {
 				if(!isdigit((int)*c))
 					exit(EXIT_SUCCESS);
@@ -266,7 +372,6 @@
 							param);
 			if(stat(archivefilename, &stbuf) < 0)
 				exit(EXIT_SUCCESS);
-			else {
 				execlp(mlmmjsend, mlmmjsend,
 					"-T", fromemails->emaillist[0],
 					"-L", listdir,
@@ -276,8 +381,6 @@
 				log_error(LOG_ARGS, "execlp() of '%s' failed",
 							mlmmjsend);
 				exit(EXIT_FAILURE);
-			}
-		}
 		break;
 	}
 
--- a/src/log_error.c	Tue Sep 14 08:47:55 2004 +1000
+++ b/src/log_error.c	Tue Sep 14 09:01:32 2004 +1000
@@ -25,6 +25,8 @@
 #include <string.h>
 #include <stdlib.h>
 #include <stdarg.h>
+#include <sys/types.h>
+#include <unistd.h>
 
 #include "log_error.h"
 #include "../config.h"
--- a/src/mlmmj-maintd.c	Tue Sep 14 08:47:55 2004 +1000
+++ b/src/mlmmj-maintd.c	Tue Sep 14 09:01:32 2004 +1000
@@ -42,6 +42,8 @@
 #include "wrappers.h"
 #include "memory.h"
 #include "ctrlvalue.h"
+#include "send_digest.h"
+#include "mylocking.h"
 
 static int maintdlogfd = -1;
 
@@ -740,6 +742,134 @@
 	return 0;
 }
 
+int run_digests(const char *listdir, const char *mlmmjsend)
+{
+	char *lasttimestr, *lastindexstr;
+	char *digestname, *indexname;
+	char *digestintervalstr, *digestmaxmailsstr;
+	char *s1, *s2, *s3;
+	time_t digestinterval, t, lasttime;
+	long digestmaxmails, lastindex, index;
+	int fd, indexfd, lock;
+	size_t lenbuf, lenstr;
+	
+	digestintervalstr = ctrlvalue(listdir, "digestinterval");
+	if (digestintervalstr) {
+		digestinterval = (time_t)atol(digestintervalstr);
+		myfree(digestintervalstr);
+	} else {
+		digestinterval = (time_t)DIGESTINTERVAL;
+	}
+
+	digestmaxmailsstr = ctrlvalue(listdir, "digestmaxmails");
+	if (digestmaxmailsstr) {
+		digestmaxmails = atol(digestmaxmailsstr);
+		myfree(digestmaxmailsstr);
+	} else {
+		digestmaxmails = DIGESTMAXMAILS;
+	}
+
+	digestname = concatstr(2, listdir, "/lastdigest");
+	fd = open(digestname, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
+	if (fd < 0) {
+		log_error(LOG_ARGS, "Could not open '%s'", digestname);
+		myfree(digestname);
+		return 1;
+	}
+	
+	lock = myexcllock(fd);
+	if(lock) {
+		log_error(LOG_ARGS, "Error locking lastdigest");
+		myfree(digestname);
+		close(fd);
+		return 1;
+	}
+
+	s1 = mygetline(fd);
+
+	/* Syntax is lastindex:lasttime */
+	if (s1 && (lasttimestr = strchr(s1, ':'))) {
+		*(lasttimestr++) = '\0';
+		lasttime = atol(lasttimestr);
+		lastindexstr = s1;
+		lastindex = atol(lastindexstr);
+	} else {
+		if (s1 && (strlen(s1) > 0)) {
+			log_error(LOG_ARGS, "'%s' contains malformed data",
+					digestname);
+			myfree(digestname);
+			myfree(s1);
+			myunlock(fd);
+			close(fd);
+			return 1;
+		}
+		/* If lastdigest is empty, we start from scratch */
+		lasttime = 0;
+		lastindex = 0;
+	}
+	
+	indexname = concatstr(2, listdir, "/index");
+	indexfd = open(indexname, O_RDONLY);
+	if (indexfd < 0) {
+		log_error(LOG_ARGS, "Could not open '%s'", indexname);
+		myfree(digestname);
+		myfree(indexname);
+		myfree(s1);
+		myunlock(fd);
+		close(fd);
+		return 1;
+	}
+	s2 = mygetline(indexfd);
+	close(indexfd);
+	if (!s2) {
+		/* If we don't have an index, no mails have been sent to the
+		 * list, and therefore we don't need to send a digest */
+		myfree(digestname);
+		myfree(indexname);
+		myfree(s1);
+		myunlock(fd);
+		close(fd);
+		return 1;
+	}
+
+	index = atol(s2);
+	t = time(NULL);
+
+	if ((t - lasttime >= digestinterval) ||
+			(index - lastindex >= digestmaxmails)) {
+
+		if (index > lastindex+digestmaxmails)
+			index = lastindex+digestmaxmails;
+
+		send_digest(listdir, lastindex+1, index, NULL, mlmmjsend);
+
+		if (lseek(fd, 0, SEEK_SET) < 0) {
+			log_error(LOG_ARGS, "Could not seek '%s'", digestname);
+		} else {
+			/* index + ':' + time + '\n' + '\0' */
+			lenbuf = 20 + 1 + 20 + 2;
+			s3 = mymalloc(lenbuf);
+			lenstr = snprintf(s3, lenbuf, "%ld:%ld\n", index, t);
+			if (lenstr >= lenbuf)
+				lenstr = lenbuf - 1;
+			if (writen(fd, s3, lenstr) == -1) {
+				log_error(LOG_ARGS, "Could not write new '%s'",
+						digestname);
+			}
+			myfree(s3);
+		}
+	}
+
+	myfree(digestname);
+	myfree(indexname);
+	myfree(s1);
+	myfree(s2);
+	myunlock(fd);
+	close(fd);
+	
+	return 0;
+}
+
 void do_maintenance(const char *listdir, const char *mlmmjsend,
 		    const char *mlmmjbounce, const char *mlmmjunsub)
 {
@@ -825,6 +955,10 @@
 			mlmmjbounce, ");\n");
 	probe_bouncers(listdir, mlmmjbounce);
 
+	WRITEMAINTLOG6(5, "run_digests(", listdir, ", ", mlmmjsend,
+			");\n");
+	run_digests(listdir, mlmmjsend);
+
 	close(maintdlogfd);
 
 	logstr = concatstr(3, listdir, "/", MAINTD_LOGFILE);
--- a/src/mlmmj-make-ml.sh	Tue Sep 14 08:47:55 2004 +1000
+++ b/src/mlmmj-make-ml.sh	Tue Sep 14 09:01:32 2004 +1000
@@ -67,12 +67,12 @@
 mkdir -p $LISTDIR
 
 for DIR in incoming queue queue/discarded archive text subconf unsubconf \
-	   bounce control moderation subscribers.d requeue
+	   bounce control moderation subscribers.d digesters.d requeue
 do
 	mkdir "$LISTDIR"/"$DIR"
 done
 
-touch "$LISTDIR"/index
+test -f "$LISTDIR"/index || touch "$LISTDIR"/index
 
 echo -n "The Domain for the List? [] : "
 read FQDN
--- a/src/mlmmj-send.c	Tue Sep 14 08:47:55 2004 +1000
+++ b/src/mlmmj-send.c	Tue Sep 14 09:01:32 2004 +1000
@@ -369,11 +369,11 @@
 		} else
 			continue;
 
-		if(from)
+		if(from) {
 			sendres = send_mail(sockfd, from, addr, replyto,
 					    mailmap, mailsize, listdir, NULL,
 					    hdrs, hdrslen, body, bodylen);
-		else {
+		} else {
 			bounceaddr = bounce_from_adr(addr, listaddr,
 						     archivefilename);
 			sendres = send_mail(sockfd, bounceaddr, addr, replyto,
@@ -456,6 +456,7 @@
 	       "    '4' means 'send to file with recipients'\n"
 	       "    '5' means 'bounceprobe'\n"
 	       "    '6' means 'single listmail to single recipient'\n"
+	       "    '7' means 'digest'\n"
 	       " -L: Full path to list directory\n"
 	       " -m: Full path to mail file\n"
 	       " -r: Relayhost IP address (defaults to 127.0.0.1)\n"
@@ -470,7 +471,7 @@
 {
 	size_t len = 0, hdrslen, bodylen;
 	int sockfd = 0, mailfd = 0, opt, mindex, subfd, tmpfd;
-	int deletewhensent = 1, sendres, archive = 1;
+	int deletewhensent = 1, sendres, archive = 1, digest = 0;
 	char *listaddr, *mailfilename = NULL, *subfilename = NULL;
 	char *replyto = NULL, *bounceaddr = NULL, *to_addr = NULL;
 	char *relayhost = NULL, *archivefilename = NULL, *tmpstr;
@@ -574,6 +575,12 @@
 		exit(EXIT_FAILURE);
 	}
 
+	if((listctrl[0] == '7' && listdir == NULL)) {
+		fprintf(stderr, "With -l 7 you need -L\n");
+		exit(EXIT_FAILURE);
+	}
+
+
 	switch(listctrl[0]) {
 		case '1':
 		case '2':
@@ -581,6 +588,7 @@
 		case '4':
 		case '5':
 		case '6':
+		case '7':
 			archive = 0;
 		default:
 			break;
@@ -797,8 +805,16 @@
 			myfree(tmpstr);
 		}
 		break;
+	case '7':
+		digest = 1;
+		archivefilename = mystrdup("digest");
+		/* fall through */
 	default: /* normal list mail */
+		if (!digest) {
 		subddirname = concatstr(2, listdir, "/subscribers.d/");
+		} else {
+			subddirname = concatstr(2, listdir, "/digesters.d/");
+		}
 		if((subddir = opendir(subddirname)) == NULL) {
 			log_error(LOG_ARGS, "Could not opendir(%s)",
 					    subddirname);
@@ -807,15 +823,13 @@
 			myfree(body);
 			exit(EXIT_FAILURE);
 		}
-		myfree(subddirname);
 
 		while((dp = readdir(subddir)) != NULL) {
 			if(!strcmp(dp->d_name, "."))
 				continue;
 			if(!strcmp(dp->d_name, ".."))
 				continue;
-			subfilename = concatstr(3, listdir, "/subscribers.d/",
-						dp->d_name);
+			subfilename = concatstr(2, subddirname, dp->d_name);
 			if((subfd = open(subfilename, O_RDONLY)) < 0) {
 				log_error(LOG_ARGS, "Could not open '%s'",
 						    subfilename);
@@ -841,6 +855,7 @@
 			close(subfd);
 		}
 		closedir(subddir);
+		myfree(subddirname);
 		break;
 	}
 	
--- a/src/mlmmj-sub.c	Tue Sep 14 08:47:55 2004 +1000
+++ b/src/mlmmj-sub.c	Tue Sep 14 09:01:32 2004 +1000
@@ -47,13 +47,18 @@
 #include "memory.h"
 
 void confirm_sub(const char *listdir, const char *listaddr,
-		const char *subaddr, const char *mlmmjsend)
+		const char *subaddr, const char *mlmmjsend,
+		int digest)
 {
 	int subtextfd, queuefd;
 	char *buf, *subtextfilename, *randomstr, *queuefilename = NULL;
 	char *fromaddr, *listname, *listfqdn, *s1;
 
+	if (!digest) {
 	subtextfilename = concatstr(2, listdir, "/text/sub-ok");
+	} else {
+		subtextfilename = concatstr(2, listdir, "/text/sub-ok-digest");
+	}
 
 	if((subtextfd = open(subtextfilename, O_RDONLY)) < 0) {
 		log_error(LOG_ARGS, "Could not open '%s'", subtextfilename);
@@ -84,8 +89,16 @@
 
 	fromaddr = concatstr(3, listname, "+bounces-help@", listfqdn);
 
-	s1 = concatstr(9, "From: ", listname, "+help@", listfqdn, "\nTo: ",
-			subaddr, "\nSubject: Welcome to ", listaddr, "\n\n");
+	if (!digest) {
+		s1 = concatstr(9, "From: ", listname, "+help@", listfqdn,
+				"\nTo: ", subaddr, "\nSubject: Welcome to ",
+				listaddr, "\n\n");
+	} else {
+		s1 = concatstr(9, "From: ", listname, "+help@", listfqdn,
+				"\nTo: ", subaddr, "\nSubject: Welcome to "
+				"digest of ", listaddr, "\n\n");
+	}
+
 	if(writen(queuefd, s1, strlen(s1)) < 0) {
 		log_error(LOG_ARGS, "Could not write welcome mail");
 		exit(EXIT_FAILURE);
@@ -124,7 +137,8 @@
 }
 
 void notify_sub(const char *listdir, const char *listaddr,
-		const char *subaddr, const char *mlmmjsend)
+		const char *subaddr, const char *mlmmjsend,
+		int digest)
 {
 	char *maildata[4] = { "*LSTADDR*", NULL, "*SUBADDR*", NULL };
 	char *listfqdn, *listname, *fromaddr, *fromstr, *subject;
@@ -136,9 +150,18 @@
 	maildata[3] = mystrdup(subaddr);
 	fromaddr = concatstr(3, listname, "+bounces-help@", listfqdn);
 	fromstr = concatstr(3, listname, "+owner@", listfqdn);
+	if (!digest) {
 	subject = concatstr(2, "New subscription to ", listaddr);
 	queuefilename = prepstdreply(listdir, "notifysub", fromstr,
-				     fromstr, NULL, subject, 2, maildata);
+						fromstr, NULL, subject, 2,
+						maildata);
+	} else {
+		subject = concatstr(2, "New subscription to digest of ",
+					listaddr);
+		queuefilename = prepstdreply(listdir, "notifysub-digest",
+						fromstr, fromstr, NULL,
+						subject, 2, maildata);
+	}
 	MY_ASSERT(queuefilename)
 	myfree(listname);
 	myfree(listfqdn);
@@ -158,7 +181,8 @@
 }
 
 void generate_subconfirm(const char *listdir, const char *listaddr,
-			 const char *subaddr, const char *mlmmjsend)
+			 const char *subaddr, const char *mlmmjsend,
+			 int digest)
 {
 	int subconffd, subtextfd, queuefd;
 	char *confirmaddr, *listname, *listfqdn, *confirmfilename = NULL;
@@ -198,15 +222,20 @@
 
 	close(subconffd);
 
-	confirmaddr = concatstr(5, listname, "+confsub-", randomstr, "@",
-			           listfqdn);
-
 	fromaddr = concatstr(5, listname, "+bounces-confsub-", randomstr,
 				"@", listfqdn);
 
-	myfree(randomstr);
+	if (!digest) {
+		subtextfilename = concatstr(2, listdir, "/text/sub-confirm");
+		confirmaddr = concatstr(5, listname, "+confsub-",
+					randomstr, "@", listfqdn);
+	} else {
+		subtextfilename = concatstr(2, listdir, "/text/sub-confirm-digest");
+		confirmaddr = concatstr(5, listname, "+confsub-digest-",
+					randomstr, "@", listfqdn);
+	}
 
-	subtextfilename = concatstr(2, listdir, "/text/sub-confirm");
+	myfree(randomstr);
 
 	if((subtextfd = open(subtextfilename, O_RDONLY)) < 0) {
 		log_error(LOG_ARGS, "Could not open '%s'", subtextfilename);
@@ -233,9 +262,16 @@
 		exit(EXIT_FAILURE);
 	}
 
-	s1 = concatstr(9, "From: ", listname, "+help@", listfqdn, "\nTo: ", 
-			subaddr, "\nSubject: Confirm subscribe to ", listaddr,
-			"\n\n");
+	if (!digest) {
+		s1 = concatstr(9, "From: ", listname, "+help@", listfqdn,
+			"\nTo: ", subaddr, "\nSubject: Confirm subscribe to ",
+			listaddr, "\n\n");
+	} else {
+		s1 = concatstr(9, "From: ", listname, "+help@", listfqdn,
+			"\nTo: ", subaddr, "\nSubject: Confirm subscribe to "
+			"digest of ", listaddr, "\n\n");
+	}
+
 	if(writen(queuefd, s1, strlen(s1)) < 0) {
 		log_error(LOG_ARGS, "Could not write subconffile");
 		exit(EXIT_FAILURE);
@@ -294,6 +330,7 @@
 	       " -a: Email address to subscribe \n"
 	       " -c: Send welcome mail\n"
 	       " -C: Request mail confirmation\n"
+	       " -d: Subscribe to digest of list\n"
 	       " -h: This help\n"
 	       " -L: Full path to list directory\n"
 	       " -U: Don't switch to the user id of the listdir owner\n"
@@ -308,7 +345,7 @@
 	char *listaddr, *listdir = NULL, *address = NULL, *subfilename = NULL;
 	char *mlmmjsend, *bindir, chstr[2];
 	int subconfirm = 0, confirmsub = 0, opt, subfilefd, lock, notifysub;
-	int changeuid = 1, status;
+	int changeuid = 1, status, digest = 0;
 	size_t len;
 	off_t suboff;
 	struct stat st;
@@ -322,7 +359,7 @@
 	mlmmjsend = concatstr(2, bindir, "/mlmmj-send");
 	myfree(bindir);
 
-	while ((opt = getopt(argc, argv, "hcCVL:a:")) != -1) {
+	while ((opt = getopt(argc, argv, "hcCdVL:a:")) != -1) {
 		switch(opt) {
 		case 'a':
 			address = optarg;
@@ -333,6 +370,9 @@
 		case 'C':
 			subconfirm = 1;
 			break;
+		case 'd':
+			digest = 1;
+			break;
 		case 'h':
 			print_help(argv[0]);
 			break;
@@ -380,7 +420,11 @@
 
 	chstr[0] = address[0];
 	chstr[1] = '\0';
+	if (!digest) {
 	subfilename = concatstr(3, listdir, "/subscribers.d/", chstr);
+	} else {
+		subfilename = concatstr(3, listdir, "/digesters.d/", chstr);
+	}
 
 	subfilefd = open(subfilename, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
 	if(subfilefd == -1) {
@@ -398,7 +442,7 @@
 	if(suboff == -1) {
 		if(subconfirm)
 			generate_subconfirm(listdir, listaddr, address,
-					    mlmmjsend);
+					    mlmmjsend, digest);
 		else {
 			lseek(subfilefd, 0L, SEEK_END);
 			len = strlen(address);
@@ -419,7 +463,8 @@
 
 		if(childpid < 0) {
 			log_error(LOG_ARGS, "Could not fork");
-			confirm_sub(listdir, listaddr, address, mlmmjsend);
+			confirm_sub(listdir, listaddr, address, mlmmjsend,
+					digest);
 		}
 		
 		if(childpid > 0) {
@@ -430,14 +475,15 @@
 
 		/* child confirms subscription */
 		if(childpid == 0)
-			confirm_sub(listdir, listaddr, address, mlmmjsend);
+			confirm_sub(listdir, listaddr, address, mlmmjsend,
+					digest);
 	}
 
 	notifysub = statctrl(listdir, "notifysub");
 
 	/* Notify list owner about subscription */
 	if (notifysub)
-		notify_sub(listdir, listaddr, address, mlmmjsend);
+		notify_sub(listdir, listaddr, address, mlmmjsend, digest);
 
 	myfree(listaddr);
 
--- a/src/mlmmj-unsub.c	Tue Sep 14 08:47:55 2004 +1000
+++ b/src/mlmmj-unsub.c	Tue Sep 14 09:01:32 2004 +1000
@@ -47,13 +47,18 @@
 #include "prepstdreply.h"
 
 void confirm_unsub(const char *listdir, const char *listaddr,
-		   const char *subaddr, const char *mlmmjsend)
+		   const char *subaddr, const char *mlmmjsend, int digest)
 {
 	int subtextfd, queuefd;
 	char *buf, *subtextfilename, *randomstr, *queuefilename = NULL;
 	char *fromaddr, *listname, *listfqdn, *s1;
 
+	if (!digest) {
 	subtextfilename = concatstr(2, listdir, "/text/unsub-ok");
+	} else {
+		subtextfilename = concatstr(2, listdir,
+					    "/text/unsub-ok-digest");
+	}
 
 	if((subtextfd = open(subtextfilename, O_RDONLY)) < 0) {
 		log_error(LOG_ARGS, "Could not open '%s'", subtextfilename);
@@ -84,9 +89,15 @@
 
 	fromaddr = concatstr(3, listname, "+bounces-help@", listfqdn);
 
-	s1 = concatstr(9, "From: ", listname, "+help@", listfqdn, "\nTo: ",
-			subaddr, "\nSubject: Goodbye from ", listaddr,
-			"\n\n");
+	if (!digest) {
+		s1 = concatstr(9, "From: ", listname, "+help@", listfqdn,
+			"\nTo: ", subaddr, "\nSubject: Goodbye from ",
+			listaddr, "\n\n");
+	} else {
+		s1 = concatstr(9, "From: ", listname, "+help@", listfqdn,
+			"\nTo: ", subaddr, "\nSubject: Goodbye from "
+			"digest of ", listaddr, "\n\n");
+	}
 
 	if(writen(queuefd, s1, strlen(s1)) < 0) {
 		log_error(LOG_ARGS, "Could not write subconffile");
@@ -126,7 +137,7 @@
 }
 
 void notify_unsub(const char *listdir, const char *listaddr,
-		  const char *subaddr, const char *mlmmjsend)
+		  const char *subaddr, const char *mlmmjsend, int digest)
 {
         char *maildata[4] = { "*LSTADDR*", NULL, "*SUBADDR*", NULL };
         char *listfqdn, *listname, *fromaddr, *fromstr, *subject;
@@ -138,9 +149,17 @@
         maildata[3] = mystrdup(subaddr);
         fromaddr = concatstr(3, listname, "+bounces-help@", listfqdn);
         fromstr = concatstr(3, listname, "+owner@", listfqdn);
+	if (!digest) {
         subject = concatstr(2, "Unsubscription from ", listaddr);
         queuefilename = prepstdreply(listdir, "notifyunsub", fromstr,
                                      fromstr, NULL, subject, 2, maildata);
+	} else {
+        	subject = concatstr(2, "Unsubscription from digest of ",
+					listaddr);
+        	queuefilename = prepstdreply(listdir, "notifyunsub-digest",
+                                     fromstr, fromstr, NULL, subject, 2,
+                                     maildata);
+	}
         MY_ASSERT(queuefilename)
         myfree(listname);
         myfree(listfqdn);
@@ -161,7 +180,8 @@
 
 
 void generate_unsubconfirm(const char *listdir, const char *listaddr,
-			   const char *subaddr, const char *mlmmjsend)
+			   const char *subaddr, const char *mlmmjsend,
+			   int digest)
 {
 	char *confirmaddr, *buf, *listname, *listfqdn;
 	char *subtextfilename, *queuefilename = NULL, *fromaddr, *s1;
@@ -201,13 +221,21 @@
 
 	close(subconffd);
 
-	confirmaddr = concatstr(5, listname, "+confunsub-", randomstr, "@",
-			           listfqdn);
 	fromaddr = concatstr(5, listname, "+bounces-confunsub-", randomstr,
 				"@", listfqdn);
-	myfree(randomstr);
 
+	if (!digest) {
 	subtextfilename = concatstr(2, listdir, "/text/unsub-confirm");
+		confirmaddr = concatstr(5, listname, "+confunsub-", randomstr,
+					"@", listfqdn);
+	} else {
+		subtextfilename = concatstr(2, listdir,
+					"/text/unsub-confirm-digest");
+		confirmaddr = concatstr(5, listname, "+confunsub-digest-",
+					randomstr, "@", listfqdn);
+	}
+
+	myfree(randomstr);
 
 	if((subtextfd = open(subtextfilename, O_RDONLY)) < 0) {
 		log_error(LOG_ARGS, "Could not open '%s'", subtextfilename);
@@ -233,9 +261,16 @@
 		exit(EXIT_FAILURE);
 	}
 
-	s1 = concatstr(9, "From: ", listname, "+help@", listfqdn, "\nTo: ",
-			subaddr, "\nSubject: Confirm unsubscribe from ",
-			listaddr, "\n\n");
+	if (!digest) {
+		s1 = concatstr(9, "From: ", listname, "+help@", listfqdn,
+				"\nTo: ", subaddr, "\nSubject: Confirm "
+				"unsubscribe from ", listaddr, "\n\n");
+	} else {
+		s1 = concatstr(9, "From: ", listname, "+help@", listfqdn,
+				"\nTo: ", subaddr, "\nSubject: Confirm "
+				"unsubscribe from digest of ", listaddr,
+				"\n\n");
+	}
 
 	if(writen(queuefd, s1, strlen(s1)) < 0) {
 		log_error(LOG_ARGS, "Could not write subconffile");
@@ -331,6 +366,7 @@
 	       " -a: Email address to unsubscribe \n"
 	       " -c: Send goodbye mail\n"
 	       " -C: Request mail confirmation\n"
+	       " -d: Subscribe to digest of list\n"
 	       " -h: This help\n"
 	       " -L: Full path to list directory\n"
 	       " -V: Print version\n"
@@ -342,7 +378,7 @@
 int main(int argc, char **argv)
 {
 	int subread, subwrite, rlock, wlock, opt, unsubres, status;
-	int confirmunsub = 0, unsubconfirm = 0, notifysub = 0;
+	int confirmunsub = 0, unsubconfirm = 0, notifysub = 0, digest = 0;
 	char *listaddr, *listdir = NULL, *address = NULL, *subreadname = NULL;
 	char *subwritename, *mlmmjsend, *bindir;
 	char *subddirname;
@@ -359,7 +395,7 @@
 	mlmmjsend = concatstr(2, bindir, "/mlmmj-send");
 	myfree(bindir);
 
-	while ((opt = getopt(argc, argv, "hcCVL:a:")) != -1) {
+	while ((opt = getopt(argc, argv, "hcCdVL:a:")) != -1) {
 		switch(opt) {
 		case 'L':
 			listdir = optarg;
@@ -373,6 +409,9 @@
 		case 'C':
 			unsubconfirm = 1;
 			break;
+		case 'd':
+			digest = 1;
+			break;
 		case 'h':
 			print_help(argv[0]);
 			break;
@@ -397,9 +436,14 @@
 	listaddr = getlistaddr(listdir);
 
 	if(unsubconfirm)
-		generate_unsubconfirm(listdir, listaddr, address, mlmmjsend);
+		generate_unsubconfirm(listdir, listaddr, address, mlmmjsend,
+				digest);
 
+	if (!digest) {
 	subddirname = concatstr(2, listdir, "/subscribers.d/");
+	} else {
+		subddirname = concatstr(2, listdir, "/digesters.d/");
+	}
 	if((subddir = opendir(subddirname)) == NULL) {
 		log_error(LOG_ARGS, "Could not opendir(%s)",
 				    subddirname);
@@ -412,8 +456,13 @@
 			continue;
 		if(!strcmp(dp->d_name, ".."))
 			continue;
+		if (!digest) {
 		subreadname = concatstr(3, listdir, "/subscribers.d/",
 				dp->d_name);
+		} else {
+			subreadname = concatstr(3, listdir, "/digesters.d/",
+				dp->d_name);
+		}
 
 		subread = open(subreadname, O_RDWR);
 		if(subread == -1) {
@@ -509,7 +558,7 @@
 			if(childpid < 0) {
 				log_error(LOG_ARGS, "Could not fork");
 				confirm_unsub(listdir, listaddr, address,
-						mlmmjsend);
+						mlmmjsend, digest);
 			}
 
 			if(childpid > 0) {
@@ -521,7 +570,7 @@
 			/* child confirms subscription */
 			if(childpid == 0)
 				confirm_unsub(listdir, listaddr, address,
-						mlmmjsend);
+						mlmmjsend, digest);
 		}
         }
 
@@ -530,7 +579,7 @@
 
         /* Notify list owner about subscription */
         if (notifysub)
-                notify_unsub(listdir, listaddr, address, mlmmjsend);
+                notify_unsub(listdir, listaddr, address, mlmmjsend, digest);
 
 	myfree(listaddr);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/send_digest.c	Tue Sep 14 09:01:32 2004 +1000
@@ -0,0 +1,199 @@
+/* Copyright (C) 2004 Morten K. Poulsen <morten at afdelingp.dk>
+ *
+ * $Id$
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include "mlmmj.h"
+#include "send_digest.h"
+#include "log_error.h"
+#include "strgen.h"
+#include "memory.h"
+#include "getlistaddr.h"
+#include "wrappers.h"
+
+
+int send_digest(const char *listdir, int firstindex, int lastindex,
+		const char *addr, const char *mlmmjsend)
+{
+	int i;
+	int fd, archivefd;
+	char buf[45];
+	char *tmp, *queuename = NULL, *archivename;
+	char *boundary, *listaddr, *listname, *listfqdn;
+	pid_t childpid, pid;
+	int status;
+
+	if (addr) {
+		errno = 0;
+		log_error(LOG_ARGS, "send_digest() does not support sending "
+				"digest mails to only one recipient yet");
+		return -1;
+	}
+
+	if (firstindex > lastindex)
+		return -1;
+	
+	do {
+		tmp = random_str();
+		myfree(queuename);
+		queuename = concatstr(3, listdir, "/queue/", tmp);
+		myfree(tmp);
+		fd = open(queuename, O_RDWR|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
+	} while ((fd < 0) && (errno == EEXIST));
+
+	if (fd < 0) {
+		log_error(LOG_ARGS, "Could not open digest queue file '%s'",
+				queuename);
+		myfree(queuename);
+		return -1;
+	}
+
+	boundary = random_str();
+
+	listaddr = getlistaddr(listdir);
+	listname = genlistname(listaddr);
+	listfqdn = genlistfqdn(listaddr);
+	myfree(listaddr);
+	
+	if (lastindex == firstindex) {
+		snprintf(buf, sizeof(buf), " (%d)", firstindex);
+	} else {
+		snprintf(buf, sizeof(buf), " (%d-%d)", firstindex, lastindex);
+	}
+	tmp = concatstr(10,	"From: ", listname, "+help@", listfqdn,
+				"\nMIME-Version: 1.0"
+				"\nContent-Type: multipart/" DIGESTMIMETYPE "; "
+					"boundary=", boundary,
+				"\nSubject: Digest of ", listname, buf,
+				"\n\n");
+	myfree(listfqdn);
+
+	if (writen(fd, tmp, strlen(tmp)) == -1) {
+		log_error(LOG_ARGS, "Could not write digest headers to '%s'",
+				queuename);
+		close(fd);
+		unlink(queuename);
+		myfree(boundary);
+		myfree(tmp);
+		myfree(queuename);
+		myfree(listname);
+		return -1;
+	}
+	myfree(tmp);
+
+	for (i=firstindex; i<=lastindex; i++) {
+		snprintf(buf, sizeof(buf), "%d", i);
+		
+		archivename = concatstr(3, listdir, "/archive/", buf);
+		archivefd = open(archivename, O_RDONLY);
+		myfree(archivename);
+		
+		if (archivefd < 0)
+			continue;
+		
+		tmp = concatstr(7, "--", boundary,
+				"\nContent-Type: message/rfc822"
+				"\nContent-Disposition: inline; filename=\"",
+					listname, "_", buf, ".eml\""
+				"\n\n");
+		if (writen(fd, tmp, strlen(tmp)) == -1) {
+			log_error(LOG_ARGS, "Could not write digest part "
+					"headers for archive index %d to "
+					"'%s'", i, queuename);
+			close(fd);
+			close(archivefd);
+			unlink(queuename);
+			myfree(boundary);
+			myfree(tmp);
+			myfree(queuename);
+			myfree(listname);
+			return -1;
+		}
+		myfree(tmp);
+
+		if (dumpfd2fd(archivefd, fd) < 0) {
+			log_error(LOG_ARGS, "Could not write digest part %d "
+					"to '%s'", i,
+					queuename);
+			close(fd);
+			close(archivefd);
+			unlink(queuename);
+			myfree(boundary);
+			myfree(queuename);
+			myfree(listname);
+			return -1;
+		}
+		
+		close(archivefd);
+	}
+
+	tmp = concatstr(3, "--", boundary, "--\n");
+	if (writen(fd, tmp, strlen(tmp)) == -1) {
+		log_error(LOG_ARGS, "Could not write digest end to '%s'",
+				queuename);
+		close(fd);
+		unlink(queuename);
+		myfree(boundary);
+		myfree(queuename);
+		myfree(listname);
+		return -1;
+	}
+
+	close(fd);
+	myfree(boundary);
+	myfree(listname);
+
+	childpid = fork();
+
+	if(childpid < 0) {
+		log_error(LOG_ARGS, "Could not fork");
+		myfree(queuename);
+		return -1;
+	}
+
+	if(childpid > 0) {
+		do /* Parent waits for the child */
+		      pid = waitpid(childpid, &status, 0);
+		while(pid == -1 && errno == EINTR);
+	} else {
+		execlp(mlmmjsend, mlmmjsend,
+				"-l", "7",
+				"-L", listdir,
+				"-m", queuename,
+				NULL);
+		log_error(LOG_ARGS, "execlp() of '%s' failed", mlmmjsend);
+		exit(EXIT_FAILURE);  /* It is OK to exit, as this is a child */
+	}
+
+	unlink(queuename);
+	myfree(queuename);
+
+	return 0;
+}
--- a/src/subscriberfuncs.c	Tue Sep 14 08:47:55 2004 +1000
+++ b/src/subscriberfuncs.c	Tue Sep 14 09:01:32 2004 +1000
@@ -87,31 +87,26 @@
 	return (off_t)-1;
 }
 
-int is_subbed(const char *listdir, const char *address)
+static int is_subbed_in(const char *subddirname, const char *address)
 {
 	int retval = 1, subread;
-	char *subddirname, *subreadname;
+	char *subreadname;
 	off_t suboff;
 	DIR *subddir;
 	struct dirent *dp;
 
-	subddirname = concatstr(2, listdir, "/subscribers.d/");
 	if((subddir = opendir(subddirname)) == NULL) {
 		log_error(LOG_ARGS, "Could not opendir(%s)", subddirname);
-		myfree(subddirname);
 		exit(EXIT_FAILURE);
 	}
 
-	myfree(subddirname);
-
 	while((dp = readdir(subddir)) != NULL) {
 		if(!strcmp(dp->d_name, "."))
 			continue;
 		if(!strcmp(dp->d_name, ".."))
 			continue;
 
-		subreadname = concatstr(3, listdir, "/subscribers.d/",
-				dp->d_name);
+		subreadname = concatstr(2, subddirname, dp->d_name);
 		subread = open(subreadname, O_RDONLY);
 		if(subread < 0) {
 			log_error(LOG_ARGS, "Could not open '%s'",
@@ -135,3 +130,23 @@
 
 	return retval;
 }
+
+int is_subbed(const char *listdir, const char *address)
+{
+	int retval;
+	char *subddirname;
+	
+	subddirname = concatstr(2, listdir, "/subscribers.d/");
+	retval = is_subbed_in(subddirname, address);
+	myfree(subddirname);
+	if (retval == 0)
+		return 0;
+
+	subddirname = concatstr(2, listdir, "/digesters.d/");
+	retval = is_subbed_in(subddirname, address);
+	myfree(subddirname);
+	if (retval == 0)
+		return 0;
+
+	return 1;
+}