1 /*$Id: ezmlm-cgi.c,v 1.17 1999/12/24 04:21:26 lindberg Exp $*/
2 /*$Name: ezmlm-idx-040 $*/
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>"
22 #include "readwrite.h"
28 #include "gen_alloc.h"
29 #include "gen_allocdefs.h"
32 #include "subscribe.h"
39 #define FATAL "ezmlm-cgi: fatal: "
41 #define THREAD "-threadv"
42 #define SUBSCRIBE "-subscribe"
44 #define TXT_CGI_SUBSCRIBE "\">[eSubscribe]</a>\n"
45 #define TXT_CGI_FAQ "\">[eFAQ]</a>\n"
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 */
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 */
69 /* Need to add inits if you increase NO_HDRS */
70 stralloc hdr[NO_HDRS] = { {0},{0},{0},{0},{0},{0} };
71 /**************** Header processing ***********************/
74 /* index of subject in above, first = 1 */
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 ! */
80 /* ulong at least 32 bits. (Creating a Year 0xffffff problem ;-) */
81 #define MAXULONG 0xffffffff
83 char cmdstr[5] = "xxx:";
84 #define ITEM "-msadiz"
85 #define ITEM_MESSAGE 1
86 #define ITEM_SUBJECT 2
91 #define DIRECT "psnpn"
94 #define DIRECT_PREV -1
95 /* use only as the argument for some functions. Terrible hack for date links */
96 #define DIRECT_FIRST 3
105 char *stylesheet = 0;
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};
112 stralloc author = {0};
113 stralloc subject = {0};
116 stralloc decline = {0}; /* for rfc2047-decoded headers and QP/base64 */
117 stralloc cfline = {0}; /* from config file */
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;
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 */
138 unsigned int flagmime;
139 unsigned int cs,csbase;
145 char lastjp[] = "B"; /* to get back to the correct JP after line break */
149 mime_info *mime_current = 0;
150 mime_info *mime_tmp = 0;
158 void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); }
160 void die_syntax(char *s)
162 strerr_die4x(100,FATAL,ERR_SYNTAX,"config file: ",s);
166 substdio ssout = SUBSTDIO_FDBUF(write,1,outbuf,sizeof(outbuf));
168 void oput(register char *s, register unsigned int l)
169 /* unbuffered. Avoid extra copy as httpd buffers */
171 if (substdio_put(&ssout,s,l) == -1)
172 strerr_die3sys(111,FATAL,ERR_WRITE,"stdout: ");
175 void oputs(register char *s)
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); }
183 /* If we already issued a header than this will look ugly */
184 void cgierr(char *s,char *s1,char *s2)
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");
194 substdio_flush(&ssout);
198 unsigned long msgnav[5]; /* 0 prev prev 1 prev 2 this 3 next 4 next-next */
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;
207 unsigned long *authnav; /* msgnav structure */
208 unsigned long *subjnav; /* msgnav structure */
211 char *cgiarg; /* sub/auth as expected from axis */
214 void toggle_flagpre(int flag)
218 cn1 = 0; cn2 = 0; /* just in case */
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. */
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"))
239 if (case_startb(s+8,l-8,"-jp"))
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 */
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. */
271 if (!cs) { /* us-ascii & iso-8859- & unrecognized */
275 case '>': oputs(">"); break;
276 case '<': oputs("<"); break;
277 case '"': oputs("""); break;
278 case '&': oputs("&"); break;
279 case '\n': precharcount = 0; oput(s,1); break;
281 if (precharcount >= 84 && flagpre) {
282 oput("\n",1); /* in place of ' ' */
285 oput(s,1); /* otherwise out with it. */
287 default: oput(s,1); break;
290 } else if (cs == CS_CN) { /* cn-, gb*, big5 */
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 */
297 case '>': oputs(">"); break;
298 case '<': oputs("<"); break;
299 case '"': oputs("""); break;
300 case '&': oputs("&"); break;
301 case '\n': precharcount = 0; oput(s,1); break;
303 if (precharcount >= 84 && flagpre) {
304 oput("\n",1); /* break in ascii sequence */
309 default: oput(s,1); break;
311 } else if (precharcount >= 84 && flagpre && cn2) {
312 oput("\n",1); /* break after 2-byte code */
316 } else { /* iso-2022 => PAIN! */
319 if (ss23) { /* ss2/ss3 character */
324 if (so) { /* = 0 ascii, = 1 SO charset */
325 if (!(*s & 0xe0)) { /* ctrl-char */
327 case ESC: state = 1; break;
328 case SI: so = 0; break;
329 case '\n': precharcount = 0; break;
334 } else { /* check only ascii */
336 case '>': oputs(">"); break;
337 case '<': oputs("<"); break;
338 case '"': oputs("""); break;
339 case '&': oputs("&"); break;
341 if (precharcount >= 84 && flagpre) {
342 oput("\n",1); /* break in ascii sequence */
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;
359 } /* by now all output is done, now ESC interpretation */
361 /* ESC code - don't count */
362 if (precharcount) precharcount--;
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 */
375 case 4: switch (*s) { /* s2/ss2/ss3 or JP 2 byte shift */
377 case '@': lastjp[0] = *s;
378 so = 1; state = 0; break; /* JP */
379 default: break; /* other SS2/3 des */
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;
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 */
400 /* For iso-2022-CN: nothing if SI, otherwise SI \n SO */
401 /* For iso-2022-KR same */
412 char hexchar[] = "0123456789ABCDEF";
413 char enc_url[] = "%00";
415 void urlencode_put (register char *s,register unsigned int l)
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];
429 void urlencode_puts(register char *s)
431 urlencode_put(s,str_len(s));
434 int checkhash(register char *s)
436 register int l = HASHLEN;
438 if (*s < 'a' || *s > 'p') return 0; /* illegal */
441 if (*s) return 0; /* extraneous junk */
445 int makefn(stralloc *sa,char item, unsigned long n, char *hash)
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)))
453 } else if (item == ITEM_DATE) {
454 if (!stralloc_cats(sa,"threads/")) die_nomem();
455 if (!stralloc_catb(sa,strnum,fmt_ulong(strnum,n)))
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();
461 if (item == ITEM_AUTHOR) {
462 if (!stralloc_cats(sa,"authors/")) die_nomem();
464 if (!stralloc_cats(sa,"subjects/")) die_nomem();
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();
471 if (!stralloc_0(sa)) die_nomem();
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.*/
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>");
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 */
498 oputs(cmdstr); /* e.g. map: */
499 oput(strnum,fmt_ulong(strnum,msg));
500 if (!cp && l >= HASHLEN)
504 oput(strnum,fmt_ulong(strnum,infop->date));
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;
517 htmlencode_put(data + HASHLEN + 1,l - HASHLEN - 1);
523 void linktoindex(struct msginfo *infop,char item)
524 /* for links from message view back to author/subject/threads index */
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));
534 oput(strnum,fmt_ulong(strnum,infop->date));
540 oputs(infop->author);
544 if (infop->subject) {
546 oputs(infop->subject);
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 */
562 cmdstr[0] = ITEM[ITEM_MESSAGE];
563 cmdstr[1] = ITEM[axis];
564 cmdstr[2] = DIRECT[direction + 1];
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];
576 acc = infop->subject;
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];
592 oput(strnum,fmt_ulong(strnum,msg));
603 urlencode_puts("Just Click \"SEND\"!");
608 register char *cp,*cp1,*cp2;
614 while(*cp1 && *cp1 != '=') cp1++;
617 while(*cp2 && *cp2 != ',') cp2++;
619 oput(cp1 + 1,cp2 - cp1 - 1);
631 oputs("<a href=\"mailto:");
637 oputs(TXT_CGI_SUBSCRIBE);
638 oputs("<a href=\"mailto:");
647 void msglinks(struct msginfo *infop)
648 /* Creates the html for all links from one message view */
650 oputs("<DIV class=msglinks><STRONG>Msg by: ");
651 link_msg(infop,ITEM_SUBJECT,DIRECT_PREV);
652 oputs("[<-</A> ");
653 linktoindex(infop,ITEM_SUBJECT);
654 oputs(">thread</A> ");
655 link_msg(infop,ITEM_SUBJECT,DIRECT_NEXT);
656 oputs("->]</A> \n");
657 link_msg(infop,ITEM_MESSAGE,DIRECT_PREV);
658 oputs("[<-</A> ");
659 linktoindex(infop,ITEM_INDEX);
661 link_msg(infop,ITEM_MESSAGE,DIRECT_NEXT);
662 oputs("->]</A> \n");
663 link_msg(infop,ITEM_AUTHOR,DIRECT_PREV);
664 oputs("[<-</A> ");
665 linktoindex(infop,ITEM_AUTHOR);
666 oputs(">author</A> ");
667 link_msg(infop,ITEM_AUTHOR,DIRECT_NEXT);
668 oputs("->]</A> |\n");
669 linktoindex(infop,ITEM_DATE);
670 oputs(">[Threads]</A>\n");
672 oputs("\n<a href=\"mailto:");
675 strnum[fmt_ulong(strnum,infop->target)] = '\0';
680 oputs("\">[eMsg]</A>\n");
681 oputs("<a href=\"mailto:");
688 oputs("\">[eThread]</A>\n");
690 oputs("</STRONG></DIV>\n");
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 */
700 oputs("Content-Type: text/html; charset=");
701 oput(curcharset.s,curcharset.len);
703 oputs("\nCache-Control: ");
706 oputs("no-cache"); /* known upper border */
709 oputs("max-age=300"); /* 5 min - most lists aren't that fast*/
712 oputs("max-age=1209600"); /* 14 days is a long time */
716 oputs("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\">\n");
717 oputs("<HTML><HEAD>\n<TITLE>");
725 if (s) htmlencode_put(s,l);
727 if (class && *class && stylesheet && *stylesheet) {
728 oputs("<LINK href=\"");
730 oputs("\" rel=\"stylesheet\" type=\"text/css\">\n");
732 if (!flagrobot) /* robot access allowed to follow */
733 oputs("<META NAME=\"robots\" CONTENT=\"noindex\">\n");
735 oputs("<META NAME=\"robots\" CONTENT=\"nofollow\">\n");
736 if (flagspecial & SPC_BASE)
737 oput(base.s,base.len);
739 if (class && *class) {
740 oputs("<BODY class=");
748 void html_footer(int flagspecial)
750 oputs("<HR><DIV class=copyright>");
753 if ((flagspecial & SPC_BANNER) && banner && *banner) {
754 oputs("<DIV class=banner>\n");
755 if (*banner == '<') oputs(banner);
757 substdio_flush(&ssout);
759 bannerargs[0] = banner;
760 bannerargs[1] = host;
761 bannerargs[2] = local;
763 /* We log errors but just complete the page anyway, since we're */
764 /* already committed to output something. */
765 switch(child = fork()) {
767 strerr_warn3(FATAL,ERR_FORK,"banner program: ",&strerr_sys);
770 execv(*bannerargs,bannerargs);
771 strerr_die3x(100,FATAL,ERR_EXECUTE,"banner program: ");
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);
783 oputs("</BODY>\n</HTML>\n");
784 substdio_flush(&ssout);
789 void datelink(struct msginfo *infop,unsigned long d,char direction)
790 /* output a date with link back to thread index */
793 cmdstr[0] = ITEM[ITEM_DATE];
794 cmdstr[1] = ITEM[ITEM_DATE];
795 cmdstr[2] = DIRECT[direction + 1];
797 if (direction == DIRECT_LAST)
798 oput("0",1); /* suppress msgnum to avoid going there */
800 oput(strnum,fmt_ulong(strnum,infop->target));
802 oput(strnum,fmt_ulong(strnum,d));
806 if (dateline(&dtline,d) < 0) die_nomem();
807 oput(dtline.s,dtline.len);
816 oputs("[<<-]");
819 oputs("[->>]");
825 void finddate(struct msginfo *infop)
826 /* DIRECT_SAME works as DIRECT_PREV, dvs returns previous date or last date */
830 unsigned long ddate, startdate;
831 unsigned long below, above;
834 above = MAXULONG; /* creating a Y 0xffffff problem */
835 startdate = infop->date;
836 archivedir = opendir("archive/threads/");
838 if (errno != error_noent)
839 strerr_die4sys(111,FATAL,ERR_OPEN,dir,"/archive/threads: ");
841 strerr_die4sys(100,FATAL,ERR_OPEN,dir,"/archive/threads: ");
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 ... */
849 if (ddate > startdate && ddate < above) above = ddate;
850 if (ddate < startdate && ddate > below) below = ddate;
852 if (ddate < above) above = ddate;
853 if (ddate > below) below = ddate;
856 closedir(archivedir);
858 if (infop->direction == DIRECT_NEXT && above != MAXULONG || !below)
859 /* we always give a valid date as long as there is at least one */
866 void latestdate(struct msginfo *infop,int flagfail)
869 datetime_tai(&dt,now());
870 infop->date = ((unsigned long) dt.year + 1900) * 100 + dt.mon + 1;
873 infop->direction = DIRECT_PREV;
878 void firstdate(struct msginfo *infop,int flagfail)
881 infop->direction = DIRECT_NEXT;
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 */
889 if (!flagfail) { /* guess */
890 if (infop->direction == DIRECT_NEXT) {
892 if (infop->date % 100 > 12) infop->date += (100 - 12);
893 } else if (infop->direction == DIRECT_PREV) {
895 if (!infop->date % 100) infop->date -= (100 - 12);
902 indexlinks(struct msginfo *infop)
904 unsigned long tmpmsg;
906 tmpmsg = infop->target;
908 oputs("<DIV class=idxlinks><STRONG>");
909 linktoindex(infop,ITEM_INDEX);
910 oputs(">[<<-]</A>\n");
911 if (tmpmsg >= 100) infop->target = tmpmsg - 100;
912 linktoindex(infop,ITEM_INDEX);
913 oputs(">[<-]</A>\n");
914 infop->target = tmpmsg + 100;
915 linktoindex(infop,ITEM_INDEX);
916 oputs(">[->]</A>\n");
917 infop->target = MAXULONG;
918 linktoindex(infop,ITEM_INDEX);
919 oputs(">[->>]</A> |\n");
920 infop->target = tmpmsg;
921 linktoindex(infop,ITEM_DATE);
922 oputs(">[Threads by date]</A>\n");
925 oputs("</STRONG></DIV>\n");
928 int show_index(struct msginfo *infop)
930 unsigned long thismsg;
934 (void) makefn(&fn,ITEM_INDEX,msginfo.target,"");
935 if ((fd = open_read(fn.s)) == -1)
936 if (errno == error_noent)
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))))
944 if (!stralloc_cats(&line,"xx")) die_nomem();
945 html_header("Messages ",line.s,line.len,"idxbody",SPC_BANNER | SPC_BASE);
947 oputs("<HR><H1 id=\"idxhdr\">");
949 oput(line.s,line.len);
951 oputs("<HR><DIV class=idx>\n");
953 if (getln(&ssin,&line,&match,'\n') == -1)
954 strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
957 pos = scan_ulong(line.s,&thismsg);
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));
969 link(infop,ITEM_MESSAGE,ITEM_SUBJECT,thismsg,line.s+pos,line.len - pos - 1);
972 if (getln(&ssin,&line,&match,'\n') == -1)
973 strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
976 pos = byte_chr(line.s,line.len,';');
977 if (pos != line.len) {
978 infop->date = date2yyyymm(line.s);
980 link(infop,ITEM_AUTHOR,ITEM_AUTHOR,thismsg,line.s+pos+1,
987 oputs("\n</DIV><HR>\n");
989 html_footer(SPC_BANNER);
993 void objectlinks(struct msginfo *infop, char item)
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);
1003 if (!infop->target) infop->axis = ITEM_DATE;
1004 linktoindex(infop,ITEM_DATE);
1005 oputs(">[Threads by date]</A>\n");
1007 if (item != ITEM_INDEX) {
1008 linktoindex(infop,ITEM_INDEX);
1009 oputs(">[Messages by date]</A>\n");
1013 oputs("</STRONG></DIV>\n");
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 */
1020 unsigned long lastdate,thisdate,thismsg;
1026 targetitem = ITEM_MESSAGE; /* default message is target */
1029 if (!makefn(&fn,ITEM_SUBJECT,0L,infop->subject)) return 0;
1032 if (!makefn(&fn,ITEM_AUTHOR,0L,infop->author)) return 0;
1035 if (!makefn(&fn,ITEM_DATE,infop->date,"")) return 0;
1038 die_prog("Bad object type in show_object");
1040 if ((fd = open_read(fn.s)) == -1)
1041 if (errno == error_noent)
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");
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);
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);
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);
1080 default: die_prog("unrecognized object type in show_object");
1083 oputs("<DIV class=obj>\n");
1085 if (getln(&ssin,&line,&match,'\n') == -1) /* read subject */
1086 strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
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");
1098 if (line.len < pos + HASHLEN + 2)
1099 strerr_die4x(100,FATAL,"entry in ",fn.s," lacks hash");
1100 if (thisdate != lastdate) {
1106 datelink(infop,thisdate,DIRECT_SAME);
1107 lastdate = thisdate;
1110 if (item == ITEM_SUBJECT)
1111 linkitem = ITEM_AUTHOR;
1113 linkitem = ITEM_SUBJECT;
1114 link(infop,targetitem,linkitem,thismsg,line.s+pos,line.len - pos - 1);
1120 oputs("<a name=b></a>");
1121 oputs("<HR></DIV>\n");
1122 objectlinks(infop,item);
1123 html_footer(SPC_BANNER);
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;
1139 mime_tmp = mime_current;
1141 mime_current = mime_current->next;
1142 if (!mime_current) {
1143 if (!(mime_current = (mime_info *) alloc(sizeof (mime_info))))
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;
1153 mime_current->level = mime_tmp->level + 1;
1155 mime_current->level = 1;
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 */
1162 char *cp, *cpafter, *cpnext;
1164 if (!*l || !**s) return;
1167 cp = *s; cpnext = cp + *l; cpafter = cpnext;
1168 while (cp < cpafter) {
1176 cp = *s; cpnext = cp + *l; cpafter = cpnext;
1177 while (cp < cpafter) {
1178 if (*cp == ' ' || *cp == '\t' || *cp == '\n' || *cp == ';') {
1185 if (!stralloc_copyb(sa,*s,cp - *s)) die_nomem();
1186 *l = cpafter - cpnext; /* always >= 0 */
1191 void decode_mime_type(char *s,unsigned int l,unsigned int flagmime)
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 */
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;
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;
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;
1234 mime_current->mimetype = r;
1236 while (l && (*s == ' ' || *s == '\t' || *s == ';' || *s == '\n')) {
1237 s++; l--; } /* skip ;LWSP */
1238 if (case_startb(s,l,"boundary=")) {
1240 mime_getarg(&(mime_current->boundary),&s,&l);
1241 } else if (case_startb(s,l,"charset=")) {
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 */
1248 if (!stralloc_copy(&curcharset,&mime_current->charset)) die_nomem();
1249 } else { /* skip non LWSP */
1254 while (l && *s != '"') { s++, l--; }
1255 if (l) { s++, l--; }
1258 if (!l || *s == ' ' || *s == '\t' || *s == '\n') break;
1267 void decode_transfer_encoding(register char *s,register unsigned int l)
1270 mime_current->ctenc = CTENC_NONE;
1271 if (!l || (mime_current->mimetype & MIME_MULTI)) return;
1272 /* base64/QP ignored for multipart */
1274 while (l && (*s == ' ' || *s == '\t')) { s++; l--; } /* skip LWSP */
1275 if (case_startb(s,l,"quoted-printable")) {
1277 } else if (case_startb(s,l,"base64")) {
1280 mime_current->ctenc = r;
1284 int check_boundary()
1285 /* return 0 if no boundary, 1 if start, 2 if end */
1289 if (*line.s != '-' || line.s[1] != '-') return 0;
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 */
1301 } else { /* start */
1308 tmp = tmp->previous;
1310 if (!stralloc_copys(&curcharset,charset)) die_nomem();
1311 /* suprtfluous since header done by now */
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. */
1329 if (!stralloc_copyb(&decline,strnum,fmt_ulong(strnum,infop->target)))
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);
1335 new_mime(); /* allocate */
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);
1343 oputs("<DIV class=message>\n");
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 */
1359 unsigned int colpos,l,pos;
1363 for (i = 0; i < NO_HDRS; i++) hdr[i].len = 0;
1366 recursion_level++; /* one up */
1369 if (getln(&ssin,&line,&match,'\n') == -1)
1370 strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
1372 if ((btype = check_boundary())) {
1377 if (mime_current->level < recursion_level) {
1388 if (!flagstartseen) continue; /* skip to start */
1390 if (line.len == 1) {
1391 if (flagshowheaders) { /* rfc822hdr only */
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>");
1400 oputs(constmap_get(&headermap,i + 1));
1402 decodeHDR(hdr[i].s,hdr[i].len,&line,"",FATAL);
1403 if (i == HDR_SUBJECT - 1 && flagtoplevel) {
1404 oputs("<A class=relk href=\"mailto:");
1409 urlencode_put(line.s + 1,line.len - 2);
1412 if (flagobscure && i == HDR_FROM - 1) {
1414 decodeHDR(cp,author_name(&cp,line.s,line.len),&decline,"",FATAL);
1415 htmlencode_put(decline.s,decline.len);
1417 decodeHDR(hdr[i].s,hdr[i].len,&decline,"",FATAL);
1418 htmlencode_put(decline.s,decline.len - 1);
1420 if (i == HDR_SUBJECT - 1 && flagtoplevel)
1421 oputs("</A></SPAN>");
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);
1445 case MIME_MESSAGE_RFC822:
1448 flagshowheaders = 1;
1450 flagmime = 0; /* need new MIME-Version header */
1452 case MIME_TEXT_HTML:
1457 oputs("<strong>[\"");
1458 oput(mime_current->ctype.s,mime_current->ctype.len);
1459 oputs("\" not shown]</strong>\n");
1460 flaggoodfield = 0; /* hide */
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");
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 */
1477 } else if (line.s[0] != ' ' && line.s[0] != '\t') {
1480 colpos = byte_chr(line.s,line.len,':');
1481 if ((whatheader = constmap_index(&headermap,line.s,colpos))) {
1483 if (!stralloc_copyb(&hdr[whatheader - 1],line.s + colpos + 1,
1484 line.len - colpos - 1)) die_nomem();
1488 if (!stralloc_catb(&hdr[whatheader - 1],line.s,line.len))
1492 if (flaggoodfield) {
1493 if (mime_current->ctenc) {
1494 if (!stralloc_copy(&decline,&line)) die_nomem();
1496 if (mime_current->ctenc == CTENC_QP)
1497 decodeQ(decline.s,decline.len,&line);
1499 decodeB(decline.s,decline.len,&line);
1502 oput(line.s,line.len);
1504 htmlencode_put(line.s,line.len); /* body */
1511 int show_message(struct msginfo *infop)
1515 if(!stralloc_copys(&headers,(char *) headers_used)) die_nomem();
1516 if (!stralloc_0(&headers)) die_nomem();
1519 if (*psz == '\\') *psz = '\0';
1522 if (!constmap_init(&headermap,headers.s,headers.len,0))
1525 (void) makefn(&fn,ITEM_MESSAGE,msginfo.target,"");
1526 if ((fd = open_read(fn.s)) == -1)
1527 if (errno == error_noent)
1530 strerr_die4sys(111,FATAL,ERR_OPEN,fn.s,": ");
1531 substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
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 */
1538 show_part(infop,1,0,1); /* do real work, including html header etc */
1542 oputs("<HR></DIV>\n");
1548 char decode_item(char 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","");
1559 return 0; /* never reached */
1562 char decode_direction(char 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","");
1571 return 0; /* never reached */
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 */
1588 infop->author = (char *)0;
1589 infop->subject = (char *)0;
1590 infop->cgiarg = (char *)0;
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;
1601 if (ch >= '0' && ch <= '9') { /* numeric - simplified cmd: msgnum ... */
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);
1609 infop->axis = decode_item(ch);
1611 infop->direction = decode_direction(ch);
1612 if (*(s++) != ':') return 0;
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 */
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;
1628 int msg2hash(struct msginfo *infop)
1631 unsigned long tmpmsg;
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)
1639 strerr_die4sys(111,FATAL,ERR_OPEN,fn.s,": ");
1641 substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
1643 if (getln(&ssin,&line,&match,'\n') == -1)
1644 strerr_die3sys(111,FATAL,ERR_READ,"index: ");
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: ");
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");
1666 infop->date = date2yyyymm(line.s+1); /* ';' marks end ok */
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;
1674 return 1; /* success */
1678 return 0; /* failed to match */
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. */
1689 if (infop->direction == DIRECT_SAME) {
1690 infop->target = infop->source;
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!");
1698 strerr_die4sys(111,FATAL,ERR_OPEN,fn.s,": ");
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,": ");
1705 strerr_die3x(100,ERR_SYNTAX,fn.s,": first line missing");
1707 msgnav[3] = 0L; /* next */
1708 msgnav[4] = 0L; /* after */
1711 if (getln(&ssin,&line,&match,'\n') == -1)
1712 strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
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,": ");
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,": ");
1726 (void) scan_ulong(line.s,&(msgnav[4]));
1731 switch (infop->axis) {
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 */
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 */
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 */
1749 die_prog("Bad item in setmsg");
1754 void auth2msg(struct msginfo *infop)
1756 if (!infop->author) die_prog("no such author in authmsg");
1757 if (!makefn(&fn,ITEM_AUTHOR,0L,infop->author)) die_prog("auth2msg");
1761 void subj2msg(struct msginfo *infop)
1763 if (!infop->subject) die_prog("no such subject in subj2msg");
1764 if (!makefn(&fn,ITEM_SUBJECT,0L,infop->subject)) die_prog("subj2msg");
1768 void date2msg(struct msginfo *infop)
1770 (void) makefn(&fn,ITEM_DATE,infop->date,"");
1774 void findlastmsg(struct msginfo *infop)
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));
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*/
1788 infop->target = infop->source;
1790 switch (infop->item) {
1791 case ITEM_MESSAGE: /* we want to get a message back */
1793 switch (infop->axis) {
1795 if (infop->direction == DIRECT_SAME)
1797 else if (infop->direction == DIRECT_NEXT)
1799 else { /* previous */
1801 if (infop->target >= 2)
1808 infop->author = infop->cgiarg;
1809 if (!infop->author) /* we don't know author hash */
1810 if (!msg2hash(infop)) return 0;
1814 infop->subject = infop->cgiarg;
1815 if (!infop->subject) /* we don't know Subject hash */
1816 if (!msg2hash(infop)) return 0;
1823 switch (infop->axis) {
1826 if (!msg2hash(infop)) return 0;
1829 infop->author = infop->cgiarg;
1831 if (!msg2hash(infop)) return 0;
1835 infop->subject = infop->cgiarg;
1836 if (!infop->subject) /* we don't know Subject hash */
1837 if (!msg2hash(infop)) return 0;
1843 switch (infop->axis) {
1845 if (!msg2hash(infop)) return 0;
1848 infop->author = infop->cgiarg;
1850 if (!msg2hash(infop)) return 0;
1854 infop->subject = infop->cgiarg;
1855 if (!infop->subject) /* we don't know Subject hash */
1856 if (!msg2hash(infop)) return 0;
1861 case ITEM_DATE: /* want a date reference */
1862 switch (infop->axis) {
1867 if (!infop->date && infop->source)
1868 if (!msg2hash(infop)) return 0;
1873 case ITEM_INDEX: /* ignore direction etc - only for index */
1875 infop->target = infop->source;
1888 html_header("Robot index of lists",0,0,0,0);
1890 if (getln(&ssin,&cfline,&match,'\n') == -1) /* read line */
1891 strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
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");
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. */
1914 unsigned long msgset;
1917 strnum[fmt_ulong(strnum,listno)] = '\0';
1918 archivedir = opendir("archive/");
1920 if (errno != error_noent)
1921 strerr_die4sys(111,FATAL,ERR_OPEN,dir,"/archive: ");
1923 strerr_die4sys(100,FATAL,ERR_OPEN,dir,"/archive: ");
1926 html_header("Robot index for message sets in list",0,0,0,0);
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*/
1935 oputs("\">[link]</a>\n");
1937 closedir(archivedir);
1941 void list_set(unsigned long listno,unsigned long msgset)
1943 unsigned int msgfirst,msgmax;
1944 unsigned long lastset;
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;
1956 if (msgset > lastset) { /* assure empty list */
1959 } else if (msgset == lastset) {
1960 cache = 0; /* still changing */
1961 msgmax = msginfo.target % 100;
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");
1973 /**************** MAY BE SUID ROOT HERE ****************************/
1974 void drop_priv(int flagchroot)
1976 if (!uid) strerr_die2x(100,FATAL,ERR_SUID); /* not as root */
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);
1984 euid = (unsigned long) geteuid();
1985 if (!euid) strerr_die2x(100,FATAL,ERR_SUID); /* setuid didn't do it*/
1987 /*******************************************************************/
1994 unsigned long listno,thislistno,tmpuid,msgset;
1995 unsigned long msgnum = 0;
1996 unsigned long port = 0L;
1997 unsigned long tmptarget;
2000 int flagchroot = 1; /* chroot listdir if SUID root */
2004 /******************** we may be SUID ROOT ******************************/
2005 uid = (unsigned long) getuid(); /* should be http */
2006 euid = (unsigned long) geteuid(); /* chroot only if 0 */
2009 if ((fd = open_read(EZ_CGIRC)) == -1) /* open config */
2010 strerr_die4sys(111,FATAL,ERR_OPEN,EZ_CGIRC,": ");
2012 if ((fd = open_read(EZ_CGIRC_LOC)) == -1) /* open local config */
2013 strerr_die4sys(111,FATAL,ERR_OPEN,EZ_CGIRC_LOC,": ");
2016 substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf)); /* set up buffer */
2017 /* ##### tainted info #####*/
2019 cmd = env_get("QUERY_STRING"); /* get command */
2020 cppath = env_get("PATH_INFO"); /* get path_info */
2022 if (!cppath || !*cppath) {
2024 cmd += scan_ulong(cmd,&thislistno);
2025 if (*cmd == ':') cmd++; /* allow ':' after ln*/
2030 cppath += scan_ulong(cppath,&thislistno); /* this listno */
2031 if (!thislistno || *cppath++ == '/') {
2032 if (str_start(cppath,"index")) {
2035 if (!thislistno) { /* list index */
2036 drop_priv(0); /* <---- dropping privs */
2042 } /* rest done per list */
2046 if (getln(&ssin,&cfline,&match,'\n') == -1) /* read line */
2047 strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
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*/
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;
2070 cfline.s[pos++] = '\0'; /* .../dir\0 */
2071 break; /* do rest after dropping priv */
2073 close(fd); /* don't accept uid 0*/
2075 drop_priv(0); /* don't trust cgierr. No dir, no chroot */
2076 cgierr("list ",ERR_NOEXIST,"");
2078 if (chdir(dir) == -1) /* chdir listdir */
2079 strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": ");
2080 drop_priv(flagchroot);
2082 /******************************* RELAX **********************************/
2084 /********************* continue to process config line ******************/
2087 if (cfline.s[pos] == '-') {
2091 local = cfline.s + pos;
2092 l = byte_chr(cfline.s + pos, cfline.len - pos,sep); /* ... home */
2093 if (l < cfline.len - pos) { /* optional */
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 */
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 */
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 */
2110 cfline.s[pos++] = '\0';
2111 banner = cfline.s + pos;
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;
2122 pos = + str_rchr(local,'@');
2124 die_syntax("listaddress lacks '@'"); /* require host */
2125 local[pos++] = '\0';
2128 /********************* Accomodate robots and PATH_INFO ****************/
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);
2139 if (cppath && *cppath) { /* /2/msgnum */
2140 flagrobot = 1; /* allow index, but "nofollow" */
2141 scan_ulong(cppath,&msgnum);
2142 } /* dealt with normally */
2144 /********************* Get info from server on BASE etc ****************/
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();
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();
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,'/');
2169 if (!stralloc_cats(&url,cp + pos + 1)) die_nomem();
2170 if (!stralloc_cats(&url,"?")) die_nomem();
2172 if (!stralloc_catb(&url,strnum,fmt_ulong(strnum,thislistno))) die_nomem();
2173 if (!stralloc_cats(&url,":")) die_nomem();
2176 cache = 1; /* don't know if we want to cache */
2178 /****************************** Get command ****************************/
2180 if (msgnum) { /* to support /listno/msgno */
2181 msginfo.target = msgnum;
2182 msginfo.item = ITEM_MESSAGE;
2185 (void) decode_cmd(cmd,&msginfo);
2186 if (!do_cmd(&msginfo))
2187 cgierr("I'm sorry, Dave ... I can't do that, Dave ...","","");
2190 switch (msginfo.item) {
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);
2201 if (!show_object(&msginfo,ITEM_AUTHOR))
2202 cgierr ("I couldn't find the author for that message","","");
2205 if (!show_object(&msginfo,ITEM_SUBJECT))
2206 cgierr ("I couldn't find the subject for that message","","");
2209 if (!show_object(&msginfo,ITEM_DATE)) {
2211 ret = show_object(&msginfo,ITEM_DATE);
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) */
2225 ret = show_index(&msginfo);
2229 strerr_die2x(100,FATAL,"bad item in main");
2232 findlastmsg(&msginfo); /* as last resort; last msgindex */
2234 ret = show_message(&msginfo);