changeset 387:a408411ff8df

Added a glob-pattern aliasing facility. One use-case is virtual hosting another catch-all maildrops, but you may use it as a more flexible aliasing mechanism as well.
author markus schnalke <meillo@marmaro.de>
date Sat, 18 Feb 2012 12:35:12 +0100
parents 13c9e0969054
children aa40710f09fe
files man/masqmail.aliases.5 man/masqmail.conf.5 src/alias.c src/conf.c src/deliver.c src/masqmail.h
diffstat 6 files changed, 142 insertions(+), 27 deletions(-) [+]
line wrap: on
line diff
--- a/man/masqmail.aliases.5	Sat Feb 18 11:43:06 2012 +0100
+++ b/man/masqmail.aliases.5	Sat Feb 18 12:35:12 2012 +0100
@@ -7,21 +7,44 @@
 .SH DESCRIPTION
 
 This man page describes the format of the masqmail alias file.
-Its usual location is \fI/etc/aliases\fR.
+It's usual location is \fI/etc/aliases\fR.
+
+There exists also a variant of this format for glob patterns.
+It's used with the \fIglobalias_file\fP config option.
+
+The difference between the two formats are only on the left-hand side.
+A normal alias file has a local_part there that gets string-compared
+against the local part;
+whereas a glob alias file has a glob pattern that is matched against
+the whole address. The right-hand side of the two aliasing kinds
+has the same format.
 
 
 .SH FILE FORMAT
 
-The alias file consists of lines of the form:
+Normal alias files consist of lines of the form:
+
+.RS
 local_part: item1, item2, ...
+.RE
+
+Glob-pattern alias files consist of lines of the form:
+
+.RS
+glob_pattern: item1, item2, ...
+.RE
+
 Items can be surrounded by double quotes `"'.
 If within the quotes other quotes are needed for an address they can be
 escaped with a leading backslash `\\'.
 
-A leading backslash `\\' indicates that this address shall not be further expanded.
+A leading backslash `\\' indicates that this address shall not be
+further expanded.
 
-A leading pipe symbol `|' indicates that the item shall be treated as a pipe command.
-The content of the message will then be sent to the standard input of the command.
+A leading pipe symbol `|' indicates that the item shall be treated as a
+pipe command.
+The content of the message will then be sent to the standard input of the
+command.
 The command will run under the user id and group id masqmail is running as.
 If quotes are needed, the pipe symbol must appear within the quotes.
 
@@ -32,11 +55,15 @@
 any alias which matches one of the recipient addresses,
 the change will have effect next time a delivery is attemped.
 
