chiark / gitweb /
Debianization and various other fixes.
[ezmlm] / ezmlm-send.c
1 /* $Id: ezmlm-send.c,v 1.77 1999/10/29 02:49:14 lindberg Exp $*/
2 /* $Name: ezmlm-idx-040 $*/
3 #include "stralloc.h"
4 #include "subfd.h"
5 #include "strerr.h"
6 #include "error.h"
7 #include "qmail.h"
8 #include "env.h"
9 #include "lock.h"
10 #include "sig.h"
11 #include "open.h"
12 #include "getln.h"
13 #include "case.h"
14 #include "scan.h"
15 #include "str.h"
16 #include "fmt.h"
17 #include "readwrite.h"
18 #include "exit.h"
19 #include "substdio.h"
20 #include "getconf.h"
21 #include "constmap.h"
22 #include "byte.h"
23 #include "sgetopt.h"
24 #include "quote.h"
25 #include "subscribe.h"
26 #include "mime.h"
27 #include "errtxt.h"
28 #include "makehash.h"
29 #include "cookie.h"
30 #include "idx.h"
31 #include "copy.h"
32
33 int flagnoreceived = 1;         /* suppress received headers by default. They*/
34                                 /* are still archived. =0 => archived and */
35                                 /* copied. */
36 int flaglog = 1;                /* for lists with mysql support, use tags */
37                                 /* and log traffic to the database */
38 #define FATAL "ezmlm-send: fatal: "
39
40 void die_usage()
41 {
42   strerr_die1x(100,"ezmlm-send: usage: ezmlm-send [-cClLqQrR] [-h header] dir");
43 }
44 void die_nomem()
45 {
46   strerr_die2x(111,FATAL,ERR_NOMEM);
47 }
48
49         /* for writing new index file indexn later moved to index. */
50 substdio ssindexn;
51 char indexnbuf[1024];
52
53 char strnum[FMT_ULONG];
54 char szmsgnum[FMT_ULONG];
55 char hash[HASHLEN];
56
57 stralloc fnadir = {0};
58 stralloc fnaf = {0};
59 stralloc fnif = {0};
60 stralloc fnifn = {0};
61 stralloc fnsub = {0};
62 stralloc line = {0};
63 stralloc qline = {0};
64 stralloc lines = {0};
65 stralloc subject = {0};
66 stralloc from = {0};
67 stralloc received = {0};
68 stralloc prefix = {0};
69 stralloc content = {0};
70 stralloc boundary = {0};
71 stralloc charset = {0};
72 stralloc dcprefix = {0};
73 stralloc dummy = {0};
74 stralloc qmqpservers = {0};
75
76 void die_indexn()
77 {
78   strerr_die4x(111,FATAL,ERR_WRITE,fnifn.s,": ");
79 }
80
81 void *psql = (void *) 0;
82
83 unsigned long innum;
84 unsigned long outnum;
85 unsigned long msgnum;
86 unsigned long hash_lo = 0L;
87 unsigned long hash_hi = 52L;
88 unsigned long msgsize = 0L;
89 unsigned long cumsize = 0L;     /* cumulative archive size bytes / 256 */
90 char flagcd = '\0';             /* no transfer-encoding for trailer */
91 char encin = '\0';
92 int flagindexed;
93 int flagfoundokpart;            /* Found something to pass on. If multipart */
94                                 /* we set to 0 and then set to 1 for any */
95                                 /* acceptable mime part. If 0 -> reject */
96 int flagreceived;
97 int flagprefixed;
98 unsigned int serial = 0;
99 int flagarchived;
100 int fdarchive;
101 int fdindex;
102 int fdindexn;
103 char hashout[COOKIE+1];
104
105 substdio ssarchive;
106 char archivebuf[1024];
107
108 int flagsublist;
109 stralloc sublist = {0};
110 stralloc mailinglist = {0};
111 stralloc outlocal = {0};
112 stralloc outhost = {0};
113 stralloc headerremove = {0};
114 struct constmap headerremovemap;
115 stralloc mimeremove = {0};
116 struct constmap mimeremovemap;
117 char *dir;
118
119 struct qmail qq;
120 substdio ssin;
121 char inbuf[1024];
122 substdio ssout;
123 char outbuf[1];
124
125 char textbuf[512];
126 substdio sstext;
127
128 unsigned int mywrite(fd,buf,len)
129 int fd;
130 char *buf;
131 unsigned int len;
132 {
133   qmail_put(&qq,buf,len);
134   return len;
135 }
136
137 int subto(s,l)
138 char *s;
139 unsigned int l;
140 {
141   qmail_put(&qq,"T",1);
142   qmail_put(&qq,s,l);
143   qmail_put(&qq,"",1);
144   return (int) l;
145 }
146
147 void die_archive()
148 {
149   strerr_die4sys(111,FATAL,ERR_WRITE,fnaf.s,": ");
150 }
151 void die_numnew()
152 {
153   strerr_die3sys(111,FATAL,ERR_CREATE,"numnew: ");
154 }
155
156 void qa_put(buf,len) char *buf; unsigned int len;
157 {
158   qmail_put(&qq,buf,len);
159   if (flagarchived)
160     if (substdio_put(&ssarchive,buf,len) == -1) die_archive();
161 }
162
163 void qa_puts(buf) char *buf;
164 {
165   qmail_puts(&qq,buf);
166   if (flagarchived)
167     if (substdio_puts(&ssarchive,buf) == -1) die_archive();
168 }
169
170 int sublistmatch(sender)
171 char *sender;
172 {
173   unsigned int i;
174   unsigned int j;
175
176   j = str_len(sender);
177   if (j < sublist.len) return 0;
178
179   i = byte_rchr(sublist.s,sublist.len,'@');
180   if (i == sublist.len) return 1;
181
182   if (byte_diff(sublist.s,i,sender)) return 0;
183   if (case_diffb(sublist.s + i,sublist.len - i,sender + j - (sublist.len - i)))
184     return 0;
185
186   return 1;
187 }
188
189 substdio ssnumnew;
190 char numnewbuf[16];
191
192 char buf0[256];
193 substdio ss0 = SUBSTDIO_FDBUF(read,0,buf0,sizeof(buf0));
194
195 void numwrite()
196 {               /* this one deals with msgnum, not outnum! */
197   int fd;
198
199   fd = open_trunc("numnew");
200   if (fd == -1) die_numnew();
201   substdio_fdbuf(&ssnumnew,write,fd,numnewbuf,sizeof(numnewbuf));
202   if (substdio_put(&ssnumnew,strnum,fmt_ulong(strnum,msgnum)) == -1)
203     die_numnew();
204   if (substdio_puts(&ssnumnew,":") == -1) die_numnew();
205   if (substdio_put(&ssnumnew,strnum,fmt_ulong(strnum,cumsize)) == -1)
206     die_numnew();
207
208   if (substdio_puts(&ssnumnew,"\n") == -1) die_numnew();
209   if (substdio_flush(&ssnumnew) == -1) die_numnew();
210   if (fsync(fd) == -1) die_numnew();
211   if (close(fd) == -1) die_numnew(); /* NFS stupidity */
212   if (rename("numnew","num") == -1)
213     strerr_die3sys(111,FATAL,ERR_MOVE,"numnew: ");
214 }
215
216 stralloc mydtline = {0};
217
218 int idx_copy_insertsubject()
219 /* copies old index file up to but not including msg, then adds a line with */
220 /* 'sub' trimmed of reply indicators, then closes the new index and moves it*/
221 /* to the name 'index'. Errors are dealt with directly, and if the routine  */
222 /* returns, it was successful. 'fatal' points to a program-specific error   */
223 /* string. Sub is not destroyed, but from is!!!                             */
224 /* returns 1 if reply-indicators were found, 0 otherwise.                   */
225 /* no terminal \n or \0 in any of the strallocs! */
226 {
227   char *cp;
228   unsigned long idx;
229   int match;
230   int r;
231   unsigned int pos;
232
233   if (!stralloc_copys(&fnadir,"archive/")) die_nomem();
234   if (!stralloc_catb(&fnadir,strnum,fmt_ulong(strnum,outnum / 100)))
235         die_nomem();
236   if (!stralloc_copy(&fnif,&fnadir)) die_nomem();
237   if (!stralloc_copy(&fnifn,&fnif)) die_nomem();
238   if (!stralloc_cats(&fnif,"/index")) die_nomem();
239   if (!stralloc_cats(&fnifn,"/indexn")) die_nomem();
240   if (!stralloc_0(&fnif)) die_nomem();
241   if (!stralloc_0(&fnifn)) die_nomem();
242   if (!stralloc_0(&fnadir)) die_nomem();
243
244                         /* may not exists since we run before ezmlm-send */
245   if (mkdir(fnadir.s,0755) == -1)
246     if (errno != error_exist)
247       strerr_die4x(111,FATAL,ERR_CREATE,fnadir.s,": ");
248
249                         /* Open indexn */
250   fdindexn = open_trunc(fnifn.s);
251   if (fdindexn == -1)
252     strerr_die4x(111,FATAL,ERR_WRITE,fnifn.s,": ");
253
254                         /* set up buffers for indexn */
255   substdio_fdbuf(&ssindexn,write,fdindexn,indexnbuf,sizeof(indexnbuf));
256
257   concatHDR(subject.s,subject.len,&lines,FATAL);        /* make 1 line */
258   decodeHDR(lines.s,lines.len,&qline,charset.s,FATAL);  /* decode mime */
259   r = unfoldHDR(qline.s,qline.len,&lines,charset.s,&dcprefix,1,FATAL);
260                                                  /* trim mime */
261
262   fdindex = open_read(fnif.s);
263   if (fdindex == -1) {
264     if (errno != error_noent)
265       strerr_die4x(111,FATAL,ERR_OPEN, fnif.s, ": ");
266   } else {
267     substdio_fdbuf(&ssin,read,fdindex,inbuf,sizeof(inbuf));
268     for(;;) {
269       if (getln(&ssin,&qline,&match,'\n') == -1)
270         strerr_die4sys(111,FATAL,ERR_READ, fnif.s, ": ");
271       if (!match)
272         break;
273       pos = scan_ulong(qline.s,&idx);
274       if (!idx)                         /* "impossible!" */
275         strerr_die2x(111,FATAL,ERR_BAD_INDEX);
276       if (idx >= outnum)
277         break;                          /* messages always come in order */
278       if (substdio_put(&ssindexn,qline.s,qline.len) == -1)
279         die_indexn();
280       if (qline.s[pos] == ':') {        /* has author line */
281         if (getln(&ssin,&qline,&match,'\n') == -1)
282           strerr_die4x(111,FATAL,ERR_READ, fnif.s, ": ");
283         if (!match && qline.s[0] != '\t')       /* "impossible! */
284           strerr_die2x(111,FATAL,ERR_BAD_INDEX);
285         if (substdio_put(&ssindexn,qline.s,qline.len) == -1)
286           die_indexn();
287       }
288     }
289     close(fdindex);
290   }
291   if (!stralloc_copyb(&qline,strnum,fmt_ulong(strnum,outnum))) die_nomem();
292   if (!stralloc_cats(&qline,": ")) die_nomem(); /* ':' for new ver */
293   makehash(lines.s,lines.len,hash);
294   if (!stralloc_catb(&qline,hash,HASHLEN)) die_nomem();
295   if (!stralloc_cats(&qline," ")) die_nomem();
296   if (r & 1)            /* reply */
297     if (!stralloc_cats(&qline,"Re: ")) die_nomem();
298   if (!stralloc_cat(&qline,&lines)) die_nomem();
299   if (!stralloc_cats(&qline,"\n\t")) die_nomem();
300   if (!stralloc_cat(&qline,&received)) die_nomem();
301   if (!stralloc_cats(&qline,";")) die_nomem();
302
303   concatHDR(from.s,from.len,&lines,FATAL);
304   mkauthhash(lines.s,lines.len,hash);
305
306   if (!stralloc_catb(&qline,hash,HASHLEN)) die_nomem();
307   if (!stralloc_cats(&qline," ")) die_nomem();
308
309   decodeHDR(cp,author_name(&cp,lines.s,lines.len),&from,charset.s,FATAL);
310   (void) unfoldHDR(from.s,from.len,&lines,charset.s,&dcprefix,0,FATAL);
311   if (!stralloc_cat(&qline,&lines)) die_nomem();
312
313   if (!stralloc_cats(&qline,"\n")) die_nomem();
314   if (substdio_put(&ssindexn,qline.s,qline.len) == -1) die_indexn();
315   if (substdio_flush(&ssindexn) == -1) die_indexn();
316   if (fsync(fdindexn) == -1) die_indexn();
317   if (fchmod(fdindexn,MODE_ARCHIVE | 0700) == -1) die_indexn();
318   if (close(fdindexn) == -1) die_indexn(); /* NFS stupidity */
319   if (rename(fnifn.s,fnif.s) == -1)
320     strerr_die4x(111,FATAL,ERR_MOVE,fnifn.s,": ");
321   return r;
322 }
323
324 void transferenc()
325 {
326         if (flagcd) {
327           qmail_puts(&qq,"\nContent-Transfer-Encoding: ");
328           if (flagcd == 'Q')
329             qmail_puts(&qq,"Quoted-printable\n\n");
330           else
331             qmail_puts(&qq,"base64\n\n");
332         } else
333           qmail_puts(&qq,"\n\n");
334 }
335
336 void getcharset()
337 {
338     if (getconf_line(&charset,"charset",0,FATAL,dir)) {
339       if (charset.len >= 2 && charset.s[charset.len - 2] == ':') {
340         if (charset.s[charset.len - 1] == 'B' ||
341                         charset.s[charset.len - 1] == 'Q') {
342           flagcd = charset.s[charset.len - 1];
343           charset.s[charset.len - 2] = '\0';
344         }
345       }
346     } else
347       if (!stralloc_copys(&charset,TXT_DEF_CHARSET)) die_nomem();
348
349     if (!stralloc_0(&charset)) die_nomem();
350 }
351
352 void main(argc,argv)
353 int argc;
354 char **argv;
355 {
356   unsigned long subs;
357   int fdlock;
358   char *sender;
359   char *mlheader = (char *) 0;
360   char *ret;
361   char *err;
362   int flagmlwasthere;
363   int flagqmqp = 0;     /* don't use qmqp by default */
364   int flaglistid = 0;   /* no listid header added */
365   int match;
366   unsigned int i;
367   int r,fd;
368   int flaginheader;
369   int flagbadfield;
370   int flagbadpart;
371   int flagseenext;
372   int flagsubline;
373   int flagfromline;
374   int flagcontline;
375   int flagarchiveonly;
376   int flagtrailer;
377   unsigned int pos;
378   int opt;
379   char *cp, *cpstart, *cpafter;
380
381   umask(022);
382   sig_pipeignore();
383
384   while ((opt = getopt(argc,argv,"cCh:H:lLrRqQs:S:vV")) != opteof)
385     switch(opt) {
386       case 'c': case 'C': break;        /* ignore for backwards compat */
387       case 'h':
388       case 'H': mlheader = optarg;      /* Alternative sublist check header */
389                 mlheader[str_chr(mlheader,':')] = '\0';
390                 break;
391       case 'l': flaglog = 1; break;
392       case 'L': flaglog = 0; break;
393       case 'r': flagnoreceived = 0; break;
394       case 'R': flagnoreceived = 1; break;
395       case 's':
396       case 'S': pos = scan_ulong(optarg,&hash_lo);
397                 if (!optarg[pos++]) break;
398                 (void) scan_ulong(optarg+pos,&hash_hi);
399                 if (hash_hi > 52L) hash_hi = 52L;
400                 if (hash_lo > hash_hi) hash_lo = hash_hi;
401
402  break;
403       case 'q': flagqmqp = 0; break;
404       case 'Q': flagqmqp = 1; break;
405       case 'v':
406       case 'V': strerr_die2x(0,
407                 "ezmlm-send version: ezmlm-0.53+",EZIDX_VERSION);
408       default:
409         die_usage();
410     }
411
412
413   dir = argv[optind++];
414   if (!dir) die_usage();
415
416   sender = env_get("SENDER");
417
418   if (chdir(dir) == -1)
419     strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": ");
420
421   fdlock = open_append("lock");
422   if (fdlock == -1)
423     strerr_die4sys(111,FATAL,ERR_OPEN,dir,"/lock: ");
424   if (lock_ex(fdlock) == -1)
425     strerr_die4sys(111,FATAL,ERR_OBTAIN,dir,"/lock: ");
426
427   flagarchived = getconf_line(&line,"archived",0,FATAL,dir);
428   flagindexed = getconf_line(&line,"indexed",0,FATAL,dir);
429   getcharset();
430   flagprefixed = getconf_line(&prefix,"prefix",0,FATAL,dir);
431   if (prefix.len) {             /* encoding and serial # support */
432                                 /* no sanity checks - you put '\n' or '\0' */
433                                 /* into the coded string, you pay */
434
435     decodeHDR(prefix.s,prefix.len,&line,charset.s,FATAL);
436     (void) unfoldHDR(line.s,line.len,&dcprefix,charset.s,&dummy,0,FATAL);
437     if (!stralloc_copy(&dcprefix,&line)) die_nomem();
438     serial = byte_rchr(prefix.s,prefix.len,'#');
439   }
440   if ((fd = open_read("text/trailer")) == -1) { /* see if there is a trailer */
441     if (errno == error_noent) flagtrailer = 0;
442     else strerr_die2sys(111,ERR_OPEN,"text/trailer: ");
443   } else {
444     close(fd);
445     flagtrailer = 1;
446   }
447
448   getconf(&mimeremove,"mimeremove",0,FATAL,dir);
449
450   if (getconf_line(&line,"num",0,FATAL,dir)) {  /* Now non-FATAL, def=0 */
451     if (!stralloc_0(&line)) die_nomem();
452     cp = line.s + scan_ulong(line.s,&msgnum);
453     ++msgnum;
454     if (*cp++ == ':')
455       scan_ulong(cp,&cumsize);
456   } else
457     msgnum = 1L;                        /* if num not there */
458
459   getconf_line(&outhost,"outhost",1,FATAL,dir);
460   getconf_line(&outlocal,"outlocal",1,FATAL,dir);
461   set_cpoutlocal(&outlocal);
462   set_cpouthost(&outhost);
463   flagsublist = getconf_line(&sublist,"sublist",0,FATAL,dir);
464
465   if (flagqmqp) {                       /* forward compatibility ;-) */
466     if (!stralloc_copys(&line,QMQPSERVERS)) die_nomem();
467     if (!stralloc_cats(&line,"/0")) die_nomem();
468     if (!stralloc_0(&line)) die_nomem();
469     (void) getconf_line(&qmqpservers,line.s,0,FATAL,dir);
470   }
471
472   getconf(&headerremove,"headerremove",1,FATAL,dir);
473   if (!constmap_init(&headerremovemap,headerremove.s,headerremove.len,0))
474         die_nomem();
475
476   if (!stralloc_copys(&mydtline,"Delivered-To: mailing list ")) die_nomem();
477   if (!stralloc_catb(&mydtline,outlocal.s,outlocal.len)) die_nomem();
478   if (!stralloc_cats(&mydtline,"@")) die_nomem();
479   if (!stralloc_catb(&mydtline,outhost.s,outhost.len)) die_nomem();
480   if (!stralloc_cats(&mydtline,"\n")) die_nomem();
481
482   if (sender) {
483     if (!*sender)
484       strerr_die2x(100,FATAL,ERR_BOUNCE);
485     if (str_equal(sender,"#@[]"))
486       strerr_die2x(100,FATAL,ERR_BOUNCE);
487     if (flagsublist)
488       if (!sublistmatch(sender))
489         strerr_die2x(100,FATAL,ERR_NOT_PARENT);
490   }
491   innum = msgnum;                               /* innum = incoming */
492   outnum = msgnum;                              /* outnum = outgoing */
493   if (flagsublist && !flagarchived) {           /* msgnum = archive */
494     pos = byte_rchr(sublist.s,sublist.len,'@'); /* checked in sublistmatch */
495     if (str_start(sender+pos,"-return-"))
496       pos += 8;
497       pos += scan_ulong(sender+pos,&innum);
498       if (!flagarchived && innum && sender[pos] == '-')
499         outnum = innum;
500   }
501   szmsgnum[fmt_ulong(szmsgnum,outnum)] = '\0';
502   set_cpnum(szmsgnum);                          /* for copy */
503
504   if (flagarchived) {
505     if (!stralloc_copys(&fnadir,"archive/")) die_nomem();
506     if (!stralloc_catb(&fnadir,strnum,
507                 fmt_ulong(strnum,outnum / 100))) die_nomem();
508     if (!stralloc_copy(&fnaf,&fnadir)) die_nomem();
509     if (!stralloc_cats(&fnaf,"/")) die_nomem();
510     if (!stralloc_catb(&fnaf,strnum,fmt_uint0(strnum,
511                 (unsigned int) (outnum % 100),2))) die_nomem();
512     if (!stralloc_0(&fnadir)) die_nomem();
513     if (!stralloc_0(&fnaf)) die_nomem();
514
515     if (mkdir(fnadir.s,0755) == -1)
516       if (errno != error_exist)
517         strerr_die4sys(111,FATAL,ERR_CREATE,fnadir.s,": ");
518     fdarchive = open_trunc(fnaf.s);
519     if (fdarchive == -1)
520       strerr_die4sys(111,FATAL,ERR_WRITE,fnaf.s,": ");
521
522     substdio_fdbuf(&ssarchive,write,fdarchive,archivebuf,sizeof(archivebuf));
523                                                 /* return-path to archive */
524     if (!stralloc_copys(&line,"Return-Path: <")) die_nomem();
525     if (sender) {                               /* same as qmail-local */
526       if (!quote2(&qline,sender)) die_nomem();
527       for (i = 0;i < qline.len;++i) if (qline.s[i] == '\n') qline.s[i] = '_';
528       if (!stralloc_cat(&line,&qline)) die_nomem();
529     }
530     if (!stralloc_cats(&line,">\n")) die_nomem();
531     if (substdio_put(&ssarchive,line.s,line.len) == -1) die_archive();
532   }
533
534   if (flagqmqp) {
535     if (qmail_open(&qq,&qmqpservers) == -1)             /* open qmqp */
536       strerr_die2sys(111,FATAL,ERR_QMAIL_QUEUE);
537   } else if (qmail_open(&qq,(stralloc *) 0) == -1)      /* open queue */
538       strerr_die2sys(111,FATAL,ERR_QMAIL_QUEUE);
539
540   if (!flagsublist) {
541     getconf_line(&mailinglist,"mailinglist",1,FATAL,dir);
542     qa_puts("Mailing-List: ");
543     qa_put(mailinglist.s,mailinglist.len);
544     if (getconf_line(&line,"listid",0,FATAL,dir)) {
545       flaglistid = 1;
546       qmail_puts(&qq,"\nList-ID: ");
547       qmail_put(&qq,line.s,line.len);
548     }
549     qa_puts("\n");
550   }
551   copy(&qq,"headeradd",'H',FATAL);
552   qa_put(mydtline.s,mydtline.len);
553
554   flagmlwasthere = 0;
555   flaginheader = 1;
556   flagfoundokpart = 1;
557   flagbadfield = 0;
558   flagbadpart = 0;
559   flagseenext = 0;
560   flagsubline = 0;
561   flagfromline = 0;
562   flagreceived = 0;
563   for (;;) {
564     if (getln(&ss0,&line,&match,'\n') == -1)
565       strerr_die2sys(111,FATAL,ERR_READ_INPUT);
566     if (flaginheader && match) {
567       if (line.len == 1) {              /* end of header */
568         flaginheader = 0;
569         if (flagindexed)                /* std entry */
570           r = idx_copy_insertsubject(); /* all indexed lists */
571         if (flagprefixed && !flagsublist) {
572           qa_puts("Subject:");
573           if (!flagindexed) {           /* non-indexed prefixed lists */
574             concatHDR(subject.s,subject.len,&lines,FATAL);
575             decodeHDR(lines.s,lines.len,&qline,charset.s,FATAL);
576             r = unfoldHDR(qline.s,qline.len,&lines,
577                         charset.s,&dcprefix,1,FATAL);
578           }
579           if (!(r & 2)) {
580             qmail_puts(&qq," ");
581             if (serial == prefix.len)
582               qmail_put(&qq,prefix.s,prefix.len);
583             else {
584               qmail_put(&qq,prefix.s,serial);
585               qmail_puts(&qq,szmsgnum);
586               qmail_put(&qq,prefix.s+serial+1,prefix.len-serial-1);
587             }
588           }
589           qa_put(subject.s,subject.len);
590         }
591                 /* do other stuff to do with post header processing here */
592         if (content.len) {              /* get MIME boundary, if exists */
593           concatHDR(content.s,content.len,&qline,FATAL);
594           if (!stralloc_copy(&content,&qline)) die_nomem();
595
596           if (flagtrailer &&            /* trailer only for some multipart */
597                 case_startb(content.s,content.len,"multipart/"))
598             if (!case_startb(content.s+10,content.len-10,"mixed") &&
599                 !case_startb(content.s+10,content.len-10,"digest") &&
600                 !case_startb(content.s+10,content.len-10,"parallel"))
601               flagtrailer = 0;
602
603            cp = content.s;
604            cpafter = cp + content.len;  /* check after each ';' */
605            while ((cp += byte_chr(cp,cpafter-cp,';')) != cpafter) {
606              ++cp;
607              while (cp < cpafter &&
608                         (*cp == ' ' || *cp == '\t' || *cp == '\n')) ++cp;
609              if (case_startb(cp,cpafter-cp,"boundary=")) {
610                cp += 9;                 /* after boundary= */
611                if (*cp == '"') {        /* quoted boundary */
612                  ++cp;
613                  cpstart = cp;
614                  while (cp < cpafter && *cp != '"') ++cp;
615                  if (cp == cpafter)
616                         strerr_die1x(100,ERR_MIME_QUOTE);
617                } else {                 /* non-quoted boundary */
618                  cpstart = cp;          /* find terminator of boundary */
619                  while (cp < cpafter && *cp != ';' &&
620                         *cp != ' ' && *cp != '\t' && *cp != '\n') ++cp;
621                }
622                if (!stralloc_copys(&boundary,"--")) die_nomem();
623                if (!stralloc_catb(&boundary,cpstart,cp-cpstart))
624                         die_nomem();
625                  flagfoundokpart = 0;
626                if (!constmap_init(&mimeremovemap,mimeremove.s,mimeremove.len,0))
627                         die_nomem();
628                flagbadpart = 1;         /* skip before first boundary */
629                qa_puts("\n");           /* to make up for the lost '\n' */
630             }
631           }
632         }
633       } else if ((*line.s != ' ') && (*line.s != '\t')) {
634         flagsubline = 0;
635         flagfromline = 0;
636         flagbadfield = 0;
637         flagarchiveonly = 0;
638         flagcontline = 0;
639         if (constmap(&headerremovemap,line.s,byte_chr(line.s,line.len,':')))
640           flagbadfield = 1;
641         if ((flagnoreceived || !flagreceived) &&
642                 case_startb(line.s,line.len,"Received:")) {
643             if (!flagreceived) {                /* get date from first rec'd */
644               flagreceived = 1;                 /* line (done by qmail) */
645               pos = byte_chr(line.s,line.len,';');
646               if (pos != line.len)              /* has '\n' */
647                 if (!stralloc_copyb(&received,line.s+pos+2,line.len - pos - 3))
648                   die_nomem();
649             } else {                            /* suppress, but archive */
650               flagarchiveonly = 1;              /* but do not suppress the */
651               flagbadfield = 1;                 /* top one added by qmail */
652             }
653         } else if (case_startb(line.s,line.len,"Mailing-List:"))
654           flagmlwasthere = 1;           /* sublists always ok ezmlm masters */
655         else if (mlheader && case_startb(line.s,line.len,mlheader))
656           flagmlwasthere = 1;           /* mlheader treated as ML */
657         else if ((mimeremove.len || flagtrailer) &&     /* else no MIME need*/
658                 case_startb(line.s,line.len,"Content-Type:")) {
659           if (!stralloc_copyb(&content,line.s+13,line.len-13)) die_nomem();
660           flagcontline = 1;
661         } else if (case_startb(line.s,line.len,"Subject:")) {
662           if (!stralloc_copyb(&subject,line.s+8,line.len-8)) die_nomem();
663           flagsubline = 1;
664           if (flagprefixed && !flagsublist)     /* don't prefix for sublists */
665             flagbadfield = 1;                   /* we'll print our own */
666         } else if (flagtrailer &&
667                  case_startb(line.s,line.len,"Content-Transfer-Encoding:")) {
668           cp = line.s + 26;
669           cpafter = cp + line.len;
670           while (cp < cpafter && (*cp == ' ' || *cp == '\t')) ++cp;
671           if (case_startb(cp,cpafter-cp,"base64")) encin = 'B';
672           else if (case_startb(cp,cpafter-cp,"Quoted-Printable")) encin = 'Q';
673         } else if (flaglistid && case_startb(line.s,line.len,"list-id:"))
674           flagbadfield = 1;             /* suppress if we added our own */
675         else if (flagindexed) {
676
677           if (case_startb(line.s,line.len,"From:")) {
678             flagfromline = 1;
679             if (!stralloc_copyb(&from,line.s+5,line.len-5)) die_nomem();
680           }
681         } else if (line.len == mydtline.len)
682           if (!byte_diff(line.s,line.len,mydtline.s))
683             strerr_die2x(100,FATAL,ERR_LOOPING);
684       } else {                  /* continuation lines */
685         if (flagsubline) {
686           if (!stralloc_cat(&subject,&line)) die_nomem();
687         } else if (flagfromline) {
688           if (!stralloc_cat(&from,&line)) die_nomem();
689         } else if (flagcontline) {
690           if (!stralloc_cat(&content,&line)) die_nomem();
691         }
692       }
693     } else                              /* body */
694       msgsize += line.len;              /* always for tstdig support */
695
696     if (!(flaginheader && flagbadfield)) {
697       if (boundary.len && line.len > boundary.len &&
698                 !str_diffn(line.s,boundary.s,boundary.len)) {
699         if (line.s[boundary.len] == '-' && line.s[boundary.len+1] == '-') {
700           flagbadpart = 0;              /* end boundary should be output */
701           if (flagtrailer) {
702             qmail_puts(&qq,"\n");
703             qmail_put(&qq,boundary.s,boundary.len);
704             qmail_puts(&qq,"\nContent-Type: text/plain; charset=");
705             qmail_puts(&qq,charset.s);
706             transferenc();              /* trailer for multipart message */
707             copy(&qq,"text/trailer",flagcd,FATAL);
708             if (flagcd == 'B')  {       /* need to do our own flushing */
709               encodeB("",0,&qline,2,FATAL);
710               qmail_put(&qq,qline.s,qline.len);
711             }
712           }
713         } else {                        /* new part */
714             flagbadpart = 1;            /* skip lines */
715             if (!stralloc_copy(&lines,&line)) die_nomem();      /* but save */
716             flagseenext = 1;            /* need to check Cont-type */
717         }
718       } else if (flagseenext) {         /* last was boundary, now stored */
719         if (case_startb(line.s,line.len,"content-type:")) {
720           flagseenext = 0;              /* done thinking about it */
721           cp = line.s + 13;                     /* start of type */
722           while (*cp == ' ' || *cp == '\t') ++cp;
723           cpstart = cp;                 /* end of type */
724           while (*cp != '\n' && *cp != '\t' && *cp != ' ' && *cp != ';') ++cp;
725           if (constmap(&mimeremovemap,cpstart,cp-cpstart)) {
726             flagbadpart = 1;
727           } else {
728             flagfoundokpart = 1;
729             qa_put(lines.s,lines.len);  /* saved lines */
730             flagbadpart = 0;            /* do this part */
731           }
732         } else if (line.len == 1) {     /* end of content desc */
733           flagbadpart = 0;              /* default type, so ok */
734           flagseenext = 0;              /* done thinking about it */
735         } else                          /* save line in cont desc */
736           if (!stralloc_cat(&lines,&line)) die_nomem();
737       }
738       if (!flagbadpart)
739         qa_put(line.s,line.len);
740
741     } else if (flagarchiveonly && flagarchived) /* received headers */
742       if (substdio_put(&ssarchive,line.s,line.len) == -1) die_archive();
743     if (!match)
744       break;
745   }
746   if (!boundary.len && flagtrailer) {
747     qmail_puts(&qq,"\n");               /* trailer for non-multipart message */
748     if (!encin || encin == 'Q') {       /* can add for QP, but not for base64 */
749       copy(&qq,"text/trailer",encin,FATAL);
750       qmail_puts(&qq,"\n");             /* no need to flush for plain/QP */
751     }
752   }
753
754   cumsize += (msgsize + 128L) >> 8;     /* round to 256 byte 'records' */
755                                         /* check message tag */
756   if (flagsublist) {                    /* sublists need tag if selected/suppt*/
757     if (flaglog)
758       if ((ret = checktag(dir,innum,hash_lo+1L,"m",(char *) 0,hashout))) {
759         if (*ret) strerr_die2x(111,FATAL,ret);
760         else strerr_die2x(100,FATAL,ERR_NOT_PARENT);
761       }
762     if (!flagmlwasthere)                /* sublists need ML header */
763       strerr_die2x(100,FATAL,ERR_SUBLIST);
764   } else                                /* others are not allowed to have one */
765     if (flagmlwasthere)
766       strerr_die2x(100,FATAL,ERR_MAILING_LIST);
767   if (!flagfoundokpart)                 /* all parts were on the strip list */
768       strerr_die2x(100,FATAL,ERR_BAD_ALL);
769
770   if (flagarchived) {
771     if (substdio_flush(&ssarchive) == -1) die_archive();
772     if (fsync(fdarchive) == -1) die_archive();
773     if (fchmod(fdarchive,MODE_ARCHIVE | 0700) == -1) die_archive();
774     if (close(fdarchive) == -1) die_archive(); /* NFS stupidity */
775   }
776
777   if (flaglog) {
778     tagmsg(dir,innum,sender,"m",hashout,qq.msgbytes,53L,FATAL);
779     hashout[COOKIE] = '\0';
780   }
781
782   numwrite();
783   if (!stralloc_copy(&line,&outlocal)) die_nomem();
784   if (!stralloc_cats(&line,"-return-")) die_nomem();
785   if (!stralloc_cats(&line,szmsgnum)) die_nomem();
786   if (!stralloc_cats(&line,"-@")) die_nomem();
787   if (!stralloc_cat(&line,&outhost)) die_nomem();
788   if (!stralloc_cats(&line,"-@[]")) die_nomem();
789   if (!stralloc_0(&line)) die_nomem();
790   qmail_from(&qq,line.s);                       /* envelope sender */
791   subs = putsubs(dir,hash_lo,hash_hi,subto,1,FATAL);    /* subscribers */
792   if (flagsublist) hash_lo++;
793
794   if (*(err = qmail_close(&qq)) == '\0') {
795       if (flaglog)                              /* mysql logging */
796         (void) logmsg(dir,outnum,hash_lo,subs,flagsublist ? 3 : 4);
797       closesql();
798       strnum[fmt_ulong(strnum,qmail_qp(&qq))] = 0;
799       strerr_die2x(0,"ezmlm-send: info: qp ",strnum);
800   } else {
801       --msgnum;
802       cumsize -= (msgsize + 128L) >> 8;
803       numwrite();
804       strerr_die3x(111,FATAL,ERR_TMP_QMAIL_QUEUE,err + 1);
805   }
806 }