Import ezmlm-idx 0.40
[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 }