-There is no need to restart masqmail or run any command when the alias file has been changed.
+There is no need to restart masqmail or run any command when the alias
+file has been changed. (`masqmail -bi' is a no-op.)
 
 
 .SH EXAMPLE
 
+A normal alias file:
+
+.RS
 .nf
 # postmaster is required by RFC 2821
 postmaster: root
@@ -50,6 +77,28 @@
 # pass mail to a script
 foo: |/usr/bin/foo
 .fi
+.RE
+
+
+A glob-pattern alias file:
+
+.RS
+.nf
+# the postmaster for any domain on this host
+postmaster@*: ken
+
+# split virtual domains
+info@foo.example.org: doug
+info@bar.example.org: rob
+
+# the order of entries is important
+# this must be before the catch-all line
+list\-*@example.org: |/path/to/some/script
+
+# catch-all address forwarded to extern
+*@example.org: brian@other.host.net
+.fi
+.RE
 
 
 .SH AUTHOR
@@ -57,8 +106,10 @@
 Masqmail was written by Oliver Kurth.
 It is now maintained by Markus Schnalke <meillo@marmaro.de>.
 
-You will find the newest version of masqmail at \fBhttp://marmaro.de/prog/masqmail/\fR.
-There is also a mailing list, you will find information about it at masqmail's main site.
+You will find the newest version of masqmail at
+\fBhttp://marmaro.de/prog/masqmail/\fR.
+There is also a mailing list, you will find information about
+it at masqmail's main site.
 
 
 .SH BUGS
--- a/man/masqmail.conf.5	Sat Feb 18 11:43:06 2012 +0100
+++ b/man/masqmail.conf.5	Sat Feb 18 12:35:12 2012 +0100
@@ -276,6 +276,22 @@
 Default: <not set> (i.e. no aliasing is done)
 
 .TP
+\fBglobalias_file = \fIfile\fR
+
+Set this to the location of a glob-pattern alias file.
+This kind of aliasing matches glob patterns against full email addresses,
+not strings against local parts like in normal aliasing.
+You can use this to handle catch-all maildrops (``*@example.org'')
+and to split between virtual hosts on a single machine
+(e.g. ``info@foo.ex.org'' and ``info@bar.ex.org'').
+
+Glob aliasing is done before normal aliasing.
+If you have both kinds, glob and normal aliasing, then the results of the
+glob aliasing may be expanded further by the normal aliasing mechanism.
+
+Default: <not set> (i.e. no glob aliasing is done)
+
+.TP
 \fBcaseless_matching = \fIboolean\fR
 
 If this is set, aliasing and the matching for \fBlocal_addresses\fP and
--- a/src/alias.c	Sat Feb 18 11:43:06 2012 +0100
+++ b/src/alias.c	Sat Feb 18 12:35:12 2012 +0100
@@ -104,11 +104,28 @@
 	return list;
 }
 
+static int
+globaliascmp(const char *pattern, const char *addr)
+{
+	if (conf.localpartcmp==strcasecmp) {
+		return fnmatch(pattern, addr, FNM_CASEFOLD);
+	} else if (strncasecmp(addr, "postmaster", 10)==0) {
+		/*
+		**  postmaster must always be matched caseless
+		**  see RFC 822 and RFC 5321
+		*/
+		return fnmatch(pattern, addr, FNM_CASEFOLD);
+	} else {
+		/* case-sensitive */
+		return fnmatch(pattern, addr, 0);
+	}
+}
+
 /*
 **  addr is assumed to be local and no pipe address nor not-to-expand
 */
 static GList*
-expand_one(GList *alias_table, address *addr)
+expand_one(GList *alias_table, address *addr, int doglob)
 {
 	GList *val_list;
 	GList *val_node;
@@ -117,24 +134,33 @@
 	gchar *val;
 
 	/* expand the local alias */
-	DEBUG(6) debugf("alias: '%s' is local and will get expanded\n", addr->local_part);
+	DEBUG(6) debugf("alias: '%s' is local and will get expanded\n",
+			doglob ? addr->address : addr->local_part);
 
-	if (strcasecmp(addr->local_part, "postmaster") == 0) {
+	if (doglob) {
+		val = (gchar *) table_find_func(alias_table, addr->address,
+				globaliascmp);
+
+	} else if (strcasecmp(addr->local_part, "postmaster") == 0) {
 		/*
 		**  postmaster must always be matched caseless
 		**  see RFC 822 and RFC 5321
 		*/
-		val = (gchar *) table_find_func(alias_table, addr->local_part, strcasecmp);
+		val = (gchar *) table_find_func(alias_table, addr->local_part,
+				strcasecmp);
 	} else {
-		val = (gchar *) table_find_func(alias_table, addr->local_part, conf.localpartcmp);
+		val = (gchar *) table_find_func(alias_table, addr->local_part,
+				conf.localpartcmp);
 	}
 	if (!val) {
-		DEBUG(5) debugf("alias: '%s' is fully expanded, hence completed\n",
-		                addr->local_part);
+		DEBUG(5) debugf("alias: '%s' is fully expanded, hence "
+				"completed\n",
+				doglob ? addr->address : addr->local_part);
 		return g_list_append(NULL, addr);
 	}
 
-	DEBUG(5) debugf("alias: '%s' -> '%s'\n", addr->local_part, val);
+	DEBUG(5) debugf("alias: '%s' -> '%s'\n",
+			doglob ? addr->address : addr->local_part, val);
 	val_list = parse_list(val);
 	alias_list = NULL;
 
@@ -149,7 +175,7 @@
 			alias_addr = create_address_qualified(val+1, TRUE, conf.host_name);
 			g_free(val);
 			DEBUG(6) debugf("alias:     address generated: '%s'\n",
-			                alias_addr->local_part);
+			                alias_addr->address);
 			alias_list = g_list_append(alias_list, alias_addr);
 			continue;
 		}
@@ -168,8 +194,8 @@
 		g_free(val);
 
 		if (!addr_is_local(alias_addr)) {
-			DEBUG(5) debugf("alias: '%s@%s' is non-local, hence completed\n",
-					alias_addr->local_part, alias_addr->domain);
+			DEBUG(5) debugf("alias: '%s' is non-local, hence completed\n",
+					alias_addr->address);
 			alias_list = g_list_append(alias_list, alias_addr);
 			continue;
 		}
@@ -186,7 +212,7 @@
 
 		/* recurse */
 		DEBUG(6) debugf("alias: >>\n");
-		alias_node = expand_one(alias_table, alias_addr);
+		alias_node = expand_one(alias_table, alias_addr, doglob);
 		DEBUG(6) debugf("alias: <<\n");
 		if (alias_node) {
 			alias_list = g_list_concat(alias_list, alias_node);
@@ -199,7 +225,8 @@
 }
 
 GList*
-alias_expand(GList *alias_table, GList *rcpt_list, GList *non_rcpt_list)
+alias_expand(GList *alias_table, GList *rcpt_list, GList *non_rcpt_list,
+		int doglob)
 {
 	GList *rcpt_node = NULL;
 	GList *alias_list = NULL;
@@ -214,14 +241,14 @@
 		addr = (address *) (rcpt_node->data);
 		if (addr_is_local(addr)) {
 			DEBUG(5) debugf("alias: (orig rcpt addr) expand local '%s'\n",
-			                addr->local_part);
-			alias_list = expand_one(alias_table, addr);
+			                doglob ? addr->address : addr->local_part);
+			alias_list = expand_one(alias_table, addr, doglob);
 			if (alias_list) {
 				done_list = g_list_concat(done_list, alias_list);
 			}
 		} else {
-			DEBUG(5) debugf("alias: (orig rcpt addr) don't expand non-local '%s@%s'\n",
-					addr->local_part, addr->domain);
+			DEBUG(5) debugf("alias: (orig rcpt addr) don't expand non-local '%s'\n",
+					addr->address);
 			done_list = g_list_append(done_list, addr);
 		}
 	}
--- a/src/conf.c	Sat Feb 18 11:43:06 2012 +0100
+++ b/src/conf.c	Sat Feb 18 12:35:12 2012 +0100
@@ -472,6 +472,8 @@
 			conf.do_relay = parse_boolean(rval);
 		else if (strcmp(lval, "alias_file") == 0) {
 			conf.alias_file = g_strdup(rval);
+		} else if (strcmp(lval, "globalias_file") == 0) {
+			conf.globalias_file = g_strdup(rval);
 		} else if (strcmp(lval, "caseless_matching") == 0) {
 			conf.localpartcmp = parse_boolean(rval) ? strcasecmp : strcmp;
 		} else if (strcmp(lval, "mbox_default") == 0) {
--- a/src/deliver.c	Sat Feb 18 11:43:06 2012 +0100
+++ b/src/deliver.c	Sat Feb 18 12:35:12 2012 +0100
@@ -774,6 +774,7 @@
 	GList *remote_msgout_list = NULL;
 	GList *msgout_node;
 	GList *alias_table = NULL;
+	GList *globalias_table = NULL;
 	gboolean ok = TRUE;
 
 	/* create msgout_list */
@@ -782,6 +783,9 @@
 		msgout_list = g_list_append(msgout_list, create_msg_out(msg));
 	}
 
+	if (conf.globalias_file) {
+		globalias_table = table_read(conf.globalias_file, ':');
+	}
 	if (conf.alias_file) {
 		alias_table = table_read(conf.alias_file, ':');
 	}
@@ -808,9 +812,19 @@
 				logwrite(LOG_ALERT, "invalid log_user address `%s', ignoring\n", conf.log_user);
 			}
 		}
