chiark / gitweb /
Debianization and various other fixes.
[ezmlm] / ezmlm-cgi.c
1 /*$Id: ezmlm-cgi.c,v 1.17 1999/12/24 04:21:26 lindberg Exp $*/
2 /*$Name: ezmlm-idx-040 $*/
3
4 /* Please leave. Will hopefully help pay for further improvement. */
5 #define EZ_CRIGHT "<a href=\"http://www.lindeinc.com\">(c) 1999 Lin-De, Inc</a>"
6 /******/
7 #include <sys/types.h>
8 #include "direntry.h"
9 #include "datetime.h"
10 #include "now.h"
11 #include "stralloc.h"
12 #include "strerr.h"
13 #include "error.h"
14 #include "env.h"
15 #include "sig.h"
16 #include "open.h"
17 #include "getln.h"
18 #include "case.h"
19 #include "scan.h"
20 #include "str.h"
21 #include "fmt.h"
22 #include "readwrite.h"
23 #include "fork.h"
24 #include "wait.h"
25 #include "exit.h"
26 #include "substdio.h"
27 #include "getconf.h"
28 #include "gen_alloc.h"
29 #include "gen_allocdefs.h"
30 #include "constmap.h"
31 #include "byte.h"
32 #include "subscribe.h"
33 #include "errtxt.h"
34 #include "makehash.h"
35 #include "mime.h"
36 #include "idx.h"
37 #include "yyyymm.h"
38
39 #define FATAL "ezmlm-cgi: fatal: "
40 #define GET "-getv"
41 #define THREAD "-threadv"
42 #define SUBSCRIBE "-subscribe"
43 #define FAQ "-faq"
44 #define TXT_CGI_SUBSCRIBE "\">[eSubscribe]</a>\n"
45 #define TXT_CGI_FAQ "\">[eFAQ]</a>\n"
46
47 int flagshowhtml = 1;   /* show text/html parts. This leads to duplication */
48                         /* when both text/plain and text/html are in a     */
49                         /* multipart/alternative message, but it is assumed*/
50                         /* that text/html is not frivolous, but only used  */
51                         /* when the formatting is important. */
52 int flagobscure = 0;    /* Don't remove Sender's E-mail address in message */
53                         /* view. Overridden by config file (- before list */
54                         /* name). */
55
56 /**************** Header processing ***********************/
57 char headers_used[] = "Subject\\From\\Date\\content-type\\"
58                 "content-transfer-encoding\\mime-version";
59 /* index of headers displayed (shown in order listed above) */
60 int headers_shown[] = {1,1,1,0,0,0};
61 /* index of specific headers */
62 #define NO_HDRS 6
63 #define HDR_SUBJECT 1
64 #define HDR_FROM 2
65 #define HDR_CT 4
66 #define HDR_CTENC 5
67 #define HDR_VERSION 6
68
69 /* Need to add inits if you increase NO_HDRS */
70 stralloc hdr[NO_HDRS] = { {0},{0},{0},{0},{0},{0} };
71 /**************** Header processing ***********************/
72
73
74 /* index of subject in above, first = 1 */
75
76 /* TODO: Sort headers before display. Find a way to display the body with the*/
77 /* correct charset, ideally letting the browser do the work (should really */
78 /* be able to specify charset for DIV ! */
79
80 /* ulong at least 32 bits. (Creating a Year 0xffffff problem ;-) */
81 #define MAXULONG 0xffffffff
82
83 char cmdstr[5] = "xxx:";
84 #define ITEM "-msadiz"
85 #define ITEM_MESSAGE 1
86 #define ITEM_SUBJECT 2
87 #define ITEM_AUTHOR 3
88 #define ITEM_DATE 4
89 #define ITEM_INDEX 5
90
91 #define DIRECT "psnpn"
92 #define DIRECT_SAME 0
93 #define DIRECT_NEXT 1
94 #define DIRECT_PREV -1
95 /* use only as the argument for some functions. Terrible hack for date links */
96 #define DIRECT_FIRST 3
97 #define DIRECT_LAST 2
98
99 char *dir = 0;
100 char *local = 0;
101 char *host = 0;
102 char *home = 0;
103 char *banner = 0;
104 char *charset = 0;
105 char *stylesheet = 0;
106 char *cmd;
107 char strnum[FMT_ULONG];
108 /* these are the only headers we really care about for message display */
109 /* one can always retrieve the complete message by E-mail */
110 stralloc charg = {0};
111 stralloc url = {0};
112 stralloc author = {0};
113 stralloc subject = {0};
114 stralloc base = {0};
115 stralloc line = {0};
116 stralloc decline = {0};         /* for rfc2047-decoded headers and QP/base64 */
117 stralloc cfline = {0};          /* from config file */
118 stralloc fn = {0};
119 stralloc dtline = {0};
120 stralloc headers = {0};
121 stralloc encoding = {0};
122 stralloc content = {0};
123 stralloc charsetbase = {0};
124 stralloc curcharset = {0};
125 stralloc sainit = {0};
126 struct constmap headermap;
127 unsigned long uid,euid;
128 int recursion_level;
129 int so = 0;
130 int ss23 = 0;
131 int state = 0;
132 int newlevel;
133 int match;      /* used everywhere and no overlap */
134 int fd;         /* same; never >1 open */
135 int cache;      /* 0 = don't; 1 = don't know; 2 = do */
136 int child,wstat;
137 int flagtoplevel;
138 unsigned int flagmime;
139 unsigned int cs,csbase;
140 int flagrobot;
141 int flagpre;
142 int precharcount;
143 char cn1 = 0;
144 char cn2 = 0;
145 char lastjp[] = "B";    /* to get back to the correct JP after line break */
146 char *bannerargs[4];
147
148
149 mime_info *mime_current = 0;
150 mime_info *mime_tmp = 0;
151
152 datetime_sec when;
153 struct datetime dt;
154
155 char inbuf[4096];
156 substdio ssin;
157
158 void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); }
159
160 void die_syntax(char *s)
161 {
162   strerr_die4x(100,FATAL,ERR_SYNTAX,"config file: ",s);
163 }
164
165 char outbuf[4096];
166 substdio ssout = SUBSTDIO_FDBUF(write,1,outbuf,sizeof(outbuf));
167
168 void oput(register char *s, register unsigned int l)
169 /* unbuffered. Avoid extra copy as httpd buffers */
170 {
171   if (substdio_put(&ssout,s,l) == -1)
172     strerr_die3sys(111,FATAL,ERR_WRITE,"stdout: ");
173 }
174
175 void oputs(register char *s)
176 {
177   oput(s,str_len(s));
178 }
179
180 /* this error is for things that happen only if program logic is screwed up */
181 void die_prog(char *s) { strerr_die5x(100,FATAL,"program error (please send bug report to bugs@ezmlm.org): ",s," Command: ",cmd); }
182
183 /* If we already issued a header than this will look ugly */
184 void cgierr(char *s,char *s1,char *s2)
185 {
186   strerr_warn4(FATAL,s,s1,s2,(struct strerr *)0);
187   oputs("Content-type: text/plain\n");
188   oputs("Status: 500 Couldn't do it\n\n");
189   oputs("I tried my best, but:\n\n");
190   if (s) oputs(s);
191   if (s1) oputs(s1);
192   if (s2) oputs(s2);
193   oputs("\n");
194   substdio_flush(&ssout);
195   _exit(0);
196 }
197
198 unsigned long msgnav[5]; /* 0 prev prev 1 prev 2 this 3 next 4 next-next */
199
200 struct msginfo {        /* clean info on the target message */
201   char item;            /* What we want */
202   char direction;       /* Relation to current msg */
203   char axis;            /* Axis of desired movement [may be calculated] */
204   unsigned long source; /* reference message number */
205   unsigned long target;
206   unsigned long date;
207   unsigned long *authnav;       /* msgnav structure */
208   unsigned long *subjnav;       /* msgnav structure */
209   char *author;
210   char *subject;
211   char *cgiarg;                 /* sub/auth as expected from axis */
212 } msginfo;
213
214 void toggle_flagpre(int flag)
215 {
216   flagpre = flag;
217   precharcount = 0;
218   cn1 = 0; cn2 = 0;             /* just in case */
219 }
220
221 unsigned int decode_charset(register char *s, register unsigned int l)
222 /* return charset code. CS_BAD means that base charset should be used, i.e. */
223 /* that charset is empty or likely invalid. CS_NONE are charsets for which  */
224 /* we don't need to do anything special. */
225 {
226   unsigned int r;
227
228   if (case_startb(s,l,"iso-8859") || case_startb(s,l,"us-ascii") ||
229         case_startb(s,l,"utf")) /* at the moment, we can do utf-8 right */
230     return CS_NONE;             /* what is utf-7 (used by OE)? */
231   if (case_startb(s,l,"x-cp") ||
232         case_startb(s,l,"cp") ||
233         case_startb(s,l,"x-mac") ||
234         case_startb(s,l,"koi8")) return CS_NONE;
235   if (!l || *s == 'x' || *s == 'X') return CS_BAD;
236   if (case_startb(s,l,"iso-2022")) {
237     if (case_startb(s+8,l-8,"-cn"))
238       return CS_2022_CN;
239     if (case_startb(s+8,l-8,"-jp"))
240       return CS_2022_JP;
241     return CS_2022_KR;
242   }
243   if (case_startb(s,l,"cn-") ||
244                 case_startb(s,l,"hz-gb") ||
245                 case_startb(s,l,"gb") ||
246                 case_startb(s,l,"big5"))
247     return CS_CN;               /* Only consideration for linebreak */
248   if (case_startb(s,l,"iso_8859") ||
249         case_startb(s,l,"latin") ||
250         case_startb(s,l,"windows")) return CS_NONE;
251 /* Add other charsets here. Later we will add code to replace a detected */
252 /* charset name with another, and to connect conversion routines, such as */
253 /* between windows-1251/koi-8r/iso-8859-5 */
254   return CS_BAD;
255 }
256
257 void htmlencode_put (register char *s,register unsigned int l)
258 /* At this time, us-ascii, iso-8859-? create no problems. We just encode  */
259 /* some html chars. iso-2022 may have these chars as character components.*/
260 /* cs is set for these, 3 for CN, 2 for others. Bit 0 set means 2 byte    */
261 /* chars for SS2/SS3 shiftouts (JP doesn't use them, KR has single byte.  */
262 /* If cs is set and we're shifted out (so set) we don't substitute. We    */
263 /* also look for SI/SO to adjust so, and ESC to detect SS2/SS3. Need to   */
264 /* ignore other ESC seqs correctly. JP doesn't use SI/SO, but uses        */
265 /* ESC ( B/J and ESC $ B/@ analogously, so we use these to toggle so.     */
266 /* "Roman", i.e. ESC ( J is treated as ascii - no differences in html-    */
267 /* relevant chars. Together, this allows us to deal with all iso-2022-*   */
268 /* as a package. see rfc1468, 1554, 1557, 1922 for more info.             */
269 /* line break at 84 to avoid splits with lines just a little too long. */
270 {
271   if (!cs) {            /* us-ascii & iso-8859- & unrecognized */
272     for (;l--;s++) {
273       precharcount++;
274       switch (*s) {
275         case '>': oputs("&gt;"); break;
276         case '<': oputs("&lt;"); break;
277         case '"': oputs("&quot;"); break;
278         case '&': oputs("&amp;"); break;
279         case '\n': precharcount = 0; oput(s,1); break;
280         case ' ':
281           if (precharcount >= 84 && flagpre) {
282             oput("\n",1);                       /* in place of ' ' */
283             precharcount = 0;
284           } else
285             oput(s,1);                          /* otherwise out with it. */
286           break;
287         default: oput(s,1); break;
288       }
289     }
290   } else if (cs == CS_CN) {                     /* cn-, gb*, big5 */
291     for (;l--;s++) {
292       precharcount++;
293       if (cn1) { cn2 = cn1; cn1 = 0; }          /* this is byte 2 */
294       else { cn2 = 0; cn1 = *s & 0x80; }        /* this is byte 1/2 or ascii */
295       if (!cn1 && !cn2) {                       /* ascii */
296         switch (*s) {
297           case '>': oputs("&gt;"); break;
298           case '<': oputs("&lt;"); break;
299           case '"': oputs("&quot;"); break;
300           case '&': oputs("&amp;"); break;
301           case '\n': precharcount = 0; oput(s,1); break;
302           case ' ':
303                 if (precharcount >= 84 && flagpre) {
304                   oput("\n",1);         /* break in ascii sequence */
305                   precharcount = 0;
306                 } else
307                   oput(s,1);
308                 break;
309           default: oput(s,1); break;
310         }
311       } else if (precharcount >= 84 && flagpre && cn2) {
312           oput("\n",1);                 /* break after 2-byte code */
313           precharcount = 0;
314       }
315     }
316   } else {                                      /* iso-2022 => PAIN! */
317     for (;l--;s++) {
318       precharcount++;
319       if (ss23) {                               /* ss2/ss3 character */
320         ss23--;
321         oput(s,1);
322         continue;
323       }
324       if (so) {                                 /* = 0 ascii, = 1 SO charset */
325         if (!(*s & 0xe0)) {                     /* ctrl-char */
326           switch (*s) {
327             case ESC: state = 1; break;
328             case SI: so = 0; break;
329             case '\n': precharcount = 0; break;
330             default: break;
331           }
332         }
333         oput(s,1);
334       } else {                                  /* check only ascii */
335         switch (*s) {
336           case '>': oputs("&gt;"); break;
337           case '<': oputs("&lt;"); break;
338           case '"': oputs("&quot;"); break;
339           case '&': oputs("&amp;"); break;
340           case ' ':
341                 if (precharcount >= 84 && flagpre) {
342                   oput("\n",1);         /* break in ascii sequence */
343                   precharcount = 0;
344                 } else
345                   oput(s,1);
346                 break;
347           default:
348                   oput(s,1);
349                   if (!(*s & 0xe0)) {
350                     switch (*s) {
351                       case SO: so = 1; break;
352                       case ESC: state = 1; break;
353                       case SI: so = 0; break;   /* shouldn't happen */
354                       case '\n': precharcount = 0; break;
355                       default: break;
356                     }
357                   }
358         }
359       }         /* by now all output is done, now ESC interpretation */
360       if (state) {
361                 /* ESC code - don't count */
362           if (precharcount) precharcount--;
363           state++;
364           switch (state) {
365             case 2: break;                      /* this was the ESC */
366             case 3: switch (*s) {
367                         case 'N': ss23 = (cs & 1) + 1; state = 0; break;
368                         case 'O': ss23 = 2; state = 0; break;
369                         case '(': state = 20; so = 0; break;    /* JP ascii */
370                         case '$': break;                /* var S2/SS2/SS3 des*/
371                         case '.': state = 10;   /* g3 settings, one more char */
372                         default: state = 0; break;      /* or JP */
373                 }
374                 break;
375             case 4: switch (*s) {       /* s2/ss2/ss3 or JP 2 byte shift */
376                    case 'B':
377                    case '@': lastjp[0] = *s;
378                              so = 1; state = 0; break;  /* JP */
379                    default: break;                      /* other SS2/3 des */
380                  }
381                  break;
382             case 5:  state = 0; break;          /* 4th char of ESC $ *|+|) X */
383             case 11: state = 0; break;          /* 3nd char of ESC . */
384             case 21: state = 0; break;          /* ESC ( X for JP */
385             default: die_prog("bad state in htmlencode_put"); break;
386           }
387       } else if (so && flagpre && precharcount >= 84) {
388                 /* 84 is nicer than 78/80 since most use GUI browser */
389                 /* iso-2022-* line splitter here. SO only, SI done above */
390                 /* For JP need even precharcount, add ESC ( B \n ESC $B */
391         if (so && !(precharcount & 1)) {        /* even */
392           precharcount = 0;                     /* reset */
393           if (cs == CS_2022_JP) {               /* JP uses ESC like SI/SO */
394             oputs(TOASCII);
395             oput("\n",1);
396             oputs(TOJP);
397             oput(lastjp,1);
398           } else {
399             if (so) {
400                 /* For iso-2022-CN: nothing if SI, otherwise SI \n SO */
401                 /* For iso-2022-KR same */
402               oputs(SI_LF_SO);
403             } else
404               oput("\n",1);
405           }
406         }
407       }
408     }
409   }
410 }
411
412 char hexchar[] = "0123456789ABCDEF";
413 char enc_url[] = "%00";
414
415 void urlencode_put (register char *s,register unsigned int l)
416 {
417   for (;l--;s++) {
418     register unsigned char ch;
419     ch = (unsigned char) *s;
420     if (ch <= 32 || ch > 127 || byte_chr("?<>=/:%+#\"",10,ch) != 10) {
421       enc_url[2] = hexchar[ch & 0xf];
422       enc_url[1] = hexchar[(ch >> 4) & 0xf];
423       oput(enc_url,3);
424     } else
425       oput(s,1);
426   }
427 }
428
429 void urlencode_puts(register char *s)
430 {
431   urlencode_put(s,str_len(s));
432 }
433
434 int checkhash(register char *s)
435 {
436   register int l = HASHLEN;
437   while (l--) {
438     if (*s < 'a' || *s > 'p') return 0; /* illegal */
439     s++;
440   }
441   if (*s) return 0;                     /* extraneous junk */
442   return 1;
443 }
444
445 int makefn(stralloc *sa,char item, unsigned long n, char *hash)
446 {
447   if (!stralloc_copys(sa,"archive/")) die_nomem();
448   if (item == ITEM_MESSAGE) {
449     if (!stralloc_catb(sa,strnum,fmt_ulong(strnum, n / 100))) die_nomem();
450     if (!stralloc_cats(sa,"/")) die_nomem();
451     if (!stralloc_catb(sa,strnum,fmt_uint0(strnum,(unsigned int) (n % 100),2)))
452                         die_nomem();
453   } else if (item == ITEM_DATE) {
454     if (!stralloc_cats(sa,"threads/")) die_nomem();
455     if (!stralloc_catb(sa,strnum,fmt_ulong(strnum,n)))
456         die_nomem();
457   } else if (item == ITEM_INDEX) {
458     if (!stralloc_catb(sa,strnum,fmt_ulong(strnum, n / 100))) die_nomem();
459     if (!stralloc_cats(sa,"/index")) die_nomem();
460   } else {
461     if (item == ITEM_AUTHOR) {
462       if (!stralloc_cats(sa,"authors/")) die_nomem();
463     } else {
464       if (!stralloc_cats(sa,"subjects/")) die_nomem();
465     }
466     if (!hash) return 0;
467     if (!stralloc_catb(sa,hash,2)) die_nomem();
468     if (!stralloc_cats(sa,"/")) die_nomem();
469     if (!stralloc_catb(sa,hash+2,HASHLEN-2)) die_nomem();
470   }
471   if (!stralloc_0(sa)) die_nomem();
472   return 1;
473 }
474
475 void link(struct msginfo *infop,char item,char axis,unsigned long msg,
476                 char *data,unsigned int l)
477 /* links with targets other msg -> msg. If the link is for author, we    */
478 /* still supply subject, since most navigation at the message level will */
479 /* be along threads rather than author and we don't have an author index.*/
480 {
481   char *cp;
482
483   cp = (char *) 0;
484         /* this should be separate routine. Works because all index views */
485         /* have at least a subject link */
486   if (axis == ITEM_SUBJECT && infop->target == msg)
487     oputs("<a name=b></a>");
488   oput(url.s,url.len);
489   cmdstr[0] = ITEM[item];
490   cmdstr[1] = ITEM[axis];
491   cmdstr[2] = DIRECT[DIRECT_SAME + 1];
492   if (item == ITEM_MESSAGE && axis == ITEM_AUTHOR) {
493     if (infop->subject) {
494       cmdstr[1] = ITEM[ITEM_SUBJECT];
495       cp = infop->subject;      /* always HASLEN in length due to decode_cmd */
496     }
497   }
498   oputs(cmdstr);                /* e.g. map: */
499   oput(strnum,fmt_ulong(strnum,msg));
500   if (!cp && l >= HASHLEN)
501     cp = data;
502   if (infop->date) {
503     oput(":",1);
504     oput(strnum,fmt_ulong(strnum,infop->date));
505   }
506   if (cp) {
507     oput(":",1);
508     oput(cp,HASHLEN);
509   }
510   switch (item) {
511     case ITEM_MESSAGE: oputs("\" class=mlk>"); break;
512     case ITEM_AUTHOR: oputs("#b\" class=alk>"); break;
513     case ITEM_SUBJECT: oputs("#b\" class=slk>"); break;
514     default: oputs("#b\">"); break;
515   }
516   if (HASHLEN + 1 < l)
517     htmlencode_put(data + HASHLEN + 1,l - HASHLEN - 1);
518   else
519     oputs("(none)");
520   oputs("</A>");
521 }
522
523 void linktoindex(struct msginfo *infop,char item)
524 /* for links from message view back to author/subject/threads index */
525 {
526   oput(url.s,url.len);
527   cmdstr[0] = ITEM[item];
528   cmdstr[1] = ITEM[item];
529   cmdstr[2] = DIRECT[DIRECT_SAME + 1];
530   oputs(cmdstr);                /* e.g. map: */
531   oput(strnum,fmt_ulong(strnum,infop->target));
532   if (infop->date) {
533     oput(":",1);
534     oput(strnum,fmt_ulong(strnum,infop->date));
535   }
536   switch (item) {
537     case ITEM_AUTHOR:
538       if (infop->author) {
539         oput(":",1);
540         oputs(infop->author);
541       }
542       break;
543     case ITEM_SUBJECT:
544       if (infop->subject) {
545         oput(":",1);
546         oputs(infop->subject);
547       }
548       break;
549     default:
550       break;
551   }
552   oputs("#b\"");
553 }
554
555 void link_msg(struct msginfo *infop,char axis,char direction)
556 /* Creates <a href="mapa:123:aaaaa...."> using a maximum of available */
557 /* info only for links where the target is a message */
558 {
559   unsigned long msg;
560   char *acc;
561   oput(url.s,url.len);
562   cmdstr[0] = ITEM[ITEM_MESSAGE];
563   cmdstr[1] = ITEM[axis];
564   cmdstr[2] = DIRECT[direction + 1];
565   msg = infop->target;
566   acc = 0;
567       switch(axis) {
568         case ITEM_SUBJECT:
569           if (infop->subject)
570             acc = infop->subject;
571           if (infop->subjnav)   /* translate to message navigation */
572             if (infop->subjnav[direction]) {
573               msg = infop->subjnav[direction];
574               cmdstr[2] = DIRECT[DIRECT_SAME + 1];
575           }
576           acc = infop->subject;
577           break;
578         case ITEM_AUTHOR:
579           if (infop->author)
580             acc = infop->author;
581           if (infop->authnav)   /* translate to message navigation */
582             if (infop->authnav[direction]) {
583               msg = infop->authnav[direction];
584               cmdstr[2] = DIRECT[DIRECT_SAME + 1];
585             }
586           acc = infop->author;
587           break;
588         default:
589           break;
590         }
591         oputs(cmdstr);
592         oput(strnum,fmt_ulong(strnum,msg));
593         if (acc) {
594           oputs(":");
595           oputs(acc);
596         }
597         oputs("\">");
598 }
599
600 void justpress()
601 {
602   oputs("?subject=");
603   urlencode_puts("Just Click \"SEND\"!");
604 }
605
606 void homelink()
607 {
608   register char *cp,*cp1,*cp2;
609
610   if (home && *home) {
611     cp = home;
612     for(;;) {
613       cp1 = cp;
614       while(*cp1 && *cp1 != '=') cp1++;
615       if (!*cp1) break;
616       cp2 = cp1;
617       while(*cp2 && *cp2 != ',') cp2++;
618       oputs("<a href=\"");
619       oput(cp1 + 1,cp2 - cp1 - 1);
620       oputs("\">");
621       oput(cp,cp1 - cp);
622       oputs("</a>\n");
623       if (!*cp2) break;
624       cp = cp2 + 1;
625     }
626   }
627 }
628
629 void subfaqlinks()
630 {
631   oputs("<a href=\"mailto:");
632   oputs(local);
633   oputs(SUBSCRIBE);
634   oputs("@");
635   oputs(host);
636   justpress();
637   oputs(TXT_CGI_SUBSCRIBE);
638   oputs("<a href=\"mailto:");
639   oputs(local);
640   oputs(FAQ);
641   oputs("@");
642   oputs(host);
643   justpress();
644   oputs(TXT_CGI_FAQ);
645 }
646
647 void msglinks(struct msginfo *infop)
648 /* Creates the html for all links from one message view */
649 {
650   oputs("<DIV class=msglinks><STRONG>Msg by: ");
651   link_msg(infop,ITEM_SUBJECT,DIRECT_PREV);
652   oputs("[&lt;-</A> ");
653   linktoindex(infop,ITEM_SUBJECT);
654   oputs(">thread</A> ");
655   link_msg(infop,ITEM_SUBJECT,DIRECT_NEXT);
656   oputs("-&gt;]</A> \n");
657   link_msg(infop,ITEM_MESSAGE,DIRECT_PREV);
658   oputs("[&lt;-</A> ");
659   linktoindex(infop,ITEM_INDEX);
660   oputs(">time</A> ");
661   link_msg(infop,ITEM_MESSAGE,DIRECT_NEXT);
662   oputs("-&gt;]</A> \n");
663   link_msg(infop,ITEM_AUTHOR,DIRECT_PREV);
664   oputs("[&lt;-</A> ");
665   linktoindex(infop,ITEM_AUTHOR);
666   oputs(">author</A> ");
667   link_msg(infop,ITEM_AUTHOR,DIRECT_NEXT);
668   oputs("-&gt;]</A> |\n");
669   linktoindex(infop,ITEM_DATE);
670   oputs(">[Threads]</A>\n");
671   homelink();
672   oputs("\n<a href=\"mailto:");
673   oputs(local);
674   oputs(GET);
675   strnum[fmt_ulong(strnum,infop->target)] = '\0';
676   oputs(strnum);
677   oputs("@");
678   oputs(host);
679   justpress();
680   oputs("\">[eMsg]</A>\n");
681   oputs("<a href=\"mailto:");
682   oputs(local);
683   oputs(THREAD);
684   oputs(strnum);
685   oputs("@");
686   oputs(host);
687   justpress();
688   oputs("\">[eThread]</A>\n");
689   subfaqlinks();
690   oputs("</STRONG></DIV>\n");
691 }
692
693 #define SPC_BASE 1
694 #define SPC_BANNER 2
695
696 void html_header(char *t,char *s, unsigned int l,char *class,int flagspecial)
697 /* flagspecial: 0x1 => robot index; no style sheet, no BASE */
698 /* flagspecial: 0x2 => banner, if available */
699 {
700   oputs("Content-Type: text/html; charset=");
701   oput(curcharset.s,curcharset.len);
702
703   oputs("\nCache-Control: ");
704   switch (cache) {
705     case 0:
706         oputs("no-cache");              /* known upper border */
707         break;
708     case 1:
709         oputs("max-age=300");           /* 5 min - most lists aren't that fast*/
710         break;
711     case 2:
712         oputs("max-age=1209600");       /* 14 days is a long time */
713         break;
714   }
715   oputs("\n\n");
716   oputs("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\">\n");
717   oputs("<HTML><HEAD>\n<TITLE>");
718   if (local) {
719     oputs(local);
720     oputs("@");
721     oputs(host);
722     oputs(": ");
723   }
724   if (t) oputs(t);
725   if (s) htmlencode_put(s,l);
726   oputs("</TITLE>\n");
727   if (class && *class && stylesheet && *stylesheet) {
728     oputs("<LINK href=\"");
729     oputs(stylesheet);
730     oputs("\" rel=\"stylesheet\" type=\"text/css\">\n");
731   }
732   if (!flagrobot)       /* robot access allowed to follow */
733     oputs("<META NAME=\"robots\" CONTENT=\"noindex\">\n");
734   if (flagrobot < 2)
735     oputs("<META NAME=\"robots\" CONTENT=\"nofollow\">\n");
736   if (flagspecial & SPC_BASE)
737     oput(base.s,base.len);
738   oputs("</HEAD>\n");
739   if (class && *class) {
740     oputs("<BODY class=");
741     oputs(class);
742     oputs(">\n");
743   } else
744     oputs("<BODY>\n");
745
746 }
747
748 void html_footer(int flagspecial)
749 {
750   oputs("<HR><DIV class=copyright>");
751   oputs(EZ_CRIGHT);
752   oputs("</DIV>");
753   if ((flagspecial & SPC_BANNER) && banner && *banner) {
754     oputs("<DIV class=banner>\n");
755     if (*banner == '<') oputs(banner);
756     else {
757       substdio_flush(&ssout);
758       sig_pipeignore();
759       bannerargs[0] = banner;
760       bannerargs[1] = host;
761       bannerargs[2] = local;
762       bannerargs[3] = 0;
763         /* We log errors but just complete the page anyway, since we're */
764         /* already committed to output something. */
765       switch(child = fork()) {
766         case -1:
767           strerr_warn3(FATAL,ERR_FORK,"banner program: ",&strerr_sys);
768           break;
769         case 0:
770           execv(*bannerargs,bannerargs);
771           strerr_die3x(100,FATAL,ERR_EXECUTE,"banner program: ");
772           break;
773       }
774          /* parent */
775       wait_pid(&wstat,child);
776       if (wait_crashed(wstat))
777         strerr_warn2(FATAL,ERR_CHILD_CRASHED,(struct strerr *) 0);
778       if (wait_exitcode(wstat))
779         strerr_warn2(FATAL,ERR_CHILD_UNKNOWN,(struct strerr *) 0);
780     }
781     oputs("</DIV>\n");
782   }
783   oputs("</BODY>\n</HTML>\n");
784   substdio_flush(&ssout);
785 }
786
787 /* DATE functions */
788
789 void datelink(struct msginfo *infop,unsigned long d,char direction)
790 /* output a date with link back to thread index */
791 {
792   oput(url.s,url.len);
793   cmdstr[0] = ITEM[ITEM_DATE];
794   cmdstr[1] = ITEM[ITEM_DATE];
795   cmdstr[2] = DIRECT[direction + 1];
796   oputs(cmdstr);
797   if (direction == DIRECT_LAST)
798     oput("0",1);        /* suppress msgnum to avoid going there */
799   else
800     oput(strnum,fmt_ulong(strnum,infop->target));
801   oputs(":");
802   oput(strnum,fmt_ulong(strnum,d));
803   oputs("#b\">");
804   switch (direction) {
805     case DIRECT_SAME:
806         if (dateline(&dtline,d) < 0) die_nomem();
807         oput(dtline.s,dtline.len);
808         break;
809     case DIRECT_PREV:
810         oputs("[&lt;-]");
811         break;
812     case DIRECT_NEXT:
813         oputs("[-&gt;]");
814         break;
815     case DIRECT_FIRST:
816         oputs("[&lt;&lt;-]");
817         break;
818     case DIRECT_LAST:
819         oputs("[-&gt;&gt;]");
820         break;
821   }
822   oputs("</A>");
823 }
824
825 void finddate(struct msginfo *infop)
826 /* DIRECT_SAME works as DIRECT_PREV, dvs returns previous date or last date */
827 {
828   DIR *archivedir;
829   direntry *d;
830   unsigned long ddate, startdate;
831   unsigned long below, above;
832
833   below = 0L;
834   above = MAXULONG;     /* creating a Y 0xffffff problem */
835   startdate = infop->date;
836   archivedir = opendir("archive/threads/");
837   if (!archivedir)
838     if (errno != error_noent)
839       strerr_die4sys(111,FATAL,ERR_OPEN,dir,"/archive/threads: ");
840     else
841       strerr_die4sys(100,FATAL,ERR_OPEN,dir,"/archive/threads: ");
842
843   while ((d = readdir(archivedir))) {           /* dxxx/ */
844     if (str_equal(d->d_name,".")) continue;
845     if (str_equal(d->d_name,"..")) continue;
846     scan_ulong(d->d_name,&ddate);
847     if (!ddate) continue;       /* just in case some smart guy ... */
848     if (startdate) {
849       if (ddate > startdate && ddate < above) above = ddate;
850       if (ddate < startdate && ddate > below) below = ddate;
851     } else {
852       if (ddate < above) above = ddate;
853       if (ddate > below) below = ddate;
854     }
855   }
856   closedir(archivedir);
857
858   if (infop->direction == DIRECT_NEXT && above != MAXULONG || !below)
859         /* we always give a valid date as long as there is at least one */
860     infop->date = above;
861   else
862     infop->date = below;
863   return;
864 }
865
866 void latestdate(struct msginfo *infop,int flagfail)
867 {
868   if (!flagfail) {
869     datetime_tai(&dt,now());
870     infop->date = ((unsigned long) dt.year + 1900) * 100 + dt.mon + 1;
871   } else {
872     infop->date = 0;
873     infop->direction = DIRECT_PREV;
874     finddate(infop);
875   }
876 }
877
878 void firstdate(struct msginfo *infop,int flagfail)
879 {
880     infop->date = 0;
881     infop->direction = DIRECT_NEXT;
882     finddate(infop);
883 }
884
885 void getdate(struct msginfo *infop,int flagfail)
886 /* infop->date has to be 0 or valid on entry. Month outside of [1-12] on */
887 /* entry causes GIGO */
888 {
889   if (!flagfail) {                              /* guess */
890     if (infop->direction == DIRECT_NEXT) {
891       infop->date++;
892       if (infop->date % 100 > 12) infop->date += (100 - 12);
893     } else if (infop->direction == DIRECT_PREV) {
894       infop->date--;
895       if (!infop->date % 100) infop->date -= (100 - 12);
896     }
897   } else
898     finddate(infop);
899   return;
900 }
901
902 indexlinks(struct msginfo *infop)
903 {
904   unsigned long tmpmsg;
905
906   tmpmsg = infop->target;
907   infop->target = 1;
908   oputs("<DIV class=idxlinks><STRONG>");
909   linktoindex(infop,ITEM_INDEX);
910   oputs(">[&lt;&lt;-]</A>\n");
911   if (tmpmsg >= 100) infop->target = tmpmsg - 100;
912   linktoindex(infop,ITEM_INDEX);
913   oputs(">[&lt;-]</A>\n");
914   infop->target = tmpmsg + 100;
915   linktoindex(infop,ITEM_INDEX);
916   oputs(">[-&gt;]</A>\n");
917   infop->target = MAXULONG;
918   linktoindex(infop,ITEM_INDEX);
919   oputs(">[-&gt;&gt;]</A> |\n");
920   infop->target = tmpmsg;
921   linktoindex(infop,ITEM_DATE);
922   oputs(">[Threads by date]</A>\n");
923   subfaqlinks();
924   homelink();
925   oputs("</STRONG></DIV>\n");
926 }
927
928 int show_index(struct msginfo *infop)
929 {
930   unsigned long thismsg;
931   unsigned int pos,l;
932   char ch;
933
934   (void) makefn(&fn,ITEM_INDEX,msginfo.target,"");
935   if ((fd = open_read(fn.s)) == -1)
936     if (errno == error_noent)
937       return 0;
938     else
939       strerr_die4sys(111,FATAL,ERR_OPEN,fn.s,": ");
940   substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
941   if (!stralloc_copyb(&line,strnum,
942         fmt_ulong(strnum,(unsigned long) (infop->target / 100))))
943                 die_nomem();
944   if (!stralloc_cats(&line,"xx")) die_nomem();
945   html_header("Messages ",line.s,line.len,"idxbody",SPC_BANNER | SPC_BASE);
946   indexlinks(infop);
947   oputs("<HR><H1 id=\"idxhdr\">");
948   oputs("Messages ");
949   oput(line.s,line.len);
950   oputs("</H1>\n");
951   oputs("<HR><DIV class=idx>\n");
952   for (;;) {
953     if (getln(&ssin,&line,&match,'\n') == -1)
954       strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
955     if (!match)
956       break;
957     pos = scan_ulong(line.s,&thismsg);
958     l = pos;
959     ch = line.s[pos++];
960     pos++;
961     if (line.len < pos + 1 + HASHLEN)
962        strerr_die2x(100,FATAL,"index line with truncated subject entry");
963     if (!stralloc_copyb(&subject,line.s+pos,HASHLEN)) die_nomem();
964     if (!stralloc_0(&subject)) die_nomem();
965     infop->axis = ITEM_SUBJECT;
966     infop->subject = subject.s;
967     oput(strnum,fmt_uint0(strnum,(unsigned int) thismsg % 100,2));
968     oputs(": ");
969     link(infop,ITEM_MESSAGE,ITEM_SUBJECT,thismsg,line.s+pos,line.len - pos - 1);
970     oputs("\n");
971     if (ch == ':') {
972       if (getln(&ssin,&line,&match,'\n') == -1)
973         strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
974       if (!match)
975         break;
976       pos = byte_chr(line.s,line.len,';');
977       if (pos != line.len) {
978         infop->date = date2yyyymm(line.s);
979         oputs("(");
980         link(infop,ITEM_AUTHOR,ITEM_AUTHOR,thismsg,line.s+pos+1,
981                 line.len - pos - 2);
982         oputs(")<BR>\n");
983       }
984     }
985   }
986   close(fd);
987   oputs("\n</DIV><HR>\n");
988   indexlinks(infop);
989   html_footer(SPC_BANNER);
990   return 1;
991 }
992
993 void objectlinks(struct msginfo *infop, char item)
994 {
995   oputs("<DIV class=objlinks><STRONG>\n");
996   if (item == ITEM_DATE) {
997     datelink(infop,0,DIRECT_FIRST);
998     datelink(infop,infop->date,DIRECT_PREV);
999     datelink(infop,infop->date,DIRECT_NEXT);
1000     datelink(infop,0,DIRECT_LAST);
1001     oputs("\n");
1002   } else {
1003     if (!infop->target) infop->axis = ITEM_DATE;
1004     linktoindex(infop,ITEM_DATE);
1005     oputs(">[Threads by date]</A>\n");
1006   }
1007   if (item != ITEM_INDEX) {
1008     linktoindex(infop,ITEM_INDEX);
1009     oputs(">[Messages by date]</A>\n");
1010   }
1011   homelink();
1012   subfaqlinks();
1013   oputs("</STRONG></DIV>\n");
1014 }
1015
1016 int show_object(struct msginfo *infop,char item)
1017 /* shows thread, threads, author */
1018 /* infop has the info needed to access the author/subject/thread file */
1019 {
1020   unsigned long lastdate,thisdate,thismsg;
1021   char linkitem;
1022   char targetitem;
1023   unsigned int pos;
1024
1025   lastdate = 0L;
1026   targetitem = ITEM_MESSAGE;                    /* default message is target */
1027   switch (item) {
1028     case ITEM_SUBJECT:
1029         if (!makefn(&fn,ITEM_SUBJECT,0L,infop->subject)) return 0;
1030         break;
1031     case ITEM_AUTHOR:
1032         if (!makefn(&fn,ITEM_AUTHOR,0L,infop->author)) return 0;
1033         break;
1034     case ITEM_DATE:
1035         if (!makefn(&fn,ITEM_DATE,infop->date,"")) return 0;
1036         break;
1037     default:
1038         die_prog("Bad object type in show_object");
1039   }
1040 if ((fd = open_read(fn.s)) == -1)
1041     if (errno == error_noent)
1042       return 0;
1043     else
1044       strerr_die4sys(111,FATAL,ERR_OPEN,fn.s,": ");
1045   substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
1046   if (item != ITEM_DATE) {
1047     if (getln(&ssin,&line,&match,'\n') == -1)   /* read subject */
1048       strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
1049     if (!match || line.len < HASHLEN + 2)
1050       strerr_die4x(111,FATAL,ERR_READ,fn.s,": nothing there");
1051   }
1052   switch (item) {
1053     case ITEM_SUBJECT:
1054         html_header("Thread on: ",line.s + HASHLEN + 1,
1055                 line.len - HASHLEN - 2,"subjbody",SPC_BANNER | SPC_BASE);
1056         objectlinks(infop,item);
1057         oputs("<HR><H1>On: ");
1058         oput(line.s+HASHLEN+1,line.len-HASHLEN-2);
1059         oputs("</H1>\n");
1060         break;
1061     case ITEM_AUTHOR:
1062         html_header("Posts by: ",line.s + HASHLEN + 1,
1063                 line.len - HASHLEN - 2,"authbody",SPC_BANNER | SPC_BASE);
1064         objectlinks(infop,item);
1065         oputs("<HR><H1>By: ");
1066         oput(line.s+HASHLEN+1,line.len-HASHLEN-2);
1067         oputs("</H1>\n");
1068         break;
1069     case ITEM_DATE:
1070 /*      targetitem = ITEM_SUBJECT;*/    /* thread index is target */
1071         thisdate = infop->date;
1072         if (dateline(&dtline,infop->date) < 0) die_nomem();
1073         html_header("Threads for ",
1074                 dtline.s,dtline.len,"threadsbody",SPC_BANNER | SPC_BASE);
1075         objectlinks(infop,item);
1076         oputs("<HR><H1>Threads for ");
1077         oput(dtline.s,dtline.len);
1078         oputs("</H1>\n");
1079         break;
1080     default: die_prog("unrecognized object type in show_object");
1081   }
1082
1083   oputs("<DIV class=obj>\n");
1084   for (;;) {
1085     if (getln(&ssin,&line,&match,'\n') == -1)   /* read subject */
1086       strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
1087     if (!match)
1088       break;
1089     pos = scan_ulong(line.s,&thismsg);
1090     if (line.s[pos++] != ':')
1091         strerr_die4x(100,FATAL,"entry in ",fn.s," lacks message number");
1092     if (item != ITEM_DATE) {            /* no date for threads by date */
1093       pos += scan_ulong(line.s+pos,&thisdate);
1094       infop->date = thisdate;
1095       if (line.s[pos++] != ':')
1096         strerr_die4x(100,FATAL,"entry in ",fn.s," lacks date");
1097     }
1098     if (line.len < pos + HASHLEN + 2)
1099         strerr_die4x(100,FATAL,"entry in ",fn.s," lacks hash");
1100     if (thisdate != lastdate) {
1101       if (!lastdate)
1102         oputs("<UL>\n");
1103       else
1104         oputs("<P>");
1105       oputs("<LI><H2>");
1106       datelink(infop,thisdate,DIRECT_SAME);
1107       lastdate = thisdate;
1108       oputs("</H2>\n");
1109     }
1110     if (item == ITEM_SUBJECT)
1111       linkitem = ITEM_AUTHOR;
1112     else
1113       linkitem = ITEM_SUBJECT;
1114     link(infop,targetitem,linkitem,thismsg,line.s+pos,line.len - pos - 1);
1115     oputs("<BR>\n");
1116   }
1117   close(fd);
1118   oputs("</UL>\n");
1119   if (!infop->target)
1120     oputs("<a name=b></a>");
1121   oputs("<HR></DIV>\n");
1122   objectlinks(infop,item);
1123   html_footer(SPC_BANNER);
1124   return 1;
1125 }
1126
1127 void clear_mime()
1128 {
1129   mime_current->charset.len = 0;        /* exist but need emptying */
1130   mime_current->boundary.len = 0;
1131   mime_current->ctype.len = 0;
1132   mime_current->mimetype = MIME_NONE;
1133   mime_current->ctenc = CTENC_NONE;
1134   mime_current->cs = CS_NONE;
1135 }
1136
1137 void new_mime()
1138 {
1139     mime_tmp = mime_current;
1140     if (mime_current)
1141       mime_current = mime_current->next;
1142     if (!mime_current) {
1143       if (!(mime_current = (mime_info *) alloc(sizeof (mime_info))))
1144         die_nomem();
1145       mime_current->charset = sainit;           /* init */
1146       mime_current->boundary = sainit;
1147       mime_current->ctype = sainit;
1148       mime_current->next = (mime_info *) 0;
1149       mime_current->previous = mime_tmp;
1150     }
1151     clear_mime();
1152     if (mime_tmp)
1153       mime_current->level = mime_tmp->level + 1;
1154     else
1155       mime_current->level = 1;
1156 }
1157
1158 void mime_getarg(stralloc *sa,char **s, unsigned int *l)
1159 /* copies next token or "token" into sa and sets s & l appropriately */
1160 /* for continuing the search */
1161 {
1162   char *cp, *cpafter, *cpnext;
1163
1164       if (!*l || !**s) return;
1165       if (**s == '"') {
1166         (*s)++; (*l)--;
1167         cp = *s; cpnext = cp + *l; cpafter = cpnext;
1168         while (cp < cpafter) {
1169           if (*cp == '"') {
1170             break;
1171           }
1172           cp++;
1173         }
1174         cpnext = cp;
1175       } else {
1176         cp = *s; cpnext = cp + *l; cpafter = cpnext;
1177         while (cp < cpafter) {
1178           if (*cp == ' ' || *cp == '\t' || *cp == '\n' || *cp == ';') {
1179             break;
1180           }
1181           cp++;
1182         }
1183         cpnext = cp;
1184       }
1185       if (!stralloc_copyb(sa,*s,cp - *s)) die_nomem();
1186       *l = cpafter - cpnext;            /* always >= 0 */
1187       *s = cpnext;
1188       return;
1189 }
1190
1191 void decode_mime_type(char *s,unsigned int l,unsigned int flagmime)
1192 {
1193   char *st;
1194   unsigned int r,lt;
1195   if (!flagmime || !l) {                /* treat non-MIME as plain text */
1196     mime_current->mimetype = MIME_TEXT_PLAIN;
1197     if (!stralloc_copys(&curcharset,charset)) die_nomem();
1198         /* should be us-ascii, but this is very likely better */
1199     return;
1200   }
1201   r = MIME_APPLICATION_OCTETSTREAM;
1202   while (l && (*s == ' ' || *s == '\t')) { s++; l--; }  /* skip LWSP */
1203   mime_getarg(&(mime_current->ctype),&s,&l);
1204   st = mime_current->ctype.s;
1205   lt = mime_current->ctype.len;
1206   if (case_startb(st,lt,"text")) {                      /* text types */
1207     r = MIME_TEXT; st+= 4; lt-= 4;
1208     if (case_startb(st,lt,"/plain")) {
1209       r = MIME_TEXT_PLAIN; st+= 6; lt-= 6;
1210     } else if (case_startb(st,lt,"/html")) {
1211       r = MIME_TEXT_HTML; st+= 5; lt-= 5;
1212     } else if (case_startb(st,lt,"/enriched")) {
1213       r = MIME_TEXT_ENRICHED; st+= 9; lt-= 9;
1214     } else if (case_startb(st,lt,"/x-vcard")) {
1215       r = MIME_TEXT_ENRICHED; st+= 8; lt-= 8;
1216     }
1217   } else if (case_startb(st,lt,"multipart")) {          /* multipart types */
1218     r = MIME_MULTI; st += 9; lt-= 9;
1219     if (case_startb(st,lt,"/alternative")) {
1220       r = MIME_MULTI_ALTERNATIVE; st+= 12; lt-= 12;
1221     } else if (case_startb(st,lt,"/mixed")) {
1222       r = MIME_MULTI_MIXED; st+= 6; lt-= 6;
1223     } else if (case_startb(st,lt,"/digest")) {
1224       r = MIME_MULTI_DIGEST; st+= 7; lt-= 7;
1225     } else if (case_startb(st,lt,"/signed")) {
1226       r = MIME_MULTI_SIGNED; st+= 7; lt-= 7;
1227     }
1228   } else if (case_startb(st,lt,"message")) {            /* message types */
1229     r = MIME_MESSAGE; st += 7; lt -= 7;
1230     if (case_startb(st,lt,"/rfc822")) {
1231       r = MIME_MESSAGE_RFC822; st+= 7; lt-= 7;
1232     }
1233   }
1234   mime_current->mimetype = r;
1235   while (l) {
1236     while (l && (*s == ' ' || *s == '\t' || *s == ';' || *s == '\n')) {
1237          s++; l--; }                                    /* skip ;LWSP */
1238     if (case_startb(s,l,"boundary=")) {
1239       s += 9; l-= 9;
1240       mime_getarg(&(mime_current->boundary),&s,&l);
1241     } else if (case_startb(s,l,"charset=")) {
1242       s += 8; l-= 8;
1243       mime_getarg(&(mime_current->charset),&s,&l);
1244       cs = decode_charset(mime_current->charset.s,
1245                 mime_current->charset.len);
1246       if (cs == CS_BAD) cs = csbase;                    /* keep base cs */
1247       else
1248         if (!stralloc_copy(&curcharset,&mime_current->charset)) die_nomem();
1249     } else {                                            /* skip non LWSP */
1250       for (;;) {
1251         if (!l) break;
1252         if (*s == '"') {
1253           s++, l--;
1254           while (l && *s != '"') { s++, l--; }
1255           if (l) { s++, l--; }
1256           break;
1257         } else {
1258           if (!l || *s == ' ' || *s == '\t' || *s == '\n') break;
1259           s++; l--;
1260         }
1261       }
1262     }
1263   }
1264   return;
1265 }
1266
1267 void decode_transfer_encoding(register char *s,register unsigned int l)
1268 {
1269   unsigned int r;
1270   mime_current->ctenc = CTENC_NONE;
1271   if (!l || (mime_current->mimetype & MIME_MULTI)) return;
1272                         /* base64/QP ignored for multipart */
1273   r = CTENC_NONE;
1274   while (l && (*s == ' ' || *s == '\t')) { s++; l--; }  /* skip LWSP */
1275   if (case_startb(s,l,"quoted-printable")) {
1276     r = CTENC_QP;
1277   } else if (case_startb(s,l,"base64")) {
1278     r = CTENC_BASE64;
1279   }
1280   mime_current->ctenc = r;
1281   return;
1282 }
1283
1284 int check_boundary()
1285 /* return 0 if no boundary, 1 if start, 2 if end */
1286 {
1287   mime_info *tmp;
1288
1289   if (*line.s != '-' || line.s[1] != '-') return 0;
1290   tmp = mime_current;
1291   while (tmp) {
1292     if (tmp->boundary.len) {
1293     if (line.len > tmp->boundary.len + 2 &&
1294         !case_diffb(line.s+2,tmp->boundary.len,tmp->boundary.s)) {
1295       if (line.s[tmp->boundary.len + 2] == '-' &&
1296                 line.s[tmp->boundary.len + 3] == '-') { /* end */
1297         mime_current = tmp;
1298         clear_mime();
1299         return 2;
1300
1301       } else {                                          /* start */
1302         mime_current = tmp;
1303         new_mime();
1304         return 1;
1305       }
1306     }
1307     }
1308     tmp = tmp->previous;
1309   }
1310   if (!stralloc_copys(&curcharset,charset)) die_nomem();
1311                         /* suprtfluous since header done by now */
1312   cs = csbase;
1313   return 0;
1314 }
1315
1316 void start_message_page(struct msginfo *infop)
1317 /* header etc for message. Delayed to collect subject so that we can put */
1318 /* that in TITLE. This in turn needed for good looking robot index.      */
1319 /* Yep, not pretty, but it works and it's abhorrent to seek()/rewind     */
1320 /* and another hack: it's hard to mix charsets within a doc. So, we disp */
1321 /* messages entirely in the charset of the message. This is ok, since    */
1322 /* headers will be us-ascii or have encoded segments usually matching    */
1323 /* the charset in the message. Of course, we should be able to used e.g. */
1324 /* <DIV charset=iso-2022-jp> with internal resources as well as internal */
1325 /* ones. One might make other-charset messages external resources as well*/
1326 /* Now, the problem is that we need to "preview" MIME info _before_      */
1327 /* seeing the start boundary. */
1328 {
1329   if (!stralloc_copyb(&decline,strnum,fmt_ulong(strnum,infop->target)))
1330         die_nomem();
1331   if (!stralloc_cats(&decline,":")) die_nomem();
1332   if (!stralloc_0(&decline)) die_nomem();
1333   decodeHDR(hdr[HDR_SUBJECT - 1].s,hdr[HDR_SUBJECT - 1].len,&line,"",FATAL);
1334   if (!mime_current)
1335     new_mime();                 /* allocate */
1336   else
1337     clear_mime();
1338   decode_mime_type(hdr[HDR_CT - 1].s,hdr[HDR_CT - 1].len,
1339         hdr[HDR_VERSION - 1].len);
1340   html_header(decline.s,line.s,line.len - 1,
1341                 "msgbody",SPC_BASE);
1342   msglinks(infop);
1343   oputs("<DIV class=message>\n");
1344 }
1345
1346 void show_part(struct msginfo *infop,int flagshowheaders,
1347         int flagskip,int flagstartseen)
1348 /* if flagshowheaders we display headers, otherwise not */
1349 /* if flagstartseen we've already see the start boundary for this part, */
1350 /* if not we'll ignore what's there up to it */
1351 /* if flagskip we skip this part */
1352 {
1353   char *cp;
1354   int flaginheader;
1355   int whatheader;
1356   int flaggoodfield;
1357   int flaghtml;
1358   int btype,i;
1359   unsigned int colpos,l,pos;
1360   char linetype;
1361
1362   flaginheader = 1;
1363   for (i = 0; i < NO_HDRS; i++) hdr[i].len = 0;
1364   flaggoodfield = 1;
1365   match = 1;
1366   recursion_level++;                    /* one up */
1367   for (;;) {
1368     if (!match) return;
1369     if (getln(&ssin,&line,&match,'\n') == -1)
1370       strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
1371     if (!match) return;
1372     if ((btype = check_boundary())) {
1373       if (flagpre) {
1374         oputs("</PRE>");
1375         toggle_flagpre(0);
1376       }
1377       if (mime_current->level < recursion_level) {
1378         return;
1379       }
1380       if (btype == 1) {
1381         flagstartseen = 1;
1382         flaggoodfield = 1;
1383         flaginheader = 1;
1384       } else
1385         flagstartseen = 0;
1386       continue;
1387     }
1388     if (!flagstartseen) continue;       /* skip to start */
1389     if (flaginheader) {
1390       if (line.len == 1) {
1391         if (flagshowheaders) {          /* rfc822hdr only */
1392           if (flagtoplevel)
1393             start_message_page(infop);  /* so we can put subj in TITLE */
1394           oputs("<DIV class=rfc822hdr><HR>\n");
1395           for (i = 0; i < NO_HDRS; i++) {
1396             if (!hdr[i].len || !headers_shown[i]) continue;
1397             if (i == HDR_SUBJECT - 1 && flagtoplevel)
1398               oputs("<SPAN class=subject>");
1399             oputs("<EM>");
1400             oputs(constmap_get(&headermap,i + 1));
1401             oputs(":</EM>");
1402             decodeHDR(hdr[i].s,hdr[i].len,&line,"",FATAL);
1403             if (i == HDR_SUBJECT - 1 && flagtoplevel) {
1404               oputs("<A class=relk href=\"mailto:");
1405               oputs(local);
1406               oput("@",1);
1407               oputs(host);
1408               oputs("?subject=");
1409               urlencode_put(line.s + 1,line.len - 2);
1410               oputs("\">");
1411             }
1412             if (flagobscure && i == HDR_FROM - 1) {
1413               oputs(" ");
1414               decodeHDR(cp,author_name(&cp,line.s,line.len),&decline,"",FATAL);
1415               htmlencode_put(decline.s,decline.len);
1416             } else {
1417               decodeHDR(hdr[i].s,hdr[i].len,&decline,"",FATAL);
1418               htmlencode_put(decline.s,decline.len - 1);
1419             }
1420             if (i == HDR_SUBJECT - 1 && flagtoplevel)
1421               oputs("</A></SPAN>");
1422             oputs("\n<BR>");
1423           }
1424           oputs("</DIV>\n");
1425         }
1426         flaginheader = 0;
1427         flagtoplevel = 0;
1428         flaggoodfield = 1;
1429         flaghtml = 0;
1430         if (!flagmime)
1431           flagmime = hdr[HDR_VERSION - 1].len;  /* MIME-Version header */
1432         decode_mime_type(hdr[HDR_CT - 1].s,hdr[HDR_CT - 1].len,flagmime);
1433         decode_transfer_encoding(hdr[HDR_CTENC - 1].s,hdr[HDR_CTENC - 1].len);
1434         content.len = 0; encoding.len = 0;
1435         switch (mime_current->mimetype) {
1436           case MIME_MULTI_SIGNED:
1437           case MIME_MULTI_MIXED:
1438           case MIME_MULTI_ALTERNATIVE:
1439           case MIME_MULTI_DIGEST:
1440                 show_part(infop,0,0,0);
1441                 recursion_level--;
1442                 flagstartseen = 0;
1443                 flaginheader = 1;
1444                 continue;
1445           case MIME_MESSAGE_RFC822:
1446                 oputs("\n<PRE>");
1447                 toggle_flagpre(1);
1448                 flagshowheaders = 1;
1449                 flaginheader = 1;
1450                 flagmime = 0;           /* need new MIME-Version header */
1451                 continue;
1452           case MIME_TEXT_HTML:
1453                 if (flagshowhtml) {
1454                   oputs("<HR>\n");
1455                   flaghtml = 1;
1456                 } else {
1457                   oputs("<strong>[\"");
1458                   oput(mime_current->ctype.s,mime_current->ctype.len);
1459                   oputs("\" not shown]</strong>\n");
1460                   flaggoodfield = 0;    /* hide */
1461                 }
1462                 continue;
1463           case MIME_TEXT_PLAIN:
1464           case MIME_TEXT:               /* in honor of Phil using "text" on */
1465           case MIME_NONE:               /* the qmail list and rfc2045:5.2 */
1466                 oputs("<HR>\n<PRE>\n");
1467                 toggle_flagpre(1);
1468                 continue;
1469           case MIME_TEXT_VCARD:
1470           default:              /* application/octetstream...*/
1471                 oputs("<HR><strong>[\"");
1472                 oput(mime_current->ctype.s,mime_current->ctype.len);
1473                 oputs("\" not shown]</strong>\n");
1474                 flaggoodfield = 0;      /* hide */
1475                 continue;
1476         }
1477       } else if (line.s[0] != ' ' && line.s[0] != '\t') {
1478         linetype = ' ';
1479         flaggoodfield = 0;
1480         colpos = byte_chr(line.s,line.len,':');
1481         if ((whatheader = constmap_index(&headermap,line.s,colpos))) {
1482           flaggoodfield = 1;
1483           if (!stralloc_copyb(&hdr[whatheader - 1],line.s + colpos + 1,
1484                 line.len - colpos - 1)) die_nomem();
1485         }
1486       } else {
1487         if (whatheader)
1488           if (!stralloc_catb(&hdr[whatheader - 1],line.s,line.len))
1489                 die_nomem();
1490       }
1491     } else {
1492       if (flaggoodfield) {
1493         if (mime_current->ctenc) {
1494           if (!stralloc_copy(&decline,&line)) die_nomem();
1495           line.len = 0;
1496           if (mime_current->ctenc == CTENC_QP)
1497             decodeQ(decline.s,decline.len,&line);
1498           else
1499             decodeB(decline.s,decline.len,&line);
1500         }
1501         if (flaghtml)
1502           oput(line.s,line.len);
1503         else {
1504           htmlencode_put(line.s,line.len);              /* body */
1505         }
1506       }
1507     }
1508   }
1509 }
1510
1511 int show_message(struct msginfo *infop)
1512 {
1513   char *psz;
1514
1515   if(!stralloc_copys(&headers,(char *) headers_used)) die_nomem();
1516   if (!stralloc_0(&headers)) die_nomem();
1517   psz = headers.s;
1518   while (*psz) {
1519     if (*psz == '\\') *psz = '\0';
1520     ++psz;
1521   }
1522   if (!constmap_init(&headermap,headers.s,headers.len,0))
1523         die_nomem();
1524
1525   (void) makefn(&fn,ITEM_MESSAGE,msginfo.target,"");
1526   if ((fd = open_read(fn.s)) == -1)
1527     if (errno == error_noent)
1528       return 0;
1529     else
1530       strerr_die4sys(111,FATAL,ERR_OPEN,fn.s,": ");
1531   substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
1532   toggle_flagpre(0);
1533   recursion_level = 0;  /* recursion level for show_part */
1534   flagmime = 0;         /* no active mime */
1535   flagtoplevel = 1;     /* top message/rfc822 get special rx */
1536   new_mime();           /* initiate a MIME info storage slot */
1537
1538   show_part(infop,1,0,1);       /* do real work, including html header etc */
1539   if (flagpre)
1540     oputs("</PRE>\n");
1541   close(fd);
1542   oputs("<HR></DIV>\n");
1543   msglinks(infop);
1544   html_footer(0);
1545   return 1;
1546 }
1547
1548 char decode_item(char ch)
1549 {
1550   switch (ch) {
1551         case 'm': return ITEM_MESSAGE;
1552         case 'a': return ITEM_AUTHOR ;
1553         case 's': return ITEM_SUBJECT;
1554         case 'd': return ITEM_DATE   ;
1555         case 'i': return ITEM_INDEX  ;
1556         default: cgierr("Navigation command contains ",
1557                 "illegal item code","");
1558   }
1559   return 0;     /* never reached */
1560 }
1561
1562 char decode_direction(char ch)
1563 {
1564   switch (ch) {
1565         case 's': return DIRECT_SAME;
1566         case 'n': return DIRECT_NEXT;
1567         case 'p': return DIRECT_PREV;
1568         default: cgierr("Navigation command contains ",
1569                 "illegal direction code","");
1570   }
1571   return 0;     /* never reached */
1572 }
1573
1574 int decode_cmd(char *s,struct msginfo *infop)
1575 /* decodes s into infop. Assures that no security problems slip through by */
1576 /* checking everything */
1577 /* commands xyd:123[:abc]. x what we want, y is the axis, d the direction. */
1578 /* 123 is the current message number. abc is a date/subject/author hash,   */
1579 /* depending on axis, or empty if not available. */
1580 /* returns: 0 no command+msgnum. */
1581 /*          1 empty or at least cmd + msgnum. */
1582 /*            Guarantee: Only legal values accepted */
1583 {
1584   register char ch;
1585
1586   infop->source = 0L;
1587   infop->date = 0L;
1588   infop->author = (char *)0;
1589   infop->subject = (char *)0;
1590   infop->cgiarg = (char *)0;
1591
1592   if (!s || !*s) {      /* main index */
1593     infop->item = ITEM_DATE;
1594     infop->axis = ITEM_DATE;
1595     infop->direction = DIRECT_SAME;
1596     latestdate(&msginfo,0);
1597     infop->target = MAXULONG;
1598     return 1;
1599   }
1600   ch = *(s++);
1601   if (ch >= '0' && ch <= '9') { /* numeric - simplified cmd: msgnum ... */
1602     s--;
1603     infop->item = ITEM_MESSAGE;
1604     infop->axis = ITEM_MESSAGE;
1605     infop->direction = DIRECT_SAME;
1606   } else {                      /* what:axis:direction:msgnum ... */
1607     infop->item = decode_item(ch);
1608     ch = *(s++);
1609     infop->axis = decode_item(ch);
1610     ch = *(s++);
1611     infop->direction = decode_direction(ch);
1612     if (*(s++) != ':') return 0;
1613   }
1614   s+= scan_ulong(s,&(infop->source));
1615   if (*(s++) != ':') return 0;
1616   if (*s >= '0' && *s <= '9') { /* numeric nav hint [date] */
1617     s+= scan_ulong(s,&(infop->date));
1618     if (!*s++) return 1;        /* skip any char - should be ':' unless NUL */
1619   }
1620   if (checkhash(s)) {           /* Ignore if illegal rather than complaining*/
1621     if (!stralloc_copyb(&charg,s,HASHLEN)) die_nomem();
1622     if (!stralloc_0(&charg)) die_nomem();
1623     infop->cgiarg = charg.s;
1624   }
1625   return 1;
1626 }
1627
1628 int msg2hash(struct msginfo *infop)
1629 {
1630   unsigned int pos;
1631   unsigned long tmpmsg;
1632
1633   if (!infop->source) die_prog("source is 0 in msg2hash");
1634   (void) makefn(&fn,ITEM_INDEX,infop->source,"");
1635   if ((fd = open_read(fn.s)) == -1) {
1636     if (errno == error_noent)
1637       return 0;
1638     else
1639       strerr_die4sys(111,FATAL,ERR_OPEN,fn.s,": ");
1640   }
1641   substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
1642   for (;;) {
1643         if (getln(&ssin,&line,&match,'\n') == -1)
1644           strerr_die3sys(111,FATAL,ERR_READ,"index: ");
1645         if (!match)
1646           return 0;                             /* didn't find message */
1647         if (*line.s == '\t') continue;          /* author line */
1648         pos = scan_ulong(line.s,&tmpmsg);
1649         if (tmpmsg == infop->source) {
1650           if (line.s[pos++] != ':' || line.s[pos++] != ' ')
1651             strerr_die3x(100,ERR_SYNTAX,fn.s,": missing subject separator");
1652           if (line.len < HASHLEN + pos)
1653             strerr_die3x(100,ERR_SYNTAX,fn.s,": missing subject hash");
1654           if (!stralloc_copyb(&subject,line.s+pos,HASHLEN)) die_nomem();
1655           if (!stralloc_0(&subject)) die_nomem();
1656           infop->subject = subject.s;
1657           if (getln(&ssin,&line,&match,'\n') == -1)
1658             strerr_die3sys(111,FATAL,ERR_READ,"index: ");
1659           if (!match)
1660             strerr_die3x(100,ERR_SYNTAX,fn.s,
1661                 ": author info missing. Truncated?");
1662           pos = byte_chr(line.s,line.len,';');
1663           if (pos == line.len)
1664             strerr_die3x(100,ERR_SYNTAX,fn.s,"missing ';' after date");
1665           if (pos > 1)
1666             infop->date = date2yyyymm(line.s+1);        /* ';' marks end ok */
1667           pos++;
1668           if (line.len < HASHLEN + pos)
1669             strerr_die3x(100,ERR_SYNTAX,fn.s,": missing author hash");
1670           if (!stralloc_copyb(&author,line.s+pos,HASHLEN)) die_nomem();
1671           if (!stralloc_0(&author)) die_nomem();
1672           infop->author = author.s;
1673           close(fd);
1674           return 1;     /* success */
1675         }
1676   }
1677   close(fd);
1678   return 0;             /* failed to match */
1679 }
1680
1681 void setmsg(struct msginfo *infop)
1682 /* Reads the file corresponding to infop->axis and assumes fn.s is set */
1683 /* correctly for this. Sets up a msgnav structure and links it in      */
1684 /* correction for axis=author/subject. For axis=date it supports also  */
1685 /* direction=DIRECT_FIRST which will return the first message of the   */
1686 /* first thread in the date file. DIRECT_LAST is not supported.        */
1687 /* DIRECT_FIRST is supported ONLY for date. */
1688 {
1689   if (infop->direction == DIRECT_SAME) {
1690     infop->target = infop->source;
1691     return;
1692   }
1693   if ((fd = open_read(fn.s)) == -1) {
1694     if (errno == error_noent)
1695       strerr_die4x(100,FATAL,ERR_OPEN,fn.s,
1696         " in listmsgs. Rerun ezmlm-archive!");
1697     else
1698       strerr_die4sys(111,FATAL,ERR_OPEN,fn.s,": ");
1699   }
1700   substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
1701   if (infop->source != ITEM_DATE) {
1702     if (getln(&ssin,&line,&match,'\n') == -1)   /* first line */
1703       strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
1704     if (!match)
1705       strerr_die3x(100,ERR_SYNTAX,fn.s,": first line missing");
1706   }
1707   msgnav[3] = 0L;               /* next */
1708   msgnav[4] = 0L;               /* after */
1709   infop->target = 0L;
1710   for (;;) {
1711     if (getln(&ssin,&line,&match,'\n') == -1)
1712       strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
1713     if (!match) break;
1714     msgnav[0] = msgnav[1];
1715     msgnav[1] = msgnav[2];
1716     (void) scan_ulong(line.s,&(msgnav[2]));
1717     if (infop->direction == DIRECT_FIRST) break;
1718     if (msgnav[2] == infop->source) {
1719       if (getln(&ssin,&line,&match,'\n') == -1)
1720         strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
1721       if (!match) break;
1722       (void) scan_ulong(line.s,&(msgnav[3]));
1723       if (getln(&ssin,&line,&match,'\n') == -1)
1724       strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
1725       if (!match) break;
1726       (void) scan_ulong(line.s,&(msgnav[4]));
1727       break;
1728     }
1729   }
1730   close(fd);
1731   switch (infop->axis) {
1732     case ITEM_AUTHOR:
1733       infop->authnav = msgnav + 2 + infop->direction;
1734       infop->target = *(infop->authnav);
1735       infop->subject = (char *)0;       /* what we know is not for this msg */
1736       infop->date = 0;
1737       break;
1738     case ITEM_SUBJECT:
1739       infop->subjnav = msgnav + 2 + infop->direction;
1740       infop->target = *(infop->subjnav);
1741       infop->author = (char *)0;        /* what we know is not for this msg */
1742       infop->date = 0;
1743       break;
1744     case ITEM_DATE:
1745       infop->target = msgnav[2];
1746       infop->subject = (char *)0;       /* what we know is not for this msg */
1747       infop->author = (char *)0;        /* what we know is not for this msg */
1748     default:
1749       die_prog("Bad item in setmsg");
1750   }
1751   return;
1752 }
1753
1754 void auth2msg(struct msginfo *infop)
1755 {
1756   if (!infop->author) die_prog("no such author in authmsg");
1757   if (!makefn(&fn,ITEM_AUTHOR,0L,infop->author)) die_prog("auth2msg");
1758   setmsg(infop);
1759 }
1760
1761 void subj2msg(struct msginfo *infop)
1762 {
1763   if (!infop->subject) die_prog("no such subject in subj2msg");
1764   if (!makefn(&fn,ITEM_SUBJECT,0L,infop->subject)) die_prog("subj2msg");
1765   setmsg(infop);
1766 }
1767
1768 void date2msg(struct msginfo *infop)
1769 {
1770   (void) makefn(&fn,ITEM_DATE,infop->date,"");
1771   setmsg(infop);
1772 }
1773
1774 void findlastmsg(struct msginfo *infop)
1775 {
1776   if (!getconf_line(&line,"num",dir,0,FATAL))
1777     cgierr("Sorry, there are no messages in the archive","","");
1778   if (!stralloc_0(&line)) die_nomem();
1779   (void) scan_ulong(line.s,&(infop->target));
1780 }
1781
1782 int do_cmd(struct msginfo *infop)
1783 /* interprets msginfo to create msginfo. Upon return, msginfo can be trusted */
1784 /* to have all info needed, and that all info is correct. There may be more */
1785 /* info than needed. This can be used to build more specific links. NOTE:   */
1786 /* there is no guarantee that a message meeting the criteria actually exists*/
1787 {
1788   infop->target = infop->source;
1789
1790   switch (infop->item) {
1791         case ITEM_MESSAGE:      /* we want to get a message back */
1792           {
1793             switch (infop->axis) {
1794               case ITEM_MESSAGE:
1795                 if (infop->direction == DIRECT_SAME)
1796                   break;
1797                 else if (infop->direction == DIRECT_NEXT)
1798                   (infop->target)++;
1799                 else {          /* previous */
1800                   cache = 2;
1801                   if (infop->target >= 2)
1802                     (infop->target)--;
1803                   else
1804                     infop->target = 1;
1805                 }
1806                 break;
1807               case ITEM_AUTHOR:
1808                   infop->author = infop->cgiarg;
1809                 if (!infop->author)      /* we don't know author hash */
1810                   if (!msg2hash(infop)) return 0;
1811                 auth2msg(infop);
1812                 break;
1813               case ITEM_SUBJECT:
1814                   infop->subject = infop->cgiarg;
1815                 if (!infop->subject)     /* we don't know Subject hash */
1816                   if (!msg2hash(infop)) return 0;
1817                 subj2msg(infop);
1818                 break;
1819             }
1820             break;
1821           }
1822         case ITEM_AUTHOR:
1823           switch (infop->axis) {
1824             case ITEM_MESSAGE:
1825               if (!infop->author)
1826                 if (!msg2hash(infop)) return 0;
1827               break;
1828             case ITEM_AUTHOR:
1829               infop->author = infop->cgiarg;
1830               if (!infop->author)
1831                 if (!msg2hash(infop)) return 0;
1832                 auth2msg(infop);
1833               break;
1834             case ITEM_SUBJECT:
1835               infop->subject = infop->cgiarg;
1836               if (!infop->subject)       /* we don't know Subject hash */
1837                 if (!msg2hash(infop)) return 0;
1838               subj2msg(infop);
1839               break;
1840             }
1841             break;
1842         case ITEM_SUBJECT:
1843           switch (infop->axis) {
1844             case ITEM_MESSAGE:
1845               if (!msg2hash(infop)) return 0;
1846               break;
1847             case ITEM_AUTHOR:
1848               infop->author = infop->cgiarg;
1849               if (!infop->author)
1850                 if (!msg2hash(infop)) return 0;
1851               auth2msg(infop);
1852               break;
1853             case ITEM_SUBJECT:
1854               infop->subject = infop->cgiarg;
1855               if (!infop->subject)       /* we don't know Subject hash */
1856                 if (!msg2hash(infop)) return 0;
1857               subj2msg(infop);
1858               break;
1859             }
1860             break;
1861           case ITEM_DATE:       /* want a date reference */
1862             switch (infop->axis) {
1863               case ITEM_MESSAGE:
1864               case ITEM_AUTHOR:
1865               case ITEM_SUBJECT:
1866               case ITEM_DATE:
1867                 if (!infop->date && infop->source)
1868                   if (!msg2hash(infop)) return 0;
1869                   getdate(infop,0);
1870                 break;
1871             }
1872             break;
1873           case ITEM_INDEX:      /* ignore direction etc - only for index */
1874             if (!infop->target)
1875               infop->target = infop->source;
1876             if (!infop->target)
1877               findlastmsg(infop);
1878             break;
1879   }
1880   return 1;
1881 }
1882
1883 void list_lists()
1884 {
1885   unsigned long lno;
1886   cache = 2;
1887   flagrobot = 2;
1888   html_header("Robot index of lists",0,0,0,0);
1889   for (;;) {
1890     if (getln(&ssin,&cfline,&match,'\n') == -1)         /* read line */
1891       strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
1892     if (!match)
1893       break;
1894     if (cfline.s[0] == '#') continue;                   /* skip comment */
1895     cfline.s[cfline.len - 1] = '\0';                    /* so all are sz */
1896     (void) scan_ulong(cfline.s,&lno);                   /* listno for line */
1897     if (lno) {                          /* don't expose default list */
1898       oputs("<A href=\"");
1899       oput(strnum,fmt_ulong(strnum,lno));
1900       oputs("/index\">[link]</a>\n");
1901    }
1902   }
1903   html_footer(0);
1904 }
1905
1906 void list_list(unsigned long listno)
1907 /* Make one link [for list_set()] per set of 100 archive messages. */
1908 /* Assumption: Any directory DIR/archive/xxx where 'xxx' is a numeric,*/
1909 /* is part of the list archive and has in it an index file and one    */
1910 /* or more messages. */
1911 {
1912   DIR *archivedir;
1913   direntry *d;
1914   unsigned long msgset;
1915
1916   flagrobot = 2;
1917   strnum[fmt_ulong(strnum,listno)] = '\0';
1918   archivedir = opendir("archive/");
1919   if (!archivedir)
1920     if (errno != error_noent)
1921       strerr_die4sys(111,FATAL,ERR_OPEN,dir,"/archive: ");
1922     else
1923       strerr_die4sys(100,FATAL,ERR_OPEN,dir,"/archive: ");
1924
1925   cache = 1;
1926   html_header("Robot index for message sets in list",0,0,0,0);
1927
1928   while ((d = readdir(archivedir))) {
1929     if (d->d_name[scan_ulong(d->d_name,&msgset)])
1930         continue;               /* not numeric */
1931     oputs("<a href=\"../");     /* from /ezcgi/0/index to /ezcgi/listno/index*/
1932     oputs(strnum);
1933     oputs("/index/");
1934     oputs(d->d_name);
1935     oputs("\">[link]</a>\n");
1936   }
1937   closedir(archivedir);
1938   html_footer(0);
1939 }
1940
1941 void list_set(unsigned long listno,unsigned long msgset)
1942 {
1943   unsigned int msgfirst,msgmax;
1944   unsigned long lastset;
1945
1946   flagrobot = 2;
1947   findlastmsg(&msginfo);
1948   if (!stralloc_copys(&line,"<a href=\"../")) die_nomem();
1949   if (!stralloc_catb(&line,strnum,fmt_ulong(strnum,msgset))) die_nomem();
1950   lastset = msginfo.target / 100;
1951   cache = 2;
1952   msgfirst = 0;
1953   if (!msgset)
1954     msgfirst = 1;
1955   msgmax = 99;
1956   if (msgset > lastset) {               /* assure empty list */
1957     msgmax = 0;
1958     msgfirst = 1;
1959   } else if (msgset == lastset) {
1960     cache = 0;                          /* still changing */
1961     msgmax = msginfo.target % 100;
1962   }
1963   html_header("Robot index for messages in set",0,0,0,0);
1964   while (msgfirst <= msgmax) {
1965     oput(line.s,line.len);
1966     oput(strnum,fmt_uint0(strnum,msgfirst,2));
1967     oputs("\">[link]</a>\n");
1968     msgfirst++;
1969   }
1970   html_footer(0);
1971 }
1972
1973 /**************** MAY BE SUID ROOT HERE ****************************/
1974 void drop_priv(int flagchroot)
1975 {
1976   if (!uid) strerr_die2x(100,FATAL,ERR_SUID);           /* not as root */
1977   if (!euid) {
1978     if (flagchroot)
1979       if (chroot(dir) == -1)                            /* chroot listdir */
1980         strerr_die4sys(111,FATAL,"failed to chroot ",dir,": ");
1981     if (setuid(uid) == -1)                              /* setuid */
1982       strerr_die2sys(111,FATAL,ERR_SETUID);
1983   }
1984   euid = (unsigned long) geteuid();
1985   if (!euid) strerr_die2x(100,FATAL,ERR_SUID);          /* setuid didn't do it*/
1986 }
1987 /*******************************************************************/
1988
1989 int main(argc,argv)
1990 int argc;
1991 char **argv;
1992 {
1993   char *cp,*cppath;
1994   unsigned long listno,thislistno,tmpuid,msgset;
1995   unsigned long msgnum = 0;
1996   unsigned long port = 0L;
1997   unsigned long tmptarget;
1998   unsigned int pos,l;
1999   int flagindex = 0;
2000   int flagchroot = 1;           /* chroot listdir if SUID root */
2001   int ret;
2002   char sep;
2003
2004 /******************** we may be SUID ROOT ******************************/
2005   uid = (unsigned long) getuid();                       /* should be http */
2006   euid = (unsigned long) geteuid();                     /* chroot only if 0 */
2007
2008   if (!euid) {
2009     if ((fd = open_read(EZ_CGIRC)) == -1)               /* open config */
2010       strerr_die4sys(111,FATAL,ERR_OPEN,EZ_CGIRC,": ");
2011   } else {
2012     if ((fd = open_read(EZ_CGIRC_LOC)) == -1)           /* open local config */
2013       strerr_die4sys(111,FATAL,ERR_OPEN,EZ_CGIRC_LOC,": ");
2014   }
2015
2016   substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));    /* set up buffer */
2017         /* ##### tainted info #####*/
2018
2019   cmd = env_get("QUERY_STRING");                        /* get command */
2020   cppath = env_get("PATH_INFO");                        /* get path_info */
2021
2022   if (!cppath || !*cppath) {
2023     if (cmd && *cmd) {
2024       cmd += scan_ulong(cmd,&thislistno);
2025       if (*cmd == ':') cmd++;                           /* allow ':' after ln*/
2026     } else
2027       thislistno = 0L;
2028   } else {
2029     cppath++;
2030       cppath += scan_ulong(cppath,&thislistno);         /* this listno */
2031       if (!thislistno || *cppath++ == '/') {
2032         if (str_start(cppath,"index")) {
2033           cppath += 5;
2034           flagindex = 1;
2035           if (!thislistno) {                            /* list index */
2036             drop_priv(0);       /* <---- dropping privs */
2037             list_lists();
2038             close(fd);
2039             _exit(0);
2040           }
2041         }
2042       }                                                 /* rest done per list */
2043     }
2044
2045   for (;;) {
2046     if (getln(&ssin,&cfline,&match,'\n') == -1)         /* read line */
2047       strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
2048     if (!match)
2049       break;
2050     if (*cfline.s == '#' || cfline.len == 1) continue;  /* skip comment/blank */
2051     cfline.s[cfline.len - 1] = '\0';                    /* so all are sz */
2052     pos = scan_ulong(cfline.s,&listno);                 /* listno for line */
2053     if (thislistno != listno) continue;
2054     sep = cfline.s[pos++];
2055     if (cfline.s[pos] == '-') {                         /* no chroot if -uid*/
2056       flagchroot = 0;
2057       pos++;
2058     }
2059     pos += scan_ulong(cfline.s+pos,&tmpuid);            /* listno for line */
2060     if (tmpuid) uid = tmpuid;                           /* override default */
2061     if (!cfline.s[pos++] == sep)
2062       die_syntax("missing separator after user id");
2063     if (cfline.s[pos] != '/')
2064         die_syntax("dir");                              /* absolute path */
2065     l = byte_chr(cfline.s + pos, cfline.len - pos,sep);
2066     if (l == cfline.len - pos)                          /* listno:path:...*/
2067       die_syntax("missing separator after path");
2068     dir = cfline.s + pos;
2069     pos += l;
2070     cfline.s[pos++] = '\0';                             /* .../dir\0 */
2071     break;      /* do rest after dropping priv */
2072   }
2073   close(fd);                                            /* don't accept uid 0*/
2074   if (!dir) {
2075     drop_priv(0);       /* don't trust cgierr. No dir, no chroot */
2076     cgierr("list ",ERR_NOEXIST,"");
2077   }
2078   if (chdir(dir) == -1)                                 /* chdir listdir */
2079     strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": ");
2080   drop_priv(flagchroot);
2081
2082 /******************************* RELAX **********************************/
2083
2084 /********************* continue to process config line ******************/
2085
2086   flagrobot = 0;
2087   if (cfline.s[pos] == '-') {
2088     flagobscure = 1;
2089     pos++;
2090   }
2091   local = cfline.s + pos;
2092   l = byte_chr(cfline.s + pos, cfline.len - pos,sep);   /* ... home */
2093   if (l < cfline.len - pos) {                           /* optional */
2094     pos += l;
2095     cfline.s[pos++] = '\0';
2096     home = cfline.s + pos;
2097     l = byte_chr(cfline.s + pos, cfline.len - pos,sep); /* ... charset */
2098     if (l < cfline.len - pos) {                         /* optional */
2099       pos += l;
2100       cfline.s[pos++] = '\0';
2101       charset = cfline.s + pos;
2102       l = byte_chr(cfline.s+pos,cfline.len - pos,sep);  /* ... stylesheet */
2103       if (l < cfline.len - pos) {                       /* optional */
2104         pos += l;
2105         cfline.s[pos++] = '\0';
2106         stylesheet = cfline.s + pos;
2107         l = byte_chr(cfline.s+pos,cfline.len-pos,sep);  /* ... bannerURL */
2108         if (l < cfline.len - pos) {                     /* optional */
2109           pos += l;
2110           cfline.s[pos++] = '\0';
2111           banner = cfline.s + pos;
2112         }
2113       }
2114     }
2115   }
2116   if (!charset || !*charset)                            /* rfc822 default */
2117     charset = EZ_CHARSET;
2118   if (!stralloc_copys(&curcharset,charset)) die_nomem();
2119   csbase = decode_charset(curcharset.s,curcharset.len);
2120   if (csbase == CS_BAD) csbase = CS_NONE;
2121   cs = csbase;
2122   pos = + str_rchr(local,'@');
2123   if (!local[pos])
2124     die_syntax("listaddress lacks '@'");                /* require host */
2125   local[pos++] = '\0';
2126   host = local + pos;
2127
2128 /********************* Accomodate robots and PATH_INFO ****************/
2129
2130   if (flagindex) {
2131     if (*(cppath++) == '/') {           /* /2/index/123 */
2132       cppath += scan_ulong(cppath,&msgset);
2133       list_set(thislistno,msgset);
2134     } else                              /* /2/index */
2135       list_list(thislistno);
2136     _exit(0);
2137   }
2138
2139   if (cppath && *cppath) {              /* /2/msgnum */
2140     flagrobot = 1;                      /* allow index, but "nofollow" */
2141     scan_ulong(cppath,&msgnum);
2142   }                                     /* dealt with normally */
2143
2144 /********************* Get info from server on BASE etc ****************/
2145
2146   if (!stralloc_copys(&base,"<BASE href=\"http://")) die_nomem();
2147   cp = env_get("SERVER_PORT");
2148   if (cp) {                     /* port */
2149     (void) scan_ulong(cp,&port);
2150     if ((unsigned int) port == 443) {           /* https: */
2151       if (!stralloc_copys(&base,"<BASE href=\"https://")) die_nomem();
2152     }
2153   }
2154   if (port && (unsigned int) port != 80 && (unsigned int) port != 443) {
2155       if (!stralloc_cats(&base,":")) die_nomem();
2156       if (!stralloc_catb(&base,strnum,fmt_ulong(strnum,port))) die_nomem();
2157   }
2158   if (!(cp = env_get("HTTP_HOST")))
2159     if (!(cp = env_get("SERVER_NAME")))
2160       strerr_die2x(100,FATAL,"both HTTP_HOST and SERVER_NAME are empty");
2161   if (!stralloc_cats(&base,cp)) die_nomem();
2162   if (!(cp = env_get("SCRIPT_NAME")))
2163     strerr_die2x(100,FATAL,"empty SCRIPT_NAME");
2164   if (!stralloc_cats(&base,cp)) die_nomem();
2165   if (!stralloc_cats(&base,"\">\n")) die_nomem();
2166   if (!stralloc_copys(&url,"<A HREF=\"")) die_nomem();
2167   pos = str_rchr(cp,'/');
2168   if (cp[pos])
2169     if (!stralloc_cats(&url,cp + pos + 1)) die_nomem();
2170   if (!stralloc_cats(&url,"?")) die_nomem();
2171   if (thislistno) {
2172     if (!stralloc_catb(&url,strnum,fmt_ulong(strnum,thislistno))) die_nomem();
2173     if (!stralloc_cats(&url,":")) die_nomem();
2174   }
2175
2176   cache = 1;                            /* don't know if we want to cache */
2177
2178 /****************************** Get command ****************************/
2179
2180   if (msgnum) {                         /* to support /listno/msgno */
2181    msginfo.target = msgnum;
2182    msginfo.item = ITEM_MESSAGE;
2183    cache = 2;
2184   } else {
2185       (void) decode_cmd(cmd,&msginfo);
2186       if (!do_cmd(&msginfo))
2187         cgierr("I'm sorry, Dave ... I can't do that, Dave ...","","");
2188   }
2189
2190   switch (msginfo.item) {
2191     case ITEM_MESSAGE:
2192         if (!show_message(&msginfo)) {          /* assume next exists ... */
2193           cache = 0;                            /* border cond. - no cache */
2194           msginfo.target = msginfo.source;      /* show same */
2195           msginfo.subjnav = 0;
2196           msginfo.authnav = 0;
2197           ret = show_message(&msginfo);
2198         }
2199         break;
2200     case ITEM_AUTHOR:
2201         if (!show_object(&msginfo,ITEM_AUTHOR))
2202           cgierr ("I couldn't find the author for that message","","");
2203         break;
2204     case ITEM_SUBJECT:
2205         if (!show_object(&msginfo,ITEM_SUBJECT))
2206           cgierr ("I couldn't find the subject for that message","","");
2207         break;
2208     case ITEM_DATE:
2209         if (!show_object(&msginfo,ITEM_DATE)) {
2210           finddate(&msginfo);
2211           ret = show_object(&msginfo,ITEM_DATE);
2212         }
2213         break;
2214     case ITEM_INDEX:
2215         if (!show_index(&msginfo)) {
2216           tmptarget = msginfo.target;
2217           findlastmsg(&msginfo);
2218           cache = 0;                    /* latest one - no cache */
2219           if (!msginfo.target || msginfo.target > tmptarget) {
2220             cache = 2;                  /* won't change */
2221             firstdate(&msginfo,1);      /* first thread index */
2222             msginfo.direction = DIRECT_FIRST;
2223             date2msg(&msginfo);         /* (may not be 1 if parts removed) */
2224           }
2225           ret = show_index(&msginfo);
2226         }
2227         break;
2228     default:
2229         strerr_die2x(100,FATAL,"bad item in main");
2230   }
2231   if (!ret) {
2232     findlastmsg(&msginfo);              /* as last resort; last msgindex */
2233     cache = 0;
2234     ret = show_message(&msginfo);
2235   }
2236
2237  _exit(0);
2238 }