chiark / gitweb /
Debianization and various other fixes.
[ezmlm] / ezmlm-get.c
1 /*$Id: ezmlm-get.c,v 1.113 1999/11/22 01:47:45 lindberg Exp $*/
2 /*$Name: ezmlm-idx-040 $*/
3
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include "alloc.h"
7 #include "error.h"
8 #include "stralloc.h"
9 #include "str.h"
10 #include "env.h"
11 #include "sig.h"
12 #include "slurp.h"
13 #include "getconf.h"
14 #include "strerr.h"
15 #include "byte.h"
16 #include "getln.h"
17 #include "case.h"
18 #include "qmail.h"
19 #include "substdio.h"
20 #include "readwrite.h"
21 #include "seek.h"
22 #include "quote.h"
23 #include "datetime.h"
24 #include "now.h"
25 #include "date822fmt.h"
26 #include "fmt.h"
27 #include "sgetopt.h"
28 #include "cookie.h"
29 #include "makehash.h"
30 #include "copy.h"
31 #include "constmap.h"
32 #include "subscribe.h"
33 #include "idxthread.h"
34 #include "idx.h"
35 #include "mime.h"
36 #include "errtxt.h"
37
38 int flagdo = 1;                 /* React to commands (doesn't affect -dig)*/
39 int flagbottom = 1;             /* copy text/bottom + request */
40 int flagpublic = 2;             /* 0 = non-public, 1 = public, 2 = respect*/
41                                 /* dir/public. */
42 char flagcd = '\0';             /* default: don't use quoted-printable */
43 int flagsub = 0;                /* =1 subscribers only for get/index/thread */
44 char *digsz =
45                 "from\\to\\subject\\reply-to\\date\\message-id\\cc\\"
46                 "mime-version\\content-type\\content-transfer-encoding";
47
48 #define FATAL "ezmlm-get: fatal: "
49
50 void die_usage() {
51   strerr_die1x(100,
52     "ezmlm-get: usage: "
53         "ezmlm-get [-bBcClLpPsSvV] [-f fmt] [digestcode]");
54 }
55
56 void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); }
57
58 void die_badaddr()
59 {
60   strerr_die2x(100,FATAL,ERR_BAD_ADDRESS);
61 }
62
63 stralloc inhost = {0};
64 stralloc outhost = {0};
65 stralloc inlocal = {0};
66 stralloc outlocal = {0};
67 stralloc listname = {0};
68 stralloc mailinglist = {0};
69 stralloc qmqpservers = {0};
70 stralloc fn = {0};
71 stralloc moddir = {0};
72 stralloc charset = {0};
73 stralloc mydtline = {0};
74 stralloc digheaders = {0};
75 stralloc seed = {0};
76 struct constmap digheadersmap;
77
78 char schar[] = "00_";
79 stralloc listno = {0};
80 void *psql = (void *) 0;
81
82 datetime_sec when;
83 struct datetime dt;
84 unsigned long cumsize = 0L;     /* cumulative msgs / 256 */
85 unsigned long cumsizen = 0L;    /* new cumulative msgs / 256 */
86 unsigned long max = 0L;         /* Last message in archive */
87 unsigned long msgsize = 0L;     /* for digest accounting */
88 datetime_sec digwhen;           /* last digest */
89
90 char strnum[FMT_ULONG];
91 char szmsgnum[FMT_ULONG];
92 char date[DATE822FMT];
93 char boundary[COOKIE];
94 char hashout[COOKIE];
95 stralloc line = {0};
96 stralloc line2 = {0};
97 stralloc qline = {0};
98 stralloc quoted = {0};
99 stralloc msgnum = {0};
100 stralloc num = {0};
101 stralloc subject = {0};
102
103 /* for copy archive */
104 stralloc archdate = {0};
105 stralloc archfrom = {0};
106 stralloc archto = {0};
107 stralloc archcc = {0};
108 stralloc archsubject = {0};
109 stralloc archmessageid = {0};
110 stralloc archkeywords = {0};
111 stralloc archblanklines = {0};
112 char archtype=' ';
113
114 /* for mods on non-public lists (needed for future fuzzy sub dbs) */
115 stralloc mod = {0};             /* moderator addr for non-public lists */
116 char *pmod = (char *) 0;        /* pointer to above */
117
118 /* for digest */
119 stralloc ddir = {0};
120 stralloc edir = {0};
121
122 int act = AC_NONE;              /* Action we do */
123 int flageditor = 0;             /* if we're invoked for within dir/editor */
124 struct stat st;
125
126 int flaglocked = 0;             /* if directory is locked */
127 int flagarchived;               /* if list is archived */
128 int flagindexed;                /* if list is indexed */
129 int flagq = 0;                  /* don't use 'quoted-printable' */
130
131 char *dir;
132 char *workdir;
133 char *sender;
134 char *digestcode;
135
136 struct qmail qq;
137 int qqwrite(fd,buf,len) int fd; char *buf; unsigned int len;
138 {
139   qmail_put(&qq,buf,len);
140   return len;
141 }
142
143 int subto(s,l)
144 char *s;
145 unsigned int l;
146 {
147   qmail_put(&qq,"T",1);
148   qmail_put(&qq,s,l);
149   qmail_put(&qq,"",1);
150   return (int) l;
151 }
152
153 char qqbuf[1];
154 substdio ssqq = SUBSTDIO_FDBUF(qqwrite,-1,qqbuf,sizeof(qqbuf));
155
156 char inbuf[1024];
157 substdio ssin = SUBSTDIO_FDBUF(read,0,inbuf,sizeof(inbuf));
158 substdio ssin2 = SUBSTDIO_FDBUF(read,0,inbuf,sizeof(inbuf));
159
160 substdio ssnum;
161 char numbuf[16];
162
163 substdio sstext;
164 char textbuf[1024];
165
166 substdio ssindex;
167 char indexbuf[1024];
168
169 int fdlock;
170
171 void lockup()
172 /* lock unless locked */
173 {
174   if(!flaglocked) {
175     fdlock = open_append("lock");
176     if (fdlock == -1)
177       strerr_die2sys(111,FATAL,ERR_OPEN_LOCK);
178     if (lock_ex(fdlock) == -1) {
179       close(fdlock);
180       strerr_die2sys(111,FATAL,ERR_OBTAIN_LOCK);
181     }
182     flaglocked = 1;
183   }
184 }
185
186 void unlock()
187 /* unlock if locked */
188 {
189   if (flaglocked) {
190     close(fdlock);
191     flaglocked = 0;
192   }
193 }
194
195 void code_qput(s,n)
196 char *s;
197 unsigned int n;
198 {
199     if (!flagcd)
200       qmail_put(&qq,s,n);
201     else {
202       if (flagcd == 'B')
203         encodeB(s,n,&qline,0,FATAL);
204       else
205         encodeQ(s,n,&qline,FATAL);
206       qmail_put(&qq,qline.s,qline.len);
207       msgsize += qline.len;
208     }
209 }
210
211 void transferenc()
212 {
213         if (flagcd) {
214           qmail_puts(&qq,"\nContent-Transfer-Encoding: ");
215           if (flagcd == 'Q')
216             qmail_puts(&qq,"Quoted-printable\n\n");
217           else
218             qmail_puts(&qq,"base64\n\n");
219         } else
220           qmail_puts(&qq,"\n\n");
221 }
222
223 void zapnonsub(szerr)
224 /* fatal error if flagsub is set and sender is not a subscriber */
225 /* expects the current dir to be the list dir. Error is szerr */
226 /* added check for undefined sender as a precaution */
227 char *szerr;
228 {
229   if (sender && *sender) {      /* "no sender" is not a subscriber */
230     if (!flagsub)
231       return;
232     if (issub(dir,sender,(char *) 0,FATAL))
233       return;           /* subscriber */
234     if (issub(ddir.s,sender,(char *) 0,FATAL))
235       return;           /* digest subscriber */
236     if (issub(edir.s,sender,(char *) 0,FATAL))
237       return;           /* allow addresses */
238   }
239   strerr_die4x(100,FATAL,ERR_SUBSCRIBER_CAN,szerr,ERR_571);
240 }
241
242 void tosender()
243 {
244   qmail_puts(&qq,"To: ");
245   if (!quote2(&quoted,sender)) die_nomem();
246   qmail_put(&qq,quoted.s,quoted.len);
247   qmail_puts(&qq,"\n");
248 }
249
250 void get_num()
251 {
252 /* read dir/num -> max. max/cumsizen left alone if not present */
253 /* Both of these should have been initialized to 0L */
254
255   unsigned int pos;
256   if (getconf_line(&num,"num",0,FATAL,dir)) {
257     if(!stralloc_0(&num)) die_nomem();
258     pos = scan_ulong(num.s,&max);
259     if (num.s[pos] == ':') pos++;
260     scan_ulong(num.s+pos,&cumsizen);
261   }
262 }
263
264 unsigned long dignum()
265 {
266 /* return dignum if exists, 0 otherwise. */
267
268   unsigned long retval;
269   if (!stralloc_copys(&num,"")) die_nomem();    /* zap */
270   getconf_line(&num,"dignum",0,FATAL,dir);
271   if(!stralloc_0(&num)) die_nomem();
272   scan_ulong(num.s,&retval);
273   return retval;
274 }
275
276 void write_ulong(num,cum,dat,fn,fnn)
277 /* write num to "fnn" add ':' & cum if cum <>0, then move "fnn" to "fn" */
278 char *fn, *fnn;
279 unsigned long num,cum,dat;
280 {
281   int fd;
282
283   fd = open_trunc(fnn);
284   if (fd == -1)
285      strerr_die6sys(111,FATAL,ERR_CREATE,dir,"/",fnn,": ");
286   substdio_fdbuf(&ssnum,write,fd,numbuf,sizeof(numbuf));
287   if (substdio_put(&ssnum,strnum,fmt_ulong(strnum,num)) == -1)
288      strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn,": ");
289   if (substdio_puts(&ssnum,":") == -1)
290      strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn,": ");
291   if (substdio_put(&ssnum,strnum,fmt_ulong(strnum,cum)) == -1)
292      strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn,": ");
293   if (dat) {
294     if (substdio_puts(&ssnum,":") == -1)
295        strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn,": ");
296     if (substdio_put(&ssnum,strnum,fmt_ulong(strnum,dat)) == -1)
297        strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn,": ");
298   }
299   if (substdio_puts(&ssnum,"\n") == -1)
300      strerr_die6sys(111,FATAL,ERR_WRITE,dir,"/",fnn,": ");
301   if (substdio_flush(&ssnum) == -1)
302      strerr_die6sys(111,FATAL,ERR_FLUSH,dir,"/",fnn,": ");
303   if (fsync(fd) == -1)
304      strerr_die6sys(111,FATAL,ERR_SYNC,dir,"/",fnn,": ");
305   if (close(fd) == -1)
306      strerr_die6sys(111,FATAL,ERR_CLOSE,dir,"/",fnn,": ");
307   if (rename(fnn,fn) == -1)
308      strerr_die4sys(111,FATAL,ERR_MOVE,fnn,": ");
309 }
310
311 void normal_bottom(format)
312 char format;
313 /* Copies bottom text and the original message to the new message */
314 {
315   if (flagbottom) {
316     copy(&qq,"text/bottom",flagcd,FATAL);
317     if (flagcd && format != RFC1153) {
318       if (flagcd == 'B') {
319         encodeB("",0,&line,2,FATAL);    /* flush */
320         qmail_put(&qq,line.s,line.len);
321       }
322       qmail_puts(&qq,"\n--");
323       qmail_put(&qq,boundary,COOKIE);
324       qmail_puts(&qq,"\nContent-Type: message/rfc822");
325       qmail_puts(&qq,"\nContent-Disposition: inline; filename=request.msg\n\n");
326     }
327     qmail_puts(&qq,"Return-Path: <");
328     if (!quote2(&quoted,sender)) die_nomem();
329     qmail_put(&qq,quoted.s,quoted.len);
330     qmail_puts(&qq,">\n");
331     if (seek_begin(0) == -1)
332       strerr_die2sys(111,FATAL,ERR_SEEK_INPUT);
333     if (substdio_copy(&ssqq,&ssin2) != 0)
334       strerr_die2sys(111,FATAL,ERR_READ_INPUT);
335   } else {
336     if (flagcd == 'B' && format != RFC1153) {
337       encodeB("",0,&line,2,FATAL);      /* flush */
338       qmail_put(&qq,line.s,line.len);
339     }
340   }
341 }
342
343 void presub(from,to,subject,factype,format)
344 /* Starts within header, outputs "subject" and optional headers, terminates*/
345 /* header and handles output before table-of-contents                      */
346 unsigned long from,to;
347 stralloc *subject;
348 int factype;            /* action type (AC_THREAD, AC_GET, AC_DIGEST) */
349 char format;            /* output format type (see idx.h) */
350 {
351   qmail_puts(&qq,"MIME-Version: 1.0\n");
352   switch(format) {
353     case MIME:
354     case VIRGIN:
355     case NATIVE:
356     case MIXED:
357         qmail_puts(&qq,"Content-Type: multipart/");
358         if (format == MIXED)
359           qmail_puts(&qq,"mixed");
360         else
361           qmail_puts(&qq,"digest");
362         qmail_puts(&qq,"; boundary=");
363         qmail_put(&qq,boundary,COOKIE);
364         qmail_puts(&qq,"\nSubject: ");
365         qmail_put(&qq,subject->s,subject->len);
366         qmail_puts(&qq,"\n\n\n--");
367         qmail_put(&qq,boundary,COOKIE);
368         qmail_puts(&qq,"\nContent-Type: text/plain; charset=");
369         qmail_puts(&qq,charset.s);
370         transferenc();  /* content-transfer-enc header if needed */
371         qmail_puts(&qq,"\n");
372         break;
373     case RFC1153:
374         qmail_puts(&qq,"Content-type: text/plain; charset=");
375         qmail_puts(&qq,charset.s);
376         qmail_puts(&qq,"\nSubject: ");
377         qmail_put(&qq,subject->s,subject->len);
378         qmail_puts(&qq,"\n\n");
379         flagcd = '\0';  /* We make 8-bit messages, not QP/bas64 for rfc1153 */
380         break;          /* Since messages themselves aren't encoded */
381     }
382     if (!stralloc_cats(subject,"\n\n")) die_nomem();
383     code_qput(subject->s,subject->len);
384     if (format != NATIVE && factype != AC_THREAD && factype != AC_INDEX) {
385       if (!stralloc_copys(&line,TXT_TOP_TOPICS)) die_nomem();
386       if (!stralloc_cats(&line,TXT_TOP_MESSAGES)) die_nomem();
387       if (!stralloc_catb(&line,strnum,fmt_ulong(strnum,from))) die_nomem();
388       if (!stralloc_cats(&line,TXT_TOP_THROUGH)) die_nomem();
389       if (!stralloc_catb(&line,strnum,fmt_ulong(strnum,to))) die_nomem();
390       if (!stralloc_cats(&line,TXT_TOP_LAST)) die_nomem();
391       code_qput(line.s,line.len);
392     }
393 }
394
395 void postsub(factype,format)
396 /* output after TOC and before first message. */
397 int factype;            /* action type (AC_THREAD, AC_GET, AC_DIGEST) */
398 char format;            /* output format type (see idx.h) */
399 {
400     code_qput(TXT_ADMINISTRIVIA,str_len(TXT_ADMINISTRIVIA));
401     if(factype == AC_DIGEST) {
402       copy(&qq,"text/digest",flagcd,FATAL);
403       if (flagcd == 'B') {
404         encodeB("",0,&line,2,FATAL);    /* flush */
405         qmail_put(&qq,line.s,line.len);
406       }
407      } else
408       normal_bottom(format);
409     if (!flagcd || format == RFC1153)
410       qmail_puts(&qq,"\n----------------------------------------------------------------------\n");
411     else
412       qmail_puts(&qq,"\n");
413 }
414
415 void postmsg(format)
416 char format;
417 {
418     switch(format) {
419         case MIME:
420         case VIRGIN:
421         case NATIVE:
422         case MIXED:
423                 qmail_puts(&qq,"\n--");
424                 qmail_put(&qq,boundary,COOKIE);         /* digest boundary */
425                 qmail_puts(&qq,"--\n");
426                 break;
427         case RFC1153:
428                 qmail_puts(&qq,"End of ");
429                 qmail_put(&qq,listname.s,listname.len);
430                 qmail_puts(&qq," Digest");
431                 qmail_puts(&qq,"\n***********************************\n");
432                 break;
433     }
434 }
435
436 void copymsg(msg,fd,format)
437 /* Copy archive message "msg" itself from open file handle fd, in "format" */
438 unsigned long msg; int fd; char format;
439 {
440   int match;
441   int flaginheader;
442   int flagskipblanks;
443   int flaggoodfield;
444
445   switch(format) {
446     case VIRGIN:
447     case NATIVE:
448       substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf));
449       for (;;) {
450         if (getln(&sstext,&line,&match,'\n') == -1)
451            strerr_die4sys(111,FATAL,ERR_READ,line.s,": ");
452         if (match) {
453            qmail_put(&qq,line.s,line.len);
454            msgsize += line.len;
455         } else
456            break;
457       }
458       break;
459     case MIME:
460     case MIXED:
461       flaginheader = 1;
462       flaggoodfield = 0;
463       substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf));
464       for (;;) {
465         if (getln(&sstext,&line,&match,'\n') == -1)
466            strerr_die4sys(111,FATAL,ERR_READ,line.s,": ");
467         if (match) {
468           if (flaginheader) {
469             if (line.len == 1) {
470               flaginheader = 0;
471               flaggoodfield = 1;
472             } else if (line.s[0] != ' ' && line.s[0] != '\t') {
473               flaggoodfield = 0;
474               if (constmap(&digheadersmap,line.s,
475                         byte_chr(line.s,line.len,':')))
476                 flaggoodfield = 1;
477             }
478             if (flaggoodfield) {
479               qmail_put(&qq,line.s,line.len);           /* header */
480               msgsize += line.len;
481             }
482           } else {
483             qmail_put(&qq,line.s,line.len);             /* body */
484             msgsize += line.len;
485           }
486         } else
487           break;
488       }
489       break;
490     case RFC1153:               /* Not worth optimizing. Rarely used */
491       flaginheader = 1;
492       flagskipblanks = 1;       /* must skip terminal blanks acc to rfc1153 */
493       archtype = ' ';           /* rfc1153 requires ordered headers */
494       if (!stralloc_copys(&archblanklines,"")) die_nomem();
495       substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf));
496       for (;;) {
497         if (getln(&sstext,&line,&match,'\n') == -1)
498            strerr_die4sys(111,FATAL,ERR_READ,line.s,": ");
499         if (match) {
500           if (flaginheader) {
501             if (line.len == 1) {
502               flaginheader = 0;
503               if (archdate.len) {
504                 qmail_put(&qq,archdate.s,archdate.len);
505                 archdate.len = 0;
506                 msgsize += archdate.len;
507               }
508               if (archto.len) {
509                 qmail_put(&qq,archto.s,archto.len);
510                 msgsize += archto.len;
511                 archto.len = 0;
512               }
513               if (archfrom.len) {
514                 qmail_put(&qq,archfrom.s,archfrom.len);
515                 msgsize += archfrom.len;
516                 archfrom.len = 0;
517               }
518               if (archcc.len) {
519                 qmail_put(&qq,archcc.s,archcc.len);
520                 msgsize += archcc.len;
521                 archcc.len = 0;
522               }
523               if (archsubject.len) {
524                 qmail_put(&qq,archsubject.s,archsubject.len);
525                 msgsize += archsubject.len;
526                 archsubject.len = 0;
527               }
528               if (archmessageid.len) {
529                 qmail_put(&qq,archmessageid.s,archmessageid.len);
530                 msgsize += archmessageid.len;
531                 archmessageid.len = 0;
532               }
533               if (archkeywords.len) {
534                 qmail_put(&qq,archkeywords.s,archkeywords.len);
535                 msgsize += archkeywords.len;
536                 archkeywords.len = 0;
537               }
538               qmail_puts(&qq,"\n");
539             } else if (line.s[0] == ' ' || line.s[0] == '\t') {
540               switch (archtype) {       /* continuation lines */
541                 case ' ':
542                   break;
543                 case 'D':
544                   if (!stralloc_cat(&archdate,&line)) die_nomem(); break;
545                 case 'F':
546                   if (!stralloc_cat(&archfrom,&line)) die_nomem(); break;
547                 case 'T':
548                   if (!stralloc_cat(&archto,&line)) die_nomem(); break;
549                 case 'C':
550                   if (!stralloc_cat(&archcc,&line)) die_nomem(); break;
551                 case 'S':
552                   if (!stralloc_cat(&archsubject,&line)) die_nomem(); break;
553                 case 'M':
554                   if (!stralloc_cat(&archmessageid,&line)) die_nomem(); break;
555                 case 'K':
556                   if (!stralloc_cat(&archkeywords,&line)) die_nomem(); break;
557                 default:
558                   strerr_die2x(111,FATAL,
559                       "Program error: Bad archive header type");
560               }
561             } else {
562               archtype = ' ';
563               if (case_startb(line.s,line.len,"cc:")) {
564                 archtype='C';
565                 if (!stralloc_copy(&archcc,&line)) die_nomem();
566               }
567               else if (case_startb(line.s,line.len,"date:")) {
568                 archtype='D';
569                 if (!stralloc_copy(&archdate,&line)) die_nomem();
570               }
571               else if (case_startb(line.s,line.len,"from:")) {
572                 archtype='F';
573                 if (!stralloc_copy(&archfrom,&line)) die_nomem();
574               }
575               else if (case_startb(line.s,line.len,"keywords:")) {
576                 archtype='K';
577                 if (!stralloc_copy(&archkeywords,&line)) die_nomem();
578               }
579               else if (case_startb(line.s,line.len,"message-id:")) {
580                 archtype='M';
581                 if (!stralloc_copy(&archmessageid,&line)) die_nomem();
582               }
583               else if (case_startb(line.s,line.len,"subject:")) {
584                 archtype='S';
585                 if (!stralloc_copy(&archsubject,&line)) die_nomem();
586               }
587               else if (case_startb(line.s,line.len,"to:")) {
588                 archtype='T';
589                 if (!stralloc_copy(&archto,&line)) die_nomem();
590               }
591             }
592           } else if (line.len == 1) {
593             if (!flagskipblanks)
594               if (!stralloc_copys(&archblanklines,"\n")) die_nomem();
595           } else {
596             if (archblanklines.len) {
597               qmail_put(&qq,archblanklines.s,archblanklines.len);
598               archblanklines.len = 0;
599             }
600             flagskipblanks = 0;
601             qmail_put(&qq,line.s,line.len);
602             msgsize += line.len;
603           }
604         } else
605           break;
606       }
607       break;
608     default:
609       strerr_die2x(100,FATAL,"Program error: bad format in copymsg()");
610   }
611 }
612
613 void mime_getbad(msg)
614 /* Message not found as a MIME multipart */
615 unsigned long msg;
616 {
617    qmail_puts(&qq,"\n--");
618    qmail_put(&qq,boundary,COOKIE);
619    qmail_puts(&qq,"\nContent-Type: text/plain; charset=");
620    qmail_puts(&qq,charset.s);
621    qmail_puts(&qq,"\nContent-Disposition: inline; filename=\"");
622    qmail_put(&qq,listname.s,listname.len);
623    qmail_puts(&qq,"_");
624    qmail_put(&qq,strnum,fmt_ulong(strnum,msg));
625    qmail_puts(&qq,".ezm\"\n");
626    transferenc();
627    copy(&qq,"text/get-bad",flagcd,FATAL);
628 }
629
630 void msgout(msg,format)
631 /* Outputs message (everything that's needed per message) */
632 unsigned long msg; char format;
633 {
634   int fd;
635   unsigned int len;
636
637     if (!stralloc_copys(&fn,"archive/")) die_nomem();
638
639     len = fmt_ulong(strnum, msg / 100);
640     if (!stralloc_catb(&fn,strnum,len)) die_nomem();
641     if (!stralloc_cats(&fn,"/")) die_nomem();
642     len = fmt_uint0(strnum, (unsigned int) (msg % 100),2);
643     if (!stralloc_catb(&fn,strnum,len)) die_nomem();
644     if (!stralloc_0(&fn)) die_nomem();
645
646     switch(format) {
647       case MIME:
648       case VIRGIN:
649       case NATIVE:
650       case MIXED:
651         fd = open_read(fn.s);
652         if (fd == -1) {
653           if (errno != error_noent)
654             strerr_die4sys(111,FATAL,ERR_OPEN,fn.s,": ");
655           else
656             mime_getbad(msg);
657         } else if (fstat(fd,&st) == -1 || (!(st.st_mode & 0100))) {
658           close(fd);
659           mime_getbad(msg);
660         } else {
661           qmail_puts(&qq,"\n--");
662           qmail_put(&qq,boundary,COOKIE);
663           qmail_puts(&qq,"\nContent-Type: message/rfc822");
664           qmail_puts(&qq,"\nContent-Disposition: inline; filename=\"");
665           qmail_put(&qq,listname.s,listname.len);
666           qmail_puts(&qq,"_");
667           qmail_put(&qq,strnum,fmt_ulong(strnum,msg));
668           qmail_puts(&qq,".ezm\"\n\n");
669           copymsg(msg,fd,format);
670           close(fd);
671         }
672         break;
673       case RFC1153:
674         fd = open_read(fn.s);
675         if (fd == -1) {
676           if (errno != error_noent)
677             strerr_die4sys(111,FATAL,ERR_OPEN,fn.s,": ");
678           else {
679             qmail_puts(&qq,"\n== ");
680             qmail_put(&qq,strnum,fmt_ulong(strnum,msg));
681             qmail_puts(&qq," ==\n\n");
682             copy(&qq,"text/get-bad",flagcd,FATAL);
683           }
684         } else {
685           if (fstat(fd,&st) == -1 || (!(st.st_mode & 0100))) {
686             close(fd);
687             qmail_puts(&qq,"\n== ");
688             qmail_put(&qq,strnum,fmt_ulong(strnum,msg));
689             qmail_puts(&qq," ==\n\n");
690             copy(&qq,"text/get-bad",flagcd,FATAL);
691           } else {
692             copymsg(msg,fd,format);
693             close(fd);
694           }
695         }
696         qmail_puts(&qq,"\n------------------------------\n\n");
697         break;
698       default:
699         strerr_die2x(100,FATAL,"Program error: Unrecognized format in msgout");
700         break;
701     }
702 }
703
704 void digest(msgtable,subtable,authtable,from,to,subj,factype,format)
705 /* Output digest range from-to as per msgtable/subtable (from mkthread(s)). */
706 /* "Subject is the subject of the _entire_ digest/set. */
707 msgentry *msgtable; subentry *subtable; authentry *authtable;
708 unsigned long from,to; stralloc *subj; int factype; char format;
709 {
710   msgentry *pmsgt;
711   subentry *psubt;
712   char *cp;
713   int ffirstmsg;
714   unsigned int len;
715   unsigned long msg;
716   unsigned long subnum;
717
718   psubt = subtable;
719   presub(from,to,subj,factype,format);
720
721   if (format != NATIVE) {
722     while (psubt->sub) {
723       ffirstmsg = 1;
724                 /* ptr to first message with this subject */
725       pmsgt = msgtable + psubt->firstmsg - from;
726       subnum = (unsigned long) (psubt - subtable +1);
727       for (msg=psubt->firstmsg; msg<=to; msg++) {
728         if (pmsgt->subnum == subnum) {
729           if(ffirstmsg) {
730             ffirstmsg = 0;
731             if (!stralloc_copys(&line,"\n")) die_nomem();
732             if (psubt->sublen <= HASHLEN + 2) {
733               if (!stralloc_cats(&line,"(null)\n")) die_nomem();
734             } else
735               if (!stralloc_catb(&line,psubt->sub + HASHLEN + 1,
736                 psubt->sublen - HASHLEN - 1)) die_nomem();
737           } else
738             if (!stralloc_copys(&line,"")) die_nomem();
739           if (!stralloc_cats(&line,"\t")) die_nomem();
740           if (!stralloc_catb(&line,strnum,fmt_ulong(strnum,msg))) die_nomem();
741           if (!stralloc_cats(&line,TXT_BY)) die_nomem();
742           if (pmsgt->authnum) {
743             cp = authtable[pmsgt->authnum - 1].auth;
744             len = authtable[pmsgt->authnum - 1].authlen;
745             if (len > HASHLEN) {
746               if (!stralloc_catb(&line,cp + HASHLEN + 1,
747                 len - HASHLEN - 1)) die_nomem();
748             } else
749               if (!stralloc_catb(&line,cp,len)) die_nomem();
750           } else
751             if (!stralloc_cats(&line,"\n")) die_nomem();
752           code_qput(line.s,line.len);
753         }
754         pmsgt++;
755       }
756       psubt++;
757     }
758   }
759   postsub(factype,format);
760
761   psubt = subtable;
762   while (psubt->sub) {
763     pmsgt = msgtable + psubt->firstmsg - from;
764     subnum = (unsigned long) (psubt - subtable +1);
765     for (msg=psubt->firstmsg; msg<=to; msg++) {
766       if (pmsgt->subnum == subnum)
767         msgout(msg,format);
768       pmsgt++;
769     }
770     psubt++;
771   }
772   postmsg(format);
773   idx_destroythread(msgtable,subtable,authtable);
774 }
775
776 void doheaders()
777 {
778   int flaggoodfield,match;
779
780   if (act == AC_DIGEST)
781     copy(&qq,"headeradd",'H',FATAL);
782
783   qmail_puts(&qq,"Mailing-List: ");
784   qmail_put(&qq,mailinglist.s,mailinglist.len);
785   if (getconf_line(&line,"listid",0,FATAL,dir)) {
786     qmail_puts(&qq,"\nList-ID: ");
787     qmail_put(&qq,line.s,line.len);
788   }
789   qmail_puts(&qq,"\nDate: ");
790   qmail_put(&qq,date,date822fmt(date,&dt));
791   qmail_puts(&qq,"Message-ID: <");
792   if (!stralloc_copyb(&line,strnum,fmt_ulong(strnum,(unsigned long) when)))
793      die_nomem();
794   if (!stralloc_append(&line,".")) die_nomem();
795   if (!stralloc_catb(&line,strnum,
796                 fmt_ulong(strnum,(unsigned long) getpid()))) die_nomem();
797   if (!stralloc_cats(&line,".ezmlm@")) die_nomem();
798   if (!stralloc_cat(&line,&outhost)) die_nomem();
799   if (!stralloc_0(&line)) die_nomem();
800   qmail_puts(&qq,line.s);
801                 /* "unique" MIME boundary as hash of messageid */
802   makehash(line.s,line.len,boundary);
803   qmail_puts(&qq,">\nFrom: ");
804   if (!quote(&quoted,&outlocal)) die_nomem();
805   qmail_put(&qq,quoted.s,quoted.len);
806   qmail_puts(&qq,"-help@");
807   qmail_put(&qq,outhost.s,outhost.len);
808   qmail_puts(&qq,"\n");
809   if (!stralloc_copys(&mydtline,"Delivered-To: responder for ")) die_nomem();
810   if (!stralloc_catb(&mydtline,outlocal.s,outlocal.len)) die_nomem();
811   if (!stralloc_cats(&mydtline,"@")) die_nomem();
812   if (!stralloc_catb(&mydtline,outhost.s,outhost.len)) die_nomem();
813   if (!stralloc_cats(&mydtline,"\n")) die_nomem();
814
815   qmail_put(&qq,mydtline.s,mydtline.len);
816
817   flaggoodfield = 0;
818   if (act != AC_DIGEST)
819     for (;;) {
820     if (getln(&ssin,&line,&match,'\n') == -1)
821       strerr_die2sys(111,FATAL,ERR_READ_INPUT);
822     if (!match) break;
823     if (line.len == 1) break;
824     if ((line.s[0] != ' ') && (line.s[0] != '\t')) {
825       flaggoodfield = 0;
826       if (case_startb(line.s,line.len,"mailing-list:"))
827         if (flageditor)                 /* we may be running from a sublist */
828           flaggoodfield = 0;
829         else
830           strerr_die2x(100,FATAL,ERR_MAILING_LIST);
831       if (line.len == mydtline.len)
832         if (byte_equal(line.s,line.len,mydtline.s))
833           strerr_die2x(100,FATAL,ERR_LOOPING);
834       if (case_startb(line.s,line.len,"delivered-to:"))
835         flaggoodfield = 1;
836       if (case_startb(line.s,line.len,"received:"))
837         flaggoodfield = 1;
838     }
839     if (flaggoodfield)
840       qmail_put(&qq,line.s,line.len);
841   }
842 }
843
844
845 void main(argc,argv)
846 int argc;
847 char **argv;
848 {
849   char *def;
850   char *local;
851   char *action = "";
852   char *psz;
853   char *err;
854   int fd;
855   unsigned int i,j;
856   int flagremote;
857   int flagqmqp = 0;
858   int match;
859   int goodexit = 0;                     /* exit code for normal exit */
860                                         /* in manager this will be set to 0 */
861   unsigned long from,u,to,issue,prevmax,mno;
862   unsigned long chunk;
863   unsigned long subs;
864   unsigned int pos,pos1;
865   unsigned int len;
866   int opt;
867   char outformat = DEFAULT_FORMAT;      /* default output format */
868   msgentry *msgtable;
869   subentry *subtable;
870   authentry *authtable;
871   dateentry *datetable;
872
873   (void) umask(022);
874   sig_pipeignore();
875   when = now();
876   datetime_tai(&dt,when);
877
878   while ((opt = getopt(argc,argv,"bBcCf:pPsSt:vV")) != opteof)
879     switch (opt) {
880       case 'b': flagbottom = 1; break;  /* add text/bottom (default) */
881       case 'B': flagbottom = 0; break;  /* suppress text/bottom */
882       case 'c': flagdo = 1; break;      /* do commands */
883       case 'C': flagdo = 0; break;      /* ignore commands X dig */
884       case 'f': if (FORMATS[str_chr(FORMATS,optarg[0])])
885                    outformat = optarg[0];
886                 break;
887       case 'p': flagpublic = 1; break;  /* always public */
888       case 'P': flagpublic = 0; break;  /* never public = only mods do cmd*/
889                                         /* def = 2: respect DIR/public */
890       case 's': flagsub = 1; break;     /* only subs have archive access */
891       case 'S': flagsub = 0; break;     /* everyone has archive access */
892       case 'v':
893       case 'V': strerr_die2x(0,"ezmlm-get version: ",EZIDX_VERSION);
894       default:
895         die_usage();
896     }
897
898   dir = argv[optind++];
899   if (!dir) die_usage();
900   if (chdir(dir) == -1)
901     strerr_die4x(111,FATAL,ERR_SWITCH,dir,": ");
902
903   digestcode = argv[optind];    /* code to activate digest (-digest-code)*/
904                                 /* ignore any extra args */
905
906   getconf_line(&outlocal,"outlocal",1,FATAL,dir);
907   if (!stralloc_copy(&subject,&outlocal)) die_nomem();  /* for subjects */
908   if (!stralloc_copy(&listname,&outlocal)) die_nomem(); /* for content disp */
909
910   local = env_get("LOCAL");
911   def = env_get("DEFAULT");
912   sender = env_get("SENDER");
913   if (local && *local) {        /* in editor local = inlocal */
914     if (!sender) strerr_die2x(100,FATAL,ERR_NOSENDER);
915     if (!*sender)
916       strerr_die2x(100,FATAL,ERR_BOUNCE);
917     if (str_equal(sender,"#@[]"))
918       strerr_die2x(100,FATAL,ERR_BOUNCE);
919     if (!sender[str_chr(sender,'@')])
920       strerr_die2x(100,FATAL,ERR_ANONYMOUS);
921     if (def) {                  /* qmail>=1.02 support only */
922       if (*def) {
923         action = def;
924         goodexit = 99;
925       } else
926         _exit(0);               /* list-@host should do -help from manager */
927     } else {                    /* editor */
928       act = AC_DIGEST;          /* on list-@host ! */
929       flageditor = 1;           /* to avoid Mailing-list error on sublists */
930                                 /* when running out of dir/editor. */
931     }
932     if (case_starts(action,"dig")) {
933       action += 3;
934       if (action[0] == '-' || action [0] == '.') {
935         action++;
936         if (!digestcode)
937             strerr_die2x(100,FATAL,ERR_BAD_DIGCODE);
938         len = str_len(digestcode);
939         if (len <= str_len(action) && case_startb(action,len,digestcode)) {
940           if (FORMATS[str_chr(FORMATS,*(action+len))])
941             outformat = *(action+len);
942           act = AC_DIGEST;
943         } else
944           strerr_die2x(100,FATAL,ERR_BAD_DIGCODE);
945       }
946     }
947   } else                        /* Command line operation */
948     act = AC_DIGEST;
949
950         /* Things we deal with. If anything else just die with success!   */
951         /* At the moment this is -index, -thread, and -get.               */
952         /* If flagdo = 0 we only service -dig commands. This is to support*/
953         /* "secret" lists that are still archived and digested. -c on     */
954         /* cmd line. */
955
956   if (act == AC_NONE) {
957     if (case_equals(action,ACTION_DIGEST)) {
958       act = AC_GET;             /* list-digest@ => msg since last digest */
959       action = ACTION_GET;
960     } else if (case_starts(action,ACTION_GET) || case_starts(action,ALT_GET))
961       act = AC_GET;
962     else if (case_starts(action,ACTION_INDEX) || case_starts(action,ALT_INDEX))
963       act = AC_INDEX;
964     else if (case_starts(action,ACTION_THREAD) ||
965          case_starts(action,ALT_THREAD))
966       act = AC_THREAD;
967   }
968   if (act == AC_NONE)                   /* not for us. Pass the buck. */
969     _exit(0);
970   if (act != AC_INDEX) {                /* need to do header processing */
971     if(!getconf(&digheaders,"digheaders",0,FATAL,dir)) {
972       if(!stralloc_copys(&digheaders,digsz)) die_nomem();
973       if (!stralloc_0(&digheaders)) die_nomem();
974       psz = digheaders.s;
975       while (*psz) {
976         if (*psz == '\\') *psz = '\0';
977         ++psz;
978       }
979     }
980     if (!constmap_init(&digheadersmap,digheaders.s,digheaders.len,0))
981         die_nomem();
982   }
983   if (act != AC_DIGEST) {
984     if (!flagdo)                        /* only do digests */
985       strerr_die2x(100,FATAL,ERR_NOCMD);
986     if (flagpublic == 2)
987       flagpublic = getconf_line(&line,"public",0,FATAL,dir);
988     if (!flagpublic) {
989                 /* This all to take care of non-public lists. They should*/
990                 /* still do digests, but do other things only for        */
991                 /* moderators that have remote access. Since this is rare*/
992                 /* efforts have been made to keep everything that's not  */
993                 /* needed elsewhere in here.                   */
994       getconf_line(&moddir,"modsub",0,FATAL,dir);
995       flagremote = getconf_line(&line,"remote",0,FATAL,dir);
996       if (!flagremote)
997         strerr_die2x(100,FATAL,ERR_NOT_PUBLIC);
998       if (!moddir.len || moddir.s[0] != '/') {
999         if (line.len && line.s[0] == '/') {
1000           if (!stralloc_copy(&moddir,&line)) die_nomem();
1001         } else {
1002           if (!stralloc_copys(&moddir,dir)) die_nomem();
1003           if (!stralloc_cats(&moddir,"/mod")) die_nomem();
1004         }
1005       }
1006       if (!stralloc_0(&moddir)) die_nomem();
1007       pmod = issub(moddir.s,sender,(char *) 0,FATAL);
1008       if (!pmod)                        /* sender = moderator? */
1009         strerr_die2x(100,FATAL,ERR_NOT_PUBLIC);
1010       else {
1011         if (!stralloc_copys(&mod,pmod)) die_nomem();
1012         if (!stralloc_0(&mod)) die_nomem();
1013         pmod = mod.s;           /* send to address in list not matching bait */
1014       }
1015     }
1016   }
1017
1018   flagindexed = getconf_line(&line,"indexed",0,FATAL,dir);
1019   flagarchived = getconf_line(&line,"archived",0,FATAL,dir);
1020
1021   if (getconf_line(&charset,"charset",0,FATAL,dir)) {
1022     if (charset.len >= 2 && charset.s[charset.len - 2] == ':') {
1023       if (charset.s[charset.len - 1] == 'B' ||
1024                  charset.s[charset.len - 1] == 'Q') {
1025         flagcd = charset.s[charset.len - 1];
1026         charset.s[charset.len - 2] = '\0';
1027       }
1028     }
1029   } else
1030     if (!stralloc_copys(&charset,TXT_DEF_CHARSET)) die_nomem();
1031   if (!stralloc_0(&charset)) die_nomem();
1032   getconf_line(&mailinglist,"mailinglist",1,FATAL,dir);
1033   getconf_line(&outhost,"outhost",1,FATAL,dir);
1034   set_cpouthost(&outhost);
1035
1036     if (!stralloc_copys(&ddir,dir)) die_nomem();
1037     if (!stralloc_cats(&ddir,"/digest")) die_nomem();
1038     if (!stralloc_0(&ddir)) die_nomem();
1039   if (act == AC_DIGEST) {
1040     workdir = ddir.s;
1041     if (!stralloc_cats(&outlocal,"-digest")) die_nomem();
1042     if (getconf_line(&line,"chunk",0,FATAL,dir)) {
1043       if (!stralloc_0(&line)) die_nomem();
1044       (void) scan_ulong(line.s,&chunk);         /* same chunk as main list */
1045       if (chunk == 0)                           /* limit range to 1-53 */
1046         chunk = 1L;
1047       else if (chunk > 52)
1048         chunk = 52L;
1049     } else {
1050       chunk = 0L;                               /* maybe direct qmqp? */
1051       if (!stralloc_copys(&line,QMQPSERVERS)) die_nomem();
1052       if (!stralloc_cats(&line,"/0")) die_nomem();
1053       if (!stralloc_0(&line)) die_nomem();
1054       flagqmqp = getconf(&qmqpservers,line.s,0,FATAL,dir);
1055     }
1056   } else
1057     workdir = dir;
1058
1059   if (!stralloc_copys(&edir,dir)) die_nomem();  /* not needed for -dig, but */
1060   if (!stralloc_cats(&edir,"/allow")) die_nomem();      /* be safe */
1061   if (!stralloc_0(&edir)) die_nomem();
1062   set_cpoutlocal(&outlocal);            /* needed for copy */
1063
1064   if (flagqmqp) {
1065     if (qmail_open(&qq,&qmqpservers) == -1)             /* open qmail */
1066       strerr_die2sys(111,FATAL,ERR_QMAIL_QUEUE);
1067   } else if (qmail_open(&qq,(stralloc *) 0) == -1)      /* open qmail */
1068       strerr_die2sys(111,FATAL,ERR_QMAIL_QUEUE);
1069
1070   set_cpnum("");        /* default for <#n#> replacement */
1071
1072   if (act == AC_DIGEST) {
1073 /* -dig{.|-}'digestcode'[f] returns an rfc1153 digest                        */
1074 /* of messages from the archive. Messages                                    */
1075 /* dignum+1 through the last message received by the list are processed and  */
1076 /* dignum is updated to the last message processed. digissue is advanced.    */
1077
1078     if (!flagarchived)
1079       strerr_die2x(100,FATAL,ERR_NOT_ARCHIVED);
1080
1081     get_num();                          /* max = last successful message */
1082     to = max;
1083     lockup();                   /* another digest could corrupt dignum */
1084                                 /* but will be saved only if flagdigrange==0 */
1085     if(getconf_line(&num,"dignum",0,FATAL,dir)) {
1086       if(!stralloc_0(&num)) die_nomem();
1087       pos = scan_ulong(num.s,&prevmax);
1088       if (num.s[pos] == ':') pos++;
1089       pos += 1 + scan_ulong(num.s+pos,&cumsize);        /* last cumsize */
1090       if (num.s[pos] == ':') pos++;
1091       scan_ulong(num.s+pos,&digwhen);                   /* last reg dig */
1092     } else {
1093       prevmax = 0L;
1094       cumsize = 0L;
1095       digwhen = 0L;
1096     }
1097     mno = prevmax + 1L;
1098     if(!max || mno > max)       /* if a digest-list is "sending" the request, */
1099                                 /* don't make noise: errors go to postmaster!*/
1100       strerr_die2x(goodexit,FATAL,ERR_EMPTY_DIGEST);
1101     szmsgnum[fmt_ulong(szmsgnum,mno)] = '\0';
1102     set_cpnum(szmsgnum);        /* for copy */
1103                                 /* prepare subject to get entropy for tagmsg*/
1104     if (!stralloc_cats(&subject," Digest ")) die_nomem();
1105     if (!stralloc_catb(&subject,date,date822fmt(date,&dt)-1))
1106           die_nomem();          /* skip trailing in date '\n' */
1107     if (!stralloc_cats(&subject," Issue ")) die_nomem();
1108     if (getconf_line(&num,"digissue",0,FATAL,dir)) {
1109       if(!stralloc_0(&num)) die_nomem();
1110       scan_ulong(num.s,&issue);
1111       issue++;
1112     } else {
1113       issue = 1;
1114     }
1115     if (!stralloc_catb(&subject,strnum,fmt_ulong(strnum,issue)))
1116       die_nomem();
1117                                         /* use the subject as entropy */
1118     if (!stralloc_copy(&line,&subject)) die_nomem();
1119     if (!stralloc_0(&line)) die_nomem();
1120
1121     if (!stralloc_ready(&seed,HASHLEN+1)) die_nomem();
1122     seed.len = HASHLEN + 1;
1123     seed.s[HASHLEN] = '\0';
1124     makehash(line.s,line.len,seed.s);
1125     if (chunk) {                        /* only if slaves are used */
1126       qmail_puts(&qq,"Ezauth: ");
1127       qmail_put(&qq,seed.s,HASHLEN);
1128       qmail_puts(&qq,"\n");
1129     }
1130
1131     doheaders();
1132     qmail_puts(&qq,"To: ");
1133     if (!quote(&quoted,&listname)) die_nomem();
1134     qmail_put(&qq,quoted.s,quoted.len);
1135     qmail_puts(&qq,"@");
1136     qmail_put(&qq,outhost.s,outhost.len);
1137     qmail_puts(&qq,"\n");
1138     if (flagindexed && (outformat != NATIVE))
1139       idx_mkthreads(&msgtable,&subtable,&authtable,&datetable,
1140         mno,to,max,flaglocked,FATAL);
1141     else
1142       idx_mklist(&msgtable,&subtable,&authtable,mno,to,FATAL);
1143     digest(msgtable,subtable,authtable,mno,to,&subject,AC_DIGEST,outformat);
1144
1145     write_ulong(issue,0L,0L,"digissue","digissuen");
1146     write_ulong(max,cumsizen, (unsigned long) when,"dignum","dignumn");
1147   }
1148
1149   else if (act == AC_GET) {
1150
1151 /* -get[-|\.][[num].num2] copies archive num-num2. num & num2 are adjusted   */
1152 /* to be > 0 and <= last message, to num2 >= num and to num2-num <= MAXGET.  */
1153
1154     if (!flagarchived)
1155       strerr_die2x(100,FATAL,ERR_NOT_ARCHIVED);
1156     zapnonsub(ACTION_GET);              /* restrict to subs if requested */
1157     tosender();
1158                                 /* for rfc1153 */
1159     if (!stralloc_cats(&subject," Digest of: ")) die_nomem();
1160     if (!stralloc_cats(&subject,action)) die_nomem();
1161
1162     to = 0;
1163     pos = str_len(ACTION_GET);
1164     if (!case_starts(action,ACTION_GET))
1165       pos = str_len(ALT_GET);
1166     if (FORMATS[str_chr(FORMATS,action[pos])]) {
1167        outformat = action[pos];
1168        ++pos;
1169     }
1170                                         /* optional - or . after '-get' */
1171     if (action[pos] == '-' || action[pos] == '.') pos++;
1172     get_num();                          /* max = last successful message */
1173                                         /* accept any separator. It may be  */
1174                                         /* the terminal '\n', but then      */
1175                                         /* scan will = 0 on the \0 so should*/
1176                                         /* be safe                          */
1177     if (!max)
1178       strerr_die2x(100,FATAL,ERR_EMPTY_LIST);
1179     szmsgnum[fmt_ulong(szmsgnum,max)] = '\0';
1180     set_cpnum(szmsgnum);        /* for copy this is the latest message arch'd*/
1181     doheaders();
1182     if(action[pos += scan_ulong(action + pos,&u)])
1183       scan_ulong(action + pos + 1, &to);
1184     if (u == 0 && to == 0) {            /* default: messages since last */
1185                                         /* digest, or last MAXGET if too many */
1186       to= max;
1187       u = dignum();
1188       if (u == 0) {             /* no digest => last up to HISTGET msgs */
1189         to = max;
1190         if (max > HISTGET) u = max - HISTGET; else u = 1;
1191       }
1192       if (to - u >= MAXGET) u = to - MAXGET + 1;        /* max MAXGET */
1193     } else if (u > max) {
1194       if (to) {                 /* -get.999999_x returns 30 and msg since last*/
1195         to = max;               /* digest 30*/
1196         u = dignum();
1197         if (u > HISTGET) u -= HISTGET; else u = 1;
1198         if (to - u >= MAXGET) u = to - MAXGET + 1;
1199       } else
1200         u = max;
1201     }
1202     if (u == 0) u = 1;                  /* -get.5 => 1-5 */
1203     if (to < u) to = u;                 /* -get23_2 => 23 */
1204     if (to >= u + MAXGET) to = u + MAXGET - 1;
1205                                         /* no more than MAXGET at a time */
1206     if (to > max) to = max;
1207     if (flagindexed && (outformat != NATIVE))   /* fake out threading */
1208       idx_mkthreads(&msgtable,&subtable,&authtable,&datetable,
1209         u,to,max,0,FATAL);
1210     else
1211       idx_mklist(&msgtable,&subtable,&authtable,u,to,FATAL);
1212     digest(msgtable,subtable,authtable,u,to,&subject,AC_GET,outformat);
1213   }
1214
1215   else if (act == AC_INDEX) {
1216
1217 /* -index[f][#|-|\.][[num][.num2] Show subject index for messages num-num2 in*/
1218 /* sets of 100.                                                              */
1219 /* Default last 2 sets. num and num2 are made reasonable as for get. num2 is */
1220 /* limited to num+MAXINDEX to limit the amount of data sent.                 */
1221
1222     if (!flagindexed)
1223       strerr_die2x(100,FATAL,ERR_NOT_INDEXED);
1224     if (flagsub)
1225     zapnonsub(ACTION_INDEX);    /* restrict to subs if requested */
1226     to = 0;
1227     pos = str_len(ACTION_INDEX);
1228     if (!case_starts(action,ACTION_INDEX))
1229       pos = str_len(ALT_INDEX);
1230     if (FORMATS[str_chr(FORMATS,action[pos])]) {
1231        outformat = action[pos];         /* ignored, but be nice ... */
1232        ++pos;
1233     }
1234     get_num();                          /* max = last successful message */
1235     if (!max)
1236       strerr_die2x(100,FATAL,ERR_EMPTY_LIST);
1237     szmsgnum[fmt_ulong(szmsgnum,max)] = '\0';
1238     set_cpnum(szmsgnum);        /* for copy this is the latest message arch'd*/
1239
1240     doheaders();
1241     tosender();
1242     if (!stralloc_cats(&subject," Result of: ")) die_nomem();
1243     if (!stralloc_cats(&subject,action)) die_nomem();
1244     presub(1,1,&subject,AC_INDEX,outformat);
1245
1246     if (action[pos] == '-' || action[pos] == '.') pos++;
1247     if(action[pos += scan_ulong(action + pos,&u)])
1248       scan_ulong(action + pos + 1, &to);
1249
1250     if (u == 0 && to == 0) { to = max; u = max - 100; }
1251     if (u <= 0) u = 1;
1252     if (u > max) u = max;
1253     if (to < u) to = u;
1254     if (to > u + MAXINDEX) to = u+MAXINDEX;     /* max MAXINDEX index files */
1255     if (to > max) to = max;
1256     u /= 100;
1257     to /= 100;
1258     while (u <= to) {
1259       if (!stralloc_copys(&fn,"archive/")) die_nomem();
1260       if (!stralloc_catb(&fn,strnum,fmt_ulong(strnum,u))) die_nomem();
1261       if (!stralloc_cats(&fn,"/index")) die_nomem();
1262       if (!stralloc_0(&fn)) die_nomem();
1263
1264       if (u == max/100) /* lock if last index file in archive */
1265         lockup();
1266
1267       fd = open_read(fn.s);
1268       if (fd == -1)
1269         if (errno != error_noent)
1270           strerr_die4sys(111,FATAL,ERR_OPEN,fn.s,": ");
1271         else
1272           code_qput(TXT_NOINDEX,str_len(TXT_NOINDEX));
1273       else {
1274         substdio_fdbuf(&sstext,read,fd,textbuf,sizeof(textbuf));
1275         for (;;) {
1276           if (getln(&sstext,&line,&match,'\n') == -1)
1277             strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
1278           if (match) {
1279             if (line.s[0] != '\t') {    /* subject line */
1280               pos = byte_chr(line.s,line.len,' ');
1281               if (pos && pos != line.len && line.s[pos - 1] == ':')
1282                 pos1 = pos + HASHLEN + 1;       /* after hash */
1283               if (pos1 >= line.len) {   /* bad! */
1284                 pos = 0;
1285                 pos1 = 0;               /* output as is */
1286               }
1287               if (!stralloc_copyb(&line2,line.s,pos)) die_nomem();
1288               if (!stralloc_catb(&line2,line.s+pos1,line.len-pos1)) die_nomem();
1289             } else {
1290               pos = byte_chr(line.s,line.len,';');
1291               if (pos + HASHLEN + 1 < line.len && pos > 15 &&
1292                                 line.s[pos + 1] != ' ') {
1293                   if (!stralloc_copyb(&line2,line.s,pos - 15)) die_nomem();
1294                   pos++;
1295                   if (!stralloc_catb(&line2,line.s + pos + HASHLEN,
1296                         line.len - pos - HASHLEN)) die_nomem();
1297               } else                    /* old format - no author hash */
1298                 if (!stralloc_copyb(&line2,line.s,line.len)) die_nomem();
1299             }
1300             code_qput(line2.s,line2.len);
1301           } else
1302             break;
1303         }
1304         close(fd);
1305       }
1306
1307       if (u == max/100) /* unlock if last index in archive file */
1308         unlock();
1309
1310       u++;
1311     }
1312     normal_bottom(outformat);
1313     postmsg(outformat);
1314   }
1315
1316   else if (act == AC_THREAD) {
1317
1318 /* -thread[f][-|.]num returns messages with subject matching message        */
1319 /* 'num' in the subject index. If 'num' is not in[1..last_message] an error */
1320 /* message is returned.                                                     */
1321
1322     if (!flagarchived)
1323       strerr_die2x(100,FATAL,ERR_NOT_ARCHIVED);
1324     if (!flagindexed)
1325       strerr_die2x(100,FATAL,ERR_NOT_INDEXED);
1326
1327     zapnonsub(ACTION_THREAD);           /* restrict to subs if requested*/
1328
1329     get_num();                          /* max = last successful message */
1330     if (!max)
1331       strerr_die2x(100,FATAL,ERR_EMPTY_LIST);
1332     szmsgnum[fmt_ulong(szmsgnum,max)] = '\0';
1333     set_cpnum(szmsgnum);        /* for copy this is the latest message arch'd*/
1334
1335     doheaders();
1336     tosender();
1337                                 /* for rfc1153 */
1338     if (!stralloc_cats(&subject," Digest of: ")) die_nomem();
1339     if (!stralloc_cats(&subject,action)) die_nomem();
1340
1341     to = 0;
1342     pos = str_len(ACTION_THREAD);
1343     if (!case_starts(action,ACTION_THREAD))
1344       pos = str_len(ALT_THREAD);
1345     if (FORMATS[str_chr(FORMATS,action[pos])]) {
1346        outformat = action[pos];
1347        ++pos;
1348     }
1349     if (action[pos] == '-' || action[pos] == '.') pos++;
1350     if(action[pos += scan_ulong(action + pos,&u)])
1351       scan_ulong(action + pos + 1, &to);
1352
1353     if(u == 0 || u > max) {
1354       if (!stralloc_cats(&subject,"\n\n")) die_nomem();
1355       qmail_puts(&qq,"Subject: ");
1356       qmail_put(&qq,subject.s,subject.len);
1357       copy(&qq,"text/get-bad",flagcd,FATAL);
1358     } else {    /* limit range to at most u-THREAD_BEFORE to u+THREAD_AFTER */
1359       if (u > THREAD_BEFORE)
1360         from = u-THREAD_BEFORE;
1361       else
1362         from = 1L;
1363       if (u + THREAD_AFTER > max) {
1364         idx_mkthread(&msgtable,&subtable,&authtable,from,max,u,max,0,FATAL);
1365         digest(msgtable,subtable,authtable,from,max,&subject,
1366                 AC_THREAD,outformat);
1367       } else {
1368         idx_mkthread(&msgtable,&subtable,&authtable,
1369                 from,u+THREAD_AFTER,u,max,0,FATAL);
1370         digest(msgtable,subtable,authtable,from,u+THREAD_AFTER,
1371                         &subject,AC_THREAD,outformat);
1372       }
1373     }
1374   }
1375
1376   else
1377         /* This happens if the initial check at the beginning of 'main'    */
1378         /* matches something that isn't matched here. Conversely, just     */
1379         /* adding an action here is not enough - it has to be added to the */
1380         /* initial check as well.                                          */
1381
1382     strerr_die2x(100,FATAL,
1383       "Program error: I'm supposed to deal with this but I didn't");
1384
1385   if (!stralloc_copy(&line,&outlocal)) die_nomem();
1386   if (act == AC_DIGEST) {
1387     if (chunk) {
1388       if (!stralloc_cats(&line,"-return-g-")) die_nomem();
1389     } else
1390       if (!stralloc_cats(&line,"-return-")) die_nomem();
1391     strnum[fmt_ulong(strnum,mno)] = '\0';
1392     if (!stralloc_cats(&line,strnum)) die_nomem();
1393     if (!stralloc_cats(&line,"-@")) die_nomem();
1394
1395     if (!stralloc_cat(&line,&outhost)) die_nomem();
1396     if (!stralloc_cats(&line,"-@[]")) die_nomem();
1397   } else {
1398     if (!stralloc_cats(&line,"-return-@")) die_nomem();
1399     if (!stralloc_cat(&line,&outhost)) die_nomem();
1400   }
1401   if (!stralloc_0(&line)) die_nomem();
1402
1403   qmail_from(&qq,line.s);
1404   if (act == AC_DIGEST) {        /* Do recipients */
1405     tagmsg(workdir,mno,seed.s,"d",hashout,qq.msgbytes,chunk,FATAL);
1406     if (chunk) {
1407       if (!stralloc_copys(&line,"T")) die_nomem();
1408       if (!stralloc_cat(&line,&outlocal)) die_nomem();
1409       if (!stralloc_cats(&line,"-s-d-")) die_nomem();
1410       if (!stralloc_catb(&line,hashout,COOKIE)) die_nomem();
1411       if (!stralloc_cats(&line,"-")) die_nomem();
1412       if (!stralloc_cats(&line,strnum)) die_nomem();
1413       if (!stralloc_cats(&line,"-")) die_nomem();
1414       if (!stralloc_copys(&line2,"@")) die_nomem();
1415       if (!stralloc_cat(&line2,&outhost)) die_nomem();
1416       if (!stralloc_0(&line2)) die_nomem();
1417       j = 0;
1418       for (i = 0; i <= 52; i += chunk) {                /* To slaves */
1419         qmail_put(&qq,line.s,line.len);
1420         schar[0] = '0' + i / 10;
1421         schar[1] = '0' + (i % 10);
1422         qmail_put(&qq,schar,3);
1423         j += (chunk - 1);
1424         if (j > 52) j = 52;
1425         schar[0] = '0' + j / 10;
1426         schar[1] = '0' + (j % 10);
1427         qmail_put(&qq,schar,2);
1428         qmail_put(&qq,line2.s,line2.len);
1429       }
1430     } else
1431       subs = putsubs(workdir,0L,52L,&subto,1,FATAL);
1432   } else {                      /* if local is set, sender is checked */
1433     if (pmod)
1434       qmail_to(&qq,pmod);
1435     else
1436       qmail_to(&qq,sender);
1437   }
1438
1439   if (*(err = qmail_close(&qq)) == '\0') {      /* Done. Skip rest. */
1440     if (act == AC_DIGEST) {
1441       if (chunk)
1442         (void) logmsg(workdir,mno,0L,0L,2);
1443       else
1444         (void) logmsg(workdir,mno,0L,subs,4);
1445     }
1446     closesql();                 /* close db connection */
1447     unlock();                   /* NOP if nothing locked */
1448     strnum[fmt_ulong(strnum,qmail_qp(&qq))] = 0;
1449     strerr_die2x(goodexit,"ezmlm-get: info: qp ",strnum);
1450   } else {                      /* failed. Reset last msg & issue for digest */
1451     if(act == AC_DIGEST) {
1452       issue--;
1453       write_ulong(issue,0L,0L,"digissue","digissuen");
1454       write_ulong(prevmax,cumsize,(unsigned long) digwhen,"dignum","dignumn");
1455     }
1456     unlock();                   /* NOP if nothing locked */
1457     strerr_die3x(111,FATAL,ERR_TMP_QMAIL_QUEUE,err + 1);
1458   }
1459 }