+		if (globalias_table) {
+			GList *globaliased_rcpt_list;
+			globaliased_rcpt_list = alias_expand(globalias_table,
+					rcpt_list,
+					msgout->msg->non_rcpt_list, 1);
+			g_list_free(rcpt_list);
+			rcpt_list = globaliased_rcpt_list;
+		}
 		if (alias_table) {
 			GList *aliased_rcpt_list;
-			aliased_rcpt_list = alias_expand(alias_table, rcpt_list, msgout->msg->non_rcpt_list);
+			aliased_rcpt_list = alias_expand(alias_table,
+					rcpt_list,
+					msgout->msg->non_rcpt_list, 0);
 			g_list_free(rcpt_list);
 			rcpt_list = aliased_rcpt_list;
 		}
@@ -838,6 +852,9 @@
 	if (alias_table) {
 		destroy_table(alias_table);
 	}
+	if (globalias_table) {
+		destroy_table(globalias_table);
+	}
 
 	/* process local/remote msgout lists -> delivery */
 
--- a/src/masqmail.h	Sat Feb 18 11:43:06 2012 +0100
+++ b/src/masqmail.h	Sat Feb 18 12:35:12 2012 +0100
@@ -165,6 +165,7 @@
 
 	gchar *alias_file;
 	int (*localpartcmp) (const char *, const char *);
+	gchar *globalias_file;
 
 	GList *perma_routes;
 	GList *query_routes;  /* list of pairs which point to lists */
@@ -331,7 +332,8 @@
 
 /* alias.c*/
 gboolean addr_is_local(address *addr);
-GList *alias_expand(GList *alias_table, GList *rcpt_list, GList *non_rcpt_list);
+GList *alias_expand(GList *alias_table, GList *rcpt_list, GList *non_rcpt_list,
+		int doglob);
 
 /* child.c */
 int child(const char *command);