[exim-dev] better support for macros

Top Page
Delete this message
Reply to this message
Author: Kjetil Torgrim Homme
Date:  
To: exim-dev
Subject: [exim-dev] better support for macros
at our site, we use Exim on a number of different kinds of servers. to
keep complexity down, we have separated the common bits into files which
can be included in each exim.conf. at this time, we have 14 different
exim.conf, so I'm sure you understand that this is essential to be able
to maintain the files consistently.

however, the current macro handling in Exim is quite restrictive: they
have to be set in the main section, and they can't be redefined. this
means that most macro definitions must be duplicated across each
exim.conf as we can't put the default values in a common include file
and redefine them where needed.

the enclosed patch adds support for macro definitions in the sections
"acls", "authenticators", "routers" and "transports". I wasn't sure if
the syntax could be added to the sections "local_scans", "retrys" and
"rewrites" without breaking backwards compatibility, so I didn't change
those. a macro definition can not appear in the middle of a
router/acl/transport stanza.

the patch also adds support for redefining a macro using the "==" token.
it is an error to redefine a non-existing macro, but you can use this
syntax to gather data, e.g.

OPTIONS = an_option
OPTIONS == OPTIONS new_option
OPTIONS == OPTIONS another_option

note that

MACRO ==foo

would previously set MACRO to "=foo", but it will now cause a syntax
panic as "MACRO" is undefined. the fix is to add a space:

MACRO = =foo

although backwards compatibility is broken somewhat, I think it will be
rare, and it will be discovered very quickly indeed by the admin ;-)

the patch also fixes a small inconsistency in that

MACRO1 = foo
MACRO = bar

is allowed in today's Exim, whereas

MACRO = bar
MACRO1 = foo

causes a syntax panic due to MACRO1 containing the name of an already
defined macro.

I would love to see this patch included upstream. thank you for your
consideration.
--
Kjetil T.
postmaster, University of Oslo
--- exim-4.50/src/readconf.c.~1~    2005-02-17 15:49:11.000000000 +0100
+++ exim-4.50/src/readconf.c    2005-03-11 04:04:31.000000000 +0100
@@ -430,6 +430,92 @@
 }



+/* If an existing macro of the same name was defined on the command
+line, we just skip this definition. It's an error to attempt to
+redefine a macro without redef set to TRUE, or to redefine a macro
+when it hasn't been defined earlier.  It is also an error to define a
+macro whose name begins with the name of a previously-defined
+macro. */
+
+static void
+define_macro(uschar *name, int namelen, uschar *replacement, BOOL redef)
+{
+macro_item *m;
+macro_item *mlast = NULL;
+
+for (m = macros; m != NULL; m = m->next)
+  {
+  int len = Ustrlen(m->name);
+
+  if (Ustrcmp(m->name, name) == 0)
+    {
+    if (!m->command_line && !redef)
+      log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "macro \"%s\" is already "
+    "defined", name);
+    break;
+    }
+  if (len < namelen && Ustrstr(name, m->name) != NULL)
+    log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "\"%s\" cannot be defined as "
+      "a macro because previously defined macro \"%s\" is a substring",
+      name, m->name);
+  if (len > namelen && Ustrstr(m->name, name) != NULL)
+    log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "\"%s\" cannot be defined as "
+      "a macro because it is a substring of previously defined macro \"%s\"",
+      name, m->name);
+
+  mlast = m;
+  }
+
+if (m && m->command_line)
+  return; /* Found an overriding command-line definition */
+if (redef)
+  {
+  if (m == NULL)
+    log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "can't redefine undefined macro "
+      "\"%s\"", name);
+  if (mlast) mlast->next = m->next; else macros = m->next;
+  }
+
+/* The definition of the macro_item structure includes a final vector
+called "name" which is one byte long. Thus, adding "namelen" gives us
+enough room to store the "name" string. */
+m = store_get(sizeof(macro_item) + namelen);
+Ustrncpy(m->name, name, namelen);
+m->name[namelen] = 0;
+m->command_line = FALSE;
+m->replacement = string_copy(replacement);
+m->next = macros;
+macros = m;
+}
+
+
+/* The string line should contain a complete logical line, and start with
+the first letter of the macro name.  The macro name and the
+replacement text are extracted and stored. */
+void
+read_macro_assignment(uschar *line)
+{
+uschar name[64];
+int namelen = 0;
+uschar *s = line;
+BOOL redef = FALSE;
+
+while (isalnum(*s) || *s == '_')
+  {
+  if (namelen >= sizeof(name) - 1)
+    log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
+      "macro name too long (maximum is %d characters)", sizeof(name) - 1);
+  name[namelen++] = *s++;
+  }
+name[namelen] = 0;
+while (isspace(*s)) s++;
+if (*s++ != '=') log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
+  "malformed macro definition");
+if (*s == '=') { redef = TRUE; s++; }
+while (isspace(*s)) s++;
+define_macro(name, namelen, s, redef);
+}
+




