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