[PATCH] Rewrite ##X tags in headers of incoming messages
authorMark Wooding <mdw@distorted.org.uk>
Tue, 14 Feb 2006 15:52:22 +0000 (15:52 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Tue, 14 Feb 2006 15:52:22 +0000 (15:52 +0000)
Users of ezmlm lists sometimes do not know their subscription address
when they want to unsubscribe. Not all get the "Return-Path" header from
their delivery agent/MUA, and of the ones that do, many can still not
decipher the information. rfc2369 provides a standard way to supply
unsubscribe information. For the header to work optimally, it should
contain the command adapted to the subscriber's subscription address.

These patches enable qmail to replace tags with the subscribers address.
This is normally done ONLY in headers to avoid the risk of message
corruption.
If for some reason no substitution is done, the header remains in its
original form, which is harmless as far as message integrity is
concerned.

If any header starts with '#' this character is removed and substitution
will be extented into the body. This is safe since no legal header
starts
with '#'. It is assumed that for messages with this flag it is desired
that any tag in the message is substituted.

Assume: Subscriber=user@host, list=list@listhost.

and header added by ezmlm:
    List-Unsubscribe: <mailto:list-unsubscribe-##L=##H@listhost>

Then: Header after qmail processing:
      List-Unsubscribe: <mailto:list-unsubscribe-user=host@listhost>

qmail-local.c
qmail-remote.c

index cd01602..ec4e5e7 100644 (file)
@@ -63,6 +63,51 @@ stralloc ueo = {0};
 stralloc cmds = {0};
 stralloc messline = {0};
 stralloc foo = {0};
+stralloc qsender = {0};
+stralloc tmpline = {0};
+char *verhhost = (char *)0;
+char *verhlocal = (char *)0;
+int flagheader,flagdobody;
+unsigned int i;
+
+int verhline(sa)
+stralloc *sa;
+/* substitutes ##L => recipient local, ##H => recipient host if VERP sender */
+/* returns 0 normally, -1 on out-of-memory */
+{
+  register char *cp;
+  char *cpnext,*cpafter;
+
+  if (!verhlocal) return 0;                            /* no VERP SENDER */
+  cp = sa->s;
+  cpnext = sa->s;
+  cpafter = cp + sa->len;
+  tmpline.len = 0;                                     /* clear */
+  for (;;) {
+    while (cp < cpafter && *cp++ != '#');
+    if (cp + 1 < cpafter && *cp == '#') {              /* found '##' */
+      cp++;
+      if (*cp == 'L') {                                        /* ##L */
+       if (!stralloc_catb(&tmpline,cpnext,cp - cpnext - 2)) return -1;
+       cp++;
+       cpnext = cp;
+       if (!stralloc_cats(&tmpline,verhlocal)) return -1;
+      } else if (*cp == 'H') {                         /* ##H */
+       if (!stralloc_catb(&tmpline,cpnext,cp - cpnext - 2)) return -1;
+       cp++;
+       cpnext = cp;
+       if (!stralloc_cats(&tmpline,verhhost)) return -1;
+      }
+    }
+    if (cp >= cpafter) {
+      if (tmpline.len) {       /* true if we've done any substitutions */
+       if (!stralloc_catb(&tmpline,cpnext,cpafter - cpnext)) return -1;
+       if (!stralloc_copy(sa,&tmpline)) return -1;
+      }
+      return 0;
+    }
+  }
+}
 
 char buf[1024];
 char outbuf[1024];
@@ -82,6 +127,7 @@ char *dir;
  char host[64];
  char *s;
  int loop;
+ int match;
  struct stat st;
  int fd;
  substdio ss;
@@ -117,11 +163,27 @@ char *dir;
  if (substdio_put(&ssout,rpline.s,rpline.len) == -1) goto fail;
  if (substdio_put(&ssout,dtline.s,dtline.len) == -1) goto fail;
 
- switch(substdio_copy(&ssout,&ss))
-  {
-   case -2: tryunlinktmp(); _exit(4);
-   case -3: goto fail;
-  }
+ flagheader = 1;
+ flagdobody = 0;
+ do {                          /* for VERH */
+   if (getln(&ss,&messline,&match,'\n') != 0)
+     { tryunlinktmp(); _exit(4); }
+   if (flagheader) {
+     if (match && messline.len == 1) {
+       flagheader = 0;
+       if (!flagdobody) verhlocal = (char *)0;
+     }
+     if (messline.s[0] == '#') {       /* continue in body */
+       flagdobody = 1;                 /* remove leading '#' */
+       for (i = 1; i < messline.len; i++)
+        messline.s[i - 1] = messline.s[i];
+       messline.len--;                 /* always >= 1 from \n */
+     }
+   }
+   if (verhlocal)
+     if (verhline(&messline) == -1) goto fail;
+   if (substdio_put(&ssout,messline.s,messline.len) == -1) goto fail;
+ } while (match);
 
  if (substdio_flush(&ssout) == -1) goto fail;
  if (fsync(fd) == -1) goto fail;
@@ -193,6 +255,8 @@ char *fn;
 
  substdio_fdbuf(&ss,read,0,buf,sizeof(buf));
  substdio_fdbuf(&ssout,write,fd,outbuf,sizeof(outbuf));
+ flagheader = 1;
+ flagdobody = 0;
  if (substdio_put(&ssout,ufline.s,ufline.len)) goto writeerrs;
  if (substdio_put(&ssout,rpline.s,rpline.len)) goto writeerrs;
  if (substdio_put(&ssout,dtline.s,dtline.len)) goto writeerrs;
@@ -207,6 +271,20 @@ char *fn;
    if (!match && !messline.len) break;
    if (gfrom(messline.s,messline.len))
      if (substdio_bput(&ssout,">",1)) goto writeerrs;
+   if (flagheader) {
+     if (match && messline.len == 1) {
+       if (!flagdobody) verhlocal = (char *)0;
+       flagheader = 0;
+     }
+     if (messline.s[0] == '#') {               /* continue in body */
+       flagdobody = 1;                         /* remove leading '#' */
+       for (i = 1; i < messline.len; i++)
+         messline.s[i - 1] = messline.s[i];
+       messline.len--;
+     }
+   }
+   if (verhlocal)
+     if (verhline(&messline) == -1) goto writeerrs;
    if (substdio_bput(&ssout,messline.s,messline.len)) goto writeerrs;
    if (!match)
     {
@@ -276,9 +354,24 @@ char **recips;
  if (qmail_open(&qqt) == -1) temp_fork();
  mailforward_qp = qmail_qp(&qqt);
  qmail_put(&qqt,dtline.s,dtline.len);
+ flagheader = 1;
  do
   {
    if (getln(&ss,&messline,&match,'\n') != 0) { qmail_fail(&qqt); break; }
+   if (flagheader) {
+     if (match && messline.len == 1) {
+       flagheader = 0;
+       if (!flagdobody) verhlocal = (char *)0;
+     }
+     if (messline.s[0] == '#') {       /* continue in body */
+       flagdobody = 1;                 /* remove leading '#' */
+       for (i = 1; i < messline.len; i++)
+        messline.s[i - 1] = messline.s[i];
+       messline.len--;
+     }
+   }
+   if (verhlocal)
+     if (verhline(&messline) == -1) { qmail_fail(&qqt); break; }
    qmail_put(&qqt,messline.s,messline.len);
   }
  while (match);
@@ -458,6 +551,7 @@ char **argv;
  datetime_sec starttime;
  int flagforwardonly;
  char *x;
+ char *cplast;
 
  umask(077);
  sig_pipeignore();
@@ -518,9 +612,9 @@ char **argv;
 
  if (!env_put2("SENDER",sender)) temp_nomem();
 
- if (!quote2(&foo,sender)) temp_nomem();
+ if (!quote2(&qsender,sender)) temp_nomem();
  if (!stralloc_copys(&rpline,"Return-Path: <")) temp_nomem();
- if (!stralloc_cat(&rpline,&foo)) temp_nomem();
+ if (!stralloc_cat(&rpline,&qsender)) temp_nomem();
  for (i = 0;i < rpline.len;++i) if (rpline.s[i] == '\n') rpline.s[i] = '_';
  if (!stralloc_cats(&rpline,">\n")) temp_nomem();
 
@@ -528,6 +622,33 @@ char **argv;
  if (!stralloc_0(&foo)) temp_nomem();
  if (!env_put2("RPLINE",foo.s)) temp_nomem();
 
+ i = byte_rchr(qsender.s,qsender.len,'@');             /* for VERH */
+ if (i != qsender.len) {                               /* got @ */
+   cplast = qsender.s + i;
+   *cplast = '\0';
+   if (qsender.s[i = str_rchr(qsender.s,'=')]) {       /* got = */
+     qsender.s[i] = '\0';
+     cplast = qsender.s + i;
+     verhhost = qsender.s + i + 1;
+     i = str_rchr(qsender.s,'-');
+     if (qsender.s[i] == '-') {
+       for (;;) {
+        if (case_starts(qsender.s + i + 1,"return-")) {
+          verhlocal = qsender.s + i + 9 + str_chr(qsender.s + i + 8,'-');
+                                       /* here to avoid work if not VERP */
+                                       /* verhhost not used if verhlocal=0 */
+           for (x = verhlocal; x < cplast; x++)
+             if (*x == '\n') *x = '_'; /* \n would ruin */
+          break;
+        }
+        j = byte_rchr(qsender.s,i,'-');
+        if (j == i) break;
+        i = j;
+       }
+     }
+   }
+ }
+
  if (!stralloc_copys(&ufline,"From ")) temp_nomem();
  if (*sender)
   {
index 7d65473..864dee2 100644 (file)
@@ -189,19 +189,68 @@ char *append;
   zerodie();
 }
 
+stralloc verh = {0};                           /* quoted recipient */
+int flagverh;                                  /* argc */
+char *vp;                                      /* argv[3] */
+
 void blast()
 {
+  unsigned int posat, i;
+  int flagdobody,flagheader;
   int r;
   char ch;
 
+  posat = 0;                                   /* stays 0 if no VERH */
+  flagdobody = 0;                              /* => 0 at first blank line */
+  flagheader = 1;
+  if (flagverh == 4) {                         /* only if single recipient */
+    if (!quote2(&verh,vp)) temp_nomem();       /* non-canonicalized */
+    for (i = 0; i < verh.len; i++)             /* \n would destroy message */
+      if (verh.s[i] == '\n') verh.s[i] = '_';
+    posat = byte_rchr(verh.s,verh.len,'@');    /* posat=0 if no VERH */
+    if (posat == verh.len) posat = 0;
+  }
   for (;;) {
     r = substdio_get(&ssin,&ch,1);
     if (r == 0) break;
     if (r == -1) temp_read();
     if (ch == '.')
       substdio_put(&smtpto,".",1);
+    if (flagheader) {
+      if (ch == '\n') {                /* header ends */
+       flagheader = 0;
+       if (!flagdobody) posat = 0;
+      } else if (ch == '#') {  /* # starting line => VERH ... */
+       flagdobody = 1;         /* continues in body and ... */
+       continue;               /* character is suppressed. */
+      }
+    }
     while (ch != '\n') {
-      substdio_put(&smtpto,&ch,1);
+      if (ch == '#' && posat) {                        /*   ... # */
+        r = substdio_get(&ssin,&ch,1);
+        if (r == 0) perm_partialline();
+        if (r == -1) temp_read();
+        if (ch == '#') {                       /*  ... ## */
+         register char ch1;
+         ch1 = *substdio_peek(&ssin);
+         if (ch1 != 'L' && ch1 != 'H') {       /*  ... ##x x!=L x!=H */
+           substdio_put(&smtpto,"#",1);
+           continue;
+         }
+         r = substdio_get(&ssin,&ch,1);
+         if (r == 0) perm_partialline();
+         if (r == -1) temp_read();
+         if (ch == 'L')                        /* ... ##L */
+           substdio_put(&smtpto,verh.s,posat);
+         else                                  /* ... ##H */
+           substdio_put(&smtpto,verh.s + posat + 1,verh.len - posat - 1);
+       } else {
+         substdio_put(&smtpto,"#",1);
+         if (ch == '\n') break;
+         substdio_put(&smtpto,&ch,1);
+        }
+      } else
+        substdio_put(&smtpto,&ch,1);
       r = substdio_get(&ssin,&ch,1);
       if (r == 0) perm_partialline();
       if (r == -1) temp_read();
@@ -341,6 +390,8 @@ char **argv;
  
   sig_pipeignore();
   if (argc < 4) perm_usage();
+  flagverh = argc;
+  vp = argv[3];
   if (chdir(auto_qmail) == -1) temp_chdir();
   getcontrols();