@@ -2629,65 +2715,7 @@
 while ((s = get_config_line()) != NULL)
   {
   if (isupper(s[0]))
-    {
-    macro_item *m;
-    macro_item *mlast = NULL;
-    uschar name[64];
-    int namelen = 0;
-
-    while (isalnum(*s) || *s == '_')
-      {
-      if (namelen >= sizeof(name) - 1)
-        log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
-          "macro name too long (maximum is %d characters)", sizeof(name) - 1);
-      name[namelen++] = *s++;
-      }
-    name[namelen] = 0;
-    while (isspace(*s)) s++;
-    if (*s++ != '=') log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN,
-      "malformed macro definition");
-    while (isspace(*s)) s++;
-
-    /* If an existing macro of the same name was defined on the command line,
-    we just skip this definition. Otherwise it's an error to attempt to
-    redefine a macro. It is also an error to define a macro whose name begins
-    with the name of a previously-defined macro. */
-
-    for (m = macros; m != NULL; m = m->next)
-      {
-      int len = Ustrlen(m->name);
-
-      if (Ustrcmp(m->name, name) == 0)
-        {
-        if (m->command_line) break;
-        log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "macro \"%s\" is already "
-          "defined", name);
-        }
-
-      if (len < namelen && Ustrstr(name, m->name) != NULL)
-        log_write(0, LOG_CONFIG|LOG_PANIC_DIE, "\"%s\" cannot be defined as "
-          "a macro because previously defined macro \"%s\" is a substring",
-          name, m->name);
-
-      mlast = m;
-      }
-    if (m != NULL) continue;   /* Found an overriding command-line definition */
-
-    m = store_get(sizeof(macro_item) + namelen);
-    m->next = NULL;
-    m->command_line = FALSE;
-    if (mlast == NULL) macros = m; else mlast->next = m;
-
-    /* This use of strcpy() is OK because we have obtained a block of store
-    whose size is based on the length of "name". The definition of the
-    macro_item structure includes a final vector called "name" which is one
-    byte long. Thus, adding "namelen" gives us enough room to store the "name"
-    string. */
-
-    Ustrcpy(m->name, name);
-    m->replacement = string_copy(s);
-    }
-
+    read_macro_assignment(s);
   else if (Ustrncmp(s, "domainlist", 10) == 0)
     read_named_list(&domainlist_anchor, &domainlist_count,
       MAX_NAMED_LIST, s+10, US"domain list");
@@ -3079,6 +3107,13 @@


uschar *s = readconf_readname(name, sizeof(name), buffer);

+  /* Look for a macro (re)definition */
+  if (isupper(*name) && *s == '=')
+    {
+    read_macro_assignment(buffer);
+    continue;
+    }
+
   /* If the line starts with a name terminated by a colon, we are at the
   start of the definition of a new driver. The rest of the line must be
   blank. */
@@ -3590,6 +3625,12 @@
   uschar *error;


   p = readconf_readname(name, sizeof(name), acl_line);
+  if (isupper(*name) && *p == '=')
+    {
+    read_macro_assignment(acl_line);
+    acl_line = get_config_line();
+    continue;
+    }
   if (*p != ':')
     log_write(0, LOG_PANIC_DIE|LOG_CONFIG_IN, "missing ACL name");


--- exim-4.50/src/acl.c.~1~    2005-02-17 15:49:11.000000000 +0100
+++ exim-4.50/src/acl.c    2005-03-11 02:43:29.000000000 +0100
@@ -542,6 +542,11 @@
   /* Read the name of a verb or a condition, or the start of a new ACL */


   s = readconf_readname(name, sizeof(name), s);
+  if (*s == '=' && isupper(*name))
+    {
+    read_macro_assignment(saveline);
+    continue;
+    }
   if (*s == ':')
     {
     if (negated || name[0] == 0)