--- /dev/null
+/*$Id: ezmlm-cgi.c,v 1.17 1999/12/24 04:21:26 lindberg Exp $*/
+/*$Name: ezmlm-idx-040 $*/
+
+/* Please leave. Will hopefully help pay for further improvement. */
+#define EZ_CRIGHT "<a href=\"http://www.lindeinc.com\">(c) 1999 Lin-De, Inc</a>"
+/******/
+#include <sys/types.h>
+#include "direntry.h"
+#include "datetime.h"
+#include "now.h"
+#include "stralloc.h"
+#include "strerr.h"
+#include "error.h"
+#include "env.h"
+#include "sig.h"
+#include "open.h"
+#include "getln.h"
+#include "case.h"
+#include "scan.h"
+#include "str.h"
+#include "fmt.h"
+#include "readwrite.h"
+#include "fork.h"
+#include "wait.h"
+#include "exit.h"
+#include "substdio.h"
+#include "getconf.h"
+#include "gen_alloc.h"
+#include "gen_allocdefs.h"
+#include "constmap.h"
+#include "byte.h"
+#include "subscribe.h"
+#include "errtxt.h"
+#include "makehash.h"
+#include "mime.h"
+#include "idx.h"
+#include "yyyymm.h"
+
+#define FATAL "ezmlm-cgi: fatal: "
+#define GET "-getv"
+#define THREAD "-threadv"
+#define SUBSCRIBE "-subscribe"
+#define FAQ "-faq"
+#define TXT_CGI_SUBSCRIBE "\">[eSubscribe]</a>\n"
+#define TXT_CGI_FAQ "\">[eFAQ]</a>\n"
+
+int flagshowhtml = 1; /* show text/html parts. This leads to duplication */
+ /* when both text/plain and text/html are in a */
+ /* multipart/alternative message, but it is assumed*/
+ /* that text/html is not frivolous, but only used */
+ /* when the formatting is important. */
+int flagobscure = 0; /* Don't remove Sender's E-mail address in message */
+ /* view. Overridden by config file (- before list */
+ /* name). */
+
+/**************** Header processing ***********************/
+char headers_used[] = "Subject\\From\\Date\\content-type\\"
+ "content-transfer-encoding\\mime-version";
+/* index of headers displayed (shown in order listed above) */
+int headers_shown[] = {1,1,1,0,0,0};
+/* index of specific headers */
+#define NO_HDRS 6
+#define HDR_SUBJECT 1
+#define HDR_FROM 2
+#define HDR_CT 4
+#define HDR_CTENC 5
+#define HDR_VERSION 6
+
+/* Need to add inits if you increase NO_HDRS */
+stralloc hdr[NO_HDRS] = { {0},{0},{0},{0},{0},{0} };
+/**************** Header processing ***********************/
+
+
+/* index of subject in above, first = 1 */
+
+/* TODO: Sort headers before display. Find a way to display the body with the*/
+/* correct charset, ideally letting the browser do the work (should really */
+/* be able to specify charset for DIV ! */
+
+/* ulong at least 32 bits. (Creating a Year 0xffffff problem ;-) */
+#define MAXULONG 0xffffffff
+
+char cmdstr[5] = "xxx:";
+#define ITEM "-msadiz"
+#define ITEM_MESSAGE 1
+#define ITEM_SUBJECT 2
+#define ITEM_AUTHOR 3
+#define ITEM_DATE 4
+#define ITEM_INDEX 5
+
+#define DIRECT "psnpn"
+#define DIRECT_SAME 0
+#define DIRECT_NEXT 1
+#define DIRECT_PREV -1
+/* use only as the argument for some functions. Terrible hack for date links */
+#define DIRECT_FIRST 3
+#define DIRECT_LAST 2
+
+char *dir = 0;
+char *local = 0;
+char *host = 0;
+char *home = 0;
+char *banner = 0;
+char *charset = 0;
+char *stylesheet = 0;
+char *cmd;
+char strnum[FMT_ULONG];
+/* these are the only headers we really care about for message display */
+/* one can always retrieve the complete message by E-mail */
+stralloc charg = {0};
+stralloc url = {0};
+stralloc author = {0};
+stralloc subject = {0};
+stralloc base = {0};
+stralloc line = {0};
+stralloc decline = {0}; /* for rfc2047-decoded headers and QP/base64 */
+stralloc cfline = {0}; /* from config file */
+stralloc fn = {0};
+stralloc dtline = {0};
+stralloc headers = {0};
+stralloc encoding = {0};
+stralloc content = {0};
+stralloc charsetbase = {0};
+stralloc curcharset = {0};
+stralloc sainit = {0};
+struct constmap headermap;
+unsigned long uid,euid;
+int recursion_level;
+int so = 0;
+int ss23 = 0;
+int state = 0;
+int newlevel;
+int match; /* used everywhere and no overlap */
+int fd; /* same; never >1 open */
+int cache; /* 0 = don't; 1 = don't know; 2 = do */
+int child,wstat;
+int flagtoplevel;
+unsigned int flagmime;
+unsigned int cs,csbase;
+int flagrobot;
+int flagpre;
+int precharcount;
+char cn1 = 0;
+char cn2 = 0;
+char lastjp[] = "B"; /* to get back to the correct JP after line break */
+char *bannerargs[4];
+
+
+mime_info *mime_current = 0;
+mime_info *mime_tmp = 0;
+
+datetime_sec when;
+struct datetime dt;
+
+char inbuf[4096];
+substdio ssin;
+
+void die_nomem() { strerr_die2x(111,FATAL,ERR_NOMEM); }
+
+void die_syntax(char *s)
+{
+ strerr_die4x(100,FATAL,ERR_SYNTAX,"config file: ",s);
+}
+
+char outbuf[4096];
+substdio ssout = SUBSTDIO_FDBUF(write,1,outbuf,sizeof(outbuf));
+
+void oput(register char *s, register unsigned int l)
+/* unbuffered. Avoid extra copy as httpd buffers */
+{
+ if (substdio_put(&ssout,s,l) == -1)
+ strerr_die3sys(111,FATAL,ERR_WRITE,"stdout: ");
+}
+
+void oputs(register char *s)
+{
+ oput(s,str_len(s));
+}
+
+/* this error is for things that happen only if program logic is screwed up */
+void die_prog(char *s) { strerr_die5x(100,FATAL,"program error (please send bug report to bugs@ezmlm.org): ",s," Command: ",cmd); }
+
+/* If we already issued a header than this will look ugly */
+void cgierr(char *s,char *s1,char *s2)
+{
+ strerr_warn4(FATAL,s,s1,s2,(struct strerr *)0);
+ oputs("Content-type: text/plain\n");
+ oputs("Status: 500 Couldn't do it\n\n");
+ oputs("I tried my best, but:\n\n");
+ if (s) oputs(s);
+ if (s1) oputs(s1);
+ if (s2) oputs(s2);
+ oputs("\n");
+ substdio_flush(&ssout);
+ _exit(0);
+}
+
+unsigned long msgnav[5]; /* 0 prev prev 1 prev 2 this 3 next 4 next-next */
+
+struct msginfo { /* clean info on the target message */
+ char item; /* What we want */
+ char direction; /* Relation to current msg */
+ char axis; /* Axis of desired movement [may be calculated] */
+ unsigned long source; /* reference message number */
+ unsigned long target;
+ unsigned long date;
+ unsigned long *authnav; /* msgnav structure */
+ unsigned long *subjnav; /* msgnav structure */
+ char *author;
+ char *subject;
+ char *cgiarg; /* sub/auth as expected from axis */
+} msginfo;
+
+void toggle_flagpre(int flag)
+{
+ flagpre = flag;
+ precharcount = 0;
+ cn1 = 0; cn2 = 0; /* just in case */
+}
+
+unsigned int decode_charset(register char *s, register unsigned int l)
+/* return charset code. CS_BAD means that base charset should be used, i.e. */
+/* that charset is empty or likely invalid. CS_NONE are charsets for which */
+/* we don't need to do anything special. */
+{
+ unsigned int r;
+
+ if (case_startb(s,l,"iso-8859") || case_startb(s,l,"us-ascii") ||
+ case_startb(s,l,"utf")) /* at the moment, we can do utf-8 right */
+ return CS_NONE; /* what is utf-7 (used by OE)? */
+ if (case_startb(s,l,"x-cp") ||
+ case_startb(s,l,"cp") ||
+ case_startb(s,l,"x-mac") ||
+ case_startb(s,l,"koi8")) return CS_NONE;
+ if (!l || *s == 'x' || *s == 'X') return CS_BAD;
+ if (case_startb(s,l,"iso-2022")) {
+ if (case_startb(s+8,l-8,"-cn"))
+ return CS_2022_CN;
+ if (case_startb(s+8,l-8,"-jp"))
+ return CS_2022_JP;
+ return CS_2022_KR;
+ }
+ if (case_startb(s,l,"cn-") ||
+ case_startb(s,l,"hz-gb") ||
+ case_startb(s,l,"gb") ||
+ case_startb(s,l,"big5"))
+ return CS_CN; /* Only consideration for linebreak */
+ if (case_startb(s,l,"iso_8859") ||
+ case_startb(s,l,"latin") ||
+ case_startb(s,l,"windows")) return CS_NONE;
+/* Add other charsets here. Later we will add code to replace a detected */
+/* charset name with another, and to connect conversion routines, such as */
+/* between windows-1251/koi-8r/iso-8859-5 */
+ return CS_BAD;
+}
+
+void htmlencode_put (register char *s,register unsigned int l)
+/* At this time, us-ascii, iso-8859-? create no problems. We just encode */
+/* some html chars. iso-2022 may have these chars as character components.*/
+/* cs is set for these, 3 for CN, 2 for others. Bit 0 set means 2 byte */
+/* chars for SS2/SS3 shiftouts (JP doesn't use them, KR has single byte. */
+/* If cs is set and we're shifted out (so set) we don't substitute. We */
+/* also look for SI/SO to adjust so, and ESC to detect SS2/SS3. Need to */
+/* ignore other ESC seqs correctly. JP doesn't use SI/SO, but uses */
+/* ESC ( B/J and ESC $ B/@ analogously, so we use these to toggle so. */
+/* "Roman", i.e. ESC ( J is treated as ascii - no differences in html- */
+/* relevant chars. Together, this allows us to deal with all iso-2022-* */
+/* as a package. see rfc1468, 1554, 1557, 1922 for more info. */
+/* line break at 84 to avoid splits with lines just a little too long. */
+{
+ if (!cs) { /* us-ascii & iso-8859- & unrecognized */
+ for (;l--;s++) {
+ precharcount++;
+ switch (*s) {
+ case '>': oputs(">"); break;
+ case '<': oputs("<"); break;
+ case '"': oputs("""); break;
+ case '&': oputs("&"); break;
+ case '\n': precharcount = 0; oput(s,1); break;
+ case ' ':
+ if (precharcount >= 84 && flagpre) {
+ oput("\n",1); /* in place of ' ' */
+ precharcount = 0;
+ } else
+ oput(s,1); /* otherwise out with it. */
+ break;
+ default: oput(s,1); break;
+ }
+ }
+ } else if (cs == CS_CN) { /* cn-, gb*, big5 */
+ for (;l--;s++) {
+ precharcount++;
+ if (cn1) { cn2 = cn1; cn1 = 0; } /* this is byte 2 */
+ else { cn2 = 0; cn1 = *s & 0x80; } /* this is byte 1/2 or ascii */
+ if (!cn1 && !cn2) { /* ascii */
+ switch (*s) {
+ case '>': oputs(">"); break;
+ case '<': oputs("<"); break;
+ case '"': oputs("""); break;
+ case '&': oputs("&"); break;
+ case '\n': precharcount = 0; oput(s,1); break;
+ case ' ':
+ if (precharcount >= 84 && flagpre) {
+ oput("\n",1); /* break in ascii sequence */
+ precharcount = 0;
+ } else
+ oput(s,1);
+ break;
+ default: oput(s,1); break;
+ }
+ } else if (precharcount >= 84 && flagpre && cn2) {
+ oput("\n",1); /* break after 2-byte code */
+ precharcount = 0;
+ }
+ }
+ } else { /* iso-2022 => PAIN! */
+ for (;l--;s++) {
+ precharcount++;
+ if (ss23) { /* ss2/ss3 character */
+ ss23--;
+ oput(s,1);
+ continue;
+ }
+ if (so) { /* = 0 ascii, = 1 SO charset */
+ if (!(*s & 0xe0)) { /* ctrl-char */
+ switch (*s) {
+ case ESC: state = 1; break;
+ case SI: so = 0; break;
+ case '\n': precharcount = 0; break;
+ default: break;
+ }
+ }
+ oput(s,1);
+ } else { /* check only ascii */
+ switch (*s) {
+ case '>': oputs(">"); break;
+ case '<': oputs("<"); break;
+ case '"': oputs("""); break;
+ case '&': oputs("&"); break;
+ case ' ':
+ if (precharcount >= 84 && flagpre) {
+ oput("\n",1); /* break in ascii sequence */
+ precharcount = 0;
+ } else
+ oput(s,1);
+ break;
+ default:
+ oput(s,1);
+ if (!(*s & 0xe0)) {
+ switch (*s) {
+ case SO: so = 1; break;
+ case ESC: state = 1; break;
+ case SI: so = 0; break; /* shouldn't happen */
+ case '\n': precharcount = 0; break;
+ default: break;
+ }
+ }
+ }
+ } /* by now all output is done, now ESC interpretation */
+ if (state) {
+ /* ESC code - don't count */
+ if (precharcount) precharcount--;
+ state++;
+ switch (state) {
+ case 2: break; /* this was the ESC */
+ case 3: switch (*s) {
+ case 'N': ss23 = (cs & 1) + 1; state = 0; break;
+ case 'O': ss23 = 2; state = 0; break;
+ case '(': state = 20; so = 0; break; /* JP ascii */
+ case '$': break; /* var S2/SS2/SS3 des*/
+ case '.': state = 10; /* g3 settings, one more char */
+ default: state = 0; break; /* or JP */
+ }
+ break;
+ case 4: switch (*s) { /* s2/ss2/ss3 or JP 2 byte shift */
+ case 'B':
+ case '@': lastjp[0] = *s;
+ so = 1; state = 0; break; /* JP */
+ default: break; /* other SS2/3 des */
+ }
+ break;
+ case 5: state = 0; break; /* 4th char of ESC $ *|+|) X */
+ case 11: state = 0; break; /* 3nd char of ESC . */
+ case 21: state = 0; break; /* ESC ( X for JP */
+ default: die_prog("bad state in htmlencode_put"); break;
+ }
+ } else if (so && flagpre && precharcount >= 84) {
+ /* 84 is nicer than 78/80 since most use GUI browser */
+ /* iso-2022-* line splitter here. SO only, SI done above */
+ /* For JP need even precharcount, add ESC ( B \n ESC $B */
+ if (so && !(precharcount & 1)) { /* even */
+ precharcount = 0; /* reset */
+ if (cs == CS_2022_JP) { /* JP uses ESC like SI/SO */
+ oputs(TOASCII);
+ oput("\n",1);
+ oputs(TOJP);
+ oput(lastjp,1);
+ } else {
+ if (so) {
+ /* For iso-2022-CN: nothing if SI, otherwise SI \n SO */
+ /* For iso-2022-KR same */
+ oputs(SI_LF_SO);
+ } else
+ oput("\n",1);
+ }
+ }
+ }
+ }
+ }
+}
+
+char hexchar[] = "0123456789ABCDEF";
+char enc_url[] = "%00";
+
+void urlencode_put (register char *s,register unsigned int l)
+{
+ for (;l--;s++) {
+ register unsigned char ch;
+ ch = (unsigned char) *s;
+ if (ch <= 32 || ch > 127 || byte_chr("?<>=/:%+#\"",10,ch) != 10) {
+ enc_url[2] = hexchar[ch & 0xf];
+ enc_url[1] = hexchar[(ch >> 4) & 0xf];
+ oput(enc_url,3);
+ } else
+ oput(s,1);
+ }
+}
+
+void urlencode_puts(register char *s)
+{
+ urlencode_put(s,str_len(s));
+}
+
+int checkhash(register char *s)
+{
+ register int l = HASHLEN;
+ while (l--) {
+ if (*s < 'a' || *s > 'p') return 0; /* illegal */
+ s++;
+ }
+ if (*s) return 0; /* extraneous junk */
+ return 1;
+}
+
+int makefn(stralloc *sa,char item, unsigned long n, char *hash)
+{
+ if (!stralloc_copys(sa,"archive/")) die_nomem();
+ if (item == ITEM_MESSAGE) {
+ if (!stralloc_catb(sa,strnum,fmt_ulong(strnum, n / 100))) die_nomem();
+ if (!stralloc_cats(sa,"/")) die_nomem();
+ if (!stralloc_catb(sa,strnum,fmt_uint0(strnum,(unsigned int) (n % 100),2)))
+ die_nomem();
+ } else if (item == ITEM_DATE) {
+ if (!stralloc_cats(sa,"threads/")) die_nomem();
+ if (!stralloc_catb(sa,strnum,fmt_ulong(strnum,n)))
+ die_nomem();
+ } else if (item == ITEM_INDEX) {
+ if (!stralloc_catb(sa,strnum,fmt_ulong(strnum, n / 100))) die_nomem();
+ if (!stralloc_cats(sa,"/index")) die_nomem();
+ } else {
+ if (item == ITEM_AUTHOR) {
+ if (!stralloc_cats(sa,"authors/")) die_nomem();
+ } else {
+ if (!stralloc_cats(sa,"subjects/")) die_nomem();
+ }
+ if (!hash) return 0;
+ if (!stralloc_catb(sa,hash,2)) die_nomem();
+ if (!stralloc_cats(sa,"/")) die_nomem();
+ if (!stralloc_catb(sa,hash+2,HASHLEN-2)) die_nomem();
+ }
+ if (!stralloc_0(sa)) die_nomem();
+ return 1;
+}
+
+void link(struct msginfo *infop,char item,char axis,unsigned long msg,
+ char *data,unsigned int l)
+/* links with targets other msg -> msg. If the link is for author, we */
+/* still supply subject, since most navigation at the message level will */
+/* be along threads rather than author and we don't have an author index.*/
+{
+ char *cp;
+
+ cp = (char *) 0;
+ /* this should be separate routine. Works because all index views */
+ /* have at least a subject link */
+ if (axis == ITEM_SUBJECT && infop->target == msg)
+ oputs("<a name=b></a>");
+ oput(url.s,url.len);
+ cmdstr[0] = ITEM[item];
+ cmdstr[1] = ITEM[axis];
+ cmdstr[2] = DIRECT[DIRECT_SAME + 1];
+ if (item == ITEM_MESSAGE && axis == ITEM_AUTHOR) {
+ if (infop->subject) {
+ cmdstr[1] = ITEM[ITEM_SUBJECT];
+ cp = infop->subject; /* always HASLEN in length due to decode_cmd */
+ }
+ }
+ oputs(cmdstr); /* e.g. map: */
+ oput(strnum,fmt_ulong(strnum,msg));
+ if (!cp && l >= HASHLEN)
+ cp = data;
+ if (infop->date) {
+ oput(":",1);
+ oput(strnum,fmt_ulong(strnum,infop->date));
+ }
+ if (cp) {
+ oput(":",1);
+ oput(cp,HASHLEN);
+ }
+ switch (item) {
+ case ITEM_MESSAGE: oputs("\" class=mlk>"); break;
+ case ITEM_AUTHOR: oputs("#b\" class=alk>"); break;
+ case ITEM_SUBJECT: oputs("#b\" class=slk>"); break;
+ default: oputs("#b\">"); break;
+ }
+ if (HASHLEN + 1 < l)
+ htmlencode_put(data + HASHLEN + 1,l - HASHLEN - 1);
+ else
+ oputs("(none)");
+ oputs("</A>");
+}
+
+void linktoindex(struct msginfo *infop,char item)
+/* for links from message view back to author/subject/threads index */
+{
+ oput(url.s,url.len);
+ cmdstr[0] = ITEM[item];
+ cmdstr[1] = ITEM[item];
+ cmdstr[2] = DIRECT[DIRECT_SAME + 1];
+ oputs(cmdstr); /* e.g. map: */
+ oput(strnum,fmt_ulong(strnum,infop->target));
+ if (infop->date) {
+ oput(":",1);
+ oput(strnum,fmt_ulong(strnum,infop->date));
+ }
+ switch (item) {
+ case ITEM_AUTHOR:
+ if (infop->author) {
+ oput(":",1);
+ oputs(infop->author);
+ }
+ break;
+ case ITEM_SUBJECT:
+ if (infop->subject) {
+ oput(":",1);
+ oputs(infop->subject);
+ }
+ break;
+ default:
+ break;
+ }
+ oputs("#b\"");
+}
+
+void link_msg(struct msginfo *infop,char axis,char direction)
+/* Creates <a href="mapa:123:aaaaa...."> using a maximum of available */
+/* info only for links where the target is a message */
+{
+ unsigned long msg;
+ char *acc;
+ oput(url.s,url.len);
+ cmdstr[0] = ITEM[ITEM_MESSAGE];
+ cmdstr[1] = ITEM[axis];
+ cmdstr[2] = DIRECT[direction + 1];
+ msg = infop->target;
+ acc = 0;
+ switch(axis) {
+ case ITEM_SUBJECT:
+ if (infop->subject)
+ acc = infop->subject;
+ if (infop->subjnav) /* translate to message navigation */
+ if (infop->subjnav[direction]) {
+ msg = infop->subjnav[direction];
+ cmdstr[2] = DIRECT[DIRECT_SAME + 1];
+ }
+ acc = infop->subject;
+ break;
+ case ITEM_AUTHOR:
+ if (infop->author)
+ acc = infop->author;
+ if (infop->authnav) /* translate to message navigation */
+ if (infop->authnav[direction]) {
+ msg = infop->authnav[direction];
+ cmdstr[2] = DIRECT[DIRECT_SAME + 1];
+ }
+ acc = infop->author;
+ break;
+ default:
+ break;
+ }
+ oputs(cmdstr);
+ oput(strnum,fmt_ulong(strnum,msg));
+ if (acc) {
+ oputs(":");
+ oputs(acc);
+ }
+ oputs("\">");
+}
+
+void justpress()
+{
+ oputs("?subject=");
+ urlencode_puts("Just Click \"SEND\"!");
+}
+
+void homelink()
+{
+ register char *cp,*cp1,*cp2;
+
+ if (home && *home) {
+ cp = home;
+ for(;;) {
+ cp1 = cp;
+ while(*cp1 && *cp1 != '=') cp1++;
+ if (!*cp1) break;
+ cp2 = cp1;
+ while(*cp2 && *cp2 != ',') cp2++;
+ oputs("<a href=\"");
+ oput(cp1 + 1,cp2 - cp1 - 1);
+ oputs("\">");
+ oput(cp,cp1 - cp);
+ oputs("</a>\n");
+ if (!*cp2) break;
+ cp = cp2 + 1;
+ }
+ }
+}
+
+void subfaqlinks()
+{
+ oputs("<a href=\"mailto:");
+ oputs(local);
+ oputs(SUBSCRIBE);
+ oputs("@");
+ oputs(host);
+ justpress();
+ oputs(TXT_CGI_SUBSCRIBE);
+ oputs("<a href=\"mailto:");
+ oputs(local);
+ oputs(FAQ);
+ oputs("@");
+ oputs(host);
+ justpress();
+ oputs(TXT_CGI_FAQ);
+}
+
+void msglinks(struct msginfo *infop)
+/* Creates the html for all links from one message view */
+{
+ oputs("<DIV class=msglinks><STRONG>Msg by: ");
+ link_msg(infop,ITEM_SUBJECT,DIRECT_PREV);
+ oputs("[<-</A> ");
+ linktoindex(infop,ITEM_SUBJECT);
+ oputs(">thread</A> ");
+ link_msg(infop,ITEM_SUBJECT,DIRECT_NEXT);
+ oputs("->]</A> \n");
+ link_msg(infop,ITEM_MESSAGE,DIRECT_PREV);
+ oputs("[<-</A> ");
+ linktoindex(infop,ITEM_INDEX);
+ oputs(">time</A> ");
+ link_msg(infop,ITEM_MESSAGE,DIRECT_NEXT);
+ oputs("->]</A> \n");
+ link_msg(infop,ITEM_AUTHOR,DIRECT_PREV);
+ oputs("[<-</A> ");
+ linktoindex(infop,ITEM_AUTHOR);
+ oputs(">author</A> ");
+ link_msg(infop,ITEM_AUTHOR,DIRECT_NEXT);
+ oputs("->]</A> |\n");
+ linktoindex(infop,ITEM_DATE);
+ oputs(">[Threads]</A>\n");
+ homelink();
+ oputs("\n<a href=\"mailto:");
+ oputs(local);
+ oputs(GET);
+ strnum[fmt_ulong(strnum,infop->target)] = '\0';
+ oputs(strnum);
+ oputs("@");
+ oputs(host);
+ justpress();
+ oputs("\">[eMsg]</A>\n");
+ oputs("<a href=\"mailto:");
+ oputs(local);
+ oputs(THREAD);
+ oputs(strnum);
+ oputs("@");
+ oputs(host);
+ justpress();
+ oputs("\">[eThread]</A>\n");
+ subfaqlinks();
+ oputs("</STRONG></DIV>\n");
+}
+
+#define SPC_BASE 1
+#define SPC_BANNER 2
+
+void html_header(char *t,char *s, unsigned int l,char *class,int flagspecial)
+/* flagspecial: 0x1 => robot index; no style sheet, no BASE */
+/* flagspecial: 0x2 => banner, if available */
+{
+ oputs("Content-Type: text/html; charset=");
+ oput(curcharset.s,curcharset.len);
+
+ oputs("\nCache-Control: ");
+ switch (cache) {
+ case 0:
+ oputs("no-cache"); /* known upper border */
+ break;
+ case 1:
+ oputs("max-age=300"); /* 5 min - most lists aren't that fast*/
+ break;
+ case 2:
+ oputs("max-age=1209600"); /* 14 days is a long time */
+ break;
+ }
+ oputs("\n\n");
+ oputs("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\">\n");
+ oputs("<HTML><HEAD>\n<TITLE>");
+ if (local) {
+ oputs(local);
+ oputs("@");
+ oputs(host);
+ oputs(": ");
+ }
+ if (t) oputs(t);
+ if (s) htmlencode_put(s,l);
+ oputs("</TITLE>\n");
+ if (class && *class && stylesheet && *stylesheet) {
+ oputs("<LINK href=\"");
+ oputs(stylesheet);
+ oputs("\" rel=\"stylesheet\" type=\"text/css\">\n");
+ }
+ if (!flagrobot) /* robot access allowed to follow */
+ oputs("<META NAME=\"robots\" CONTENT=\"noindex\">\n");
+ if (flagrobot < 2)
+ oputs("<META NAME=\"robots\" CONTENT=\"nofollow\">\n");
+ if (flagspecial & SPC_BASE)
+ oput(base.s,base.len);
+ oputs("</HEAD>\n");
+ if (class && *class) {
+ oputs("<BODY class=");
+ oputs(class);
+ oputs(">\n");
+ } else
+ oputs("<BODY>\n");
+
+}
+
+void html_footer(int flagspecial)
+{
+ oputs("<HR><DIV class=copyright>");
+ oputs(EZ_CRIGHT);
+ oputs("</DIV>");
+ if ((flagspecial & SPC_BANNER) && banner && *banner) {
+ oputs("<DIV class=banner>\n");
+ if (*banner == '<') oputs(banner);
+ else {
+ substdio_flush(&ssout);
+ sig_pipeignore();
+ bannerargs[0] = banner;
+ bannerargs[1] = host;
+ bannerargs[2] = local;
+ bannerargs[3] = 0;
+ /* We log errors but just complete the page anyway, since we're */
+ /* already committed to output something. */
+ switch(child = fork()) {
+ case -1:
+ strerr_warn3(FATAL,ERR_FORK,"banner program: ",&strerr_sys);
+ break;
+ case 0:
+ execv(*bannerargs,bannerargs);
+ strerr_die3x(100,FATAL,ERR_EXECUTE,"banner program: ");
+ break;
+ }
+ /* parent */
+ wait_pid(&wstat,child);
+ if (wait_crashed(wstat))
+ strerr_warn2(FATAL,ERR_CHILD_CRASHED,(struct strerr *) 0);
+ if (wait_exitcode(wstat))
+ strerr_warn2(FATAL,ERR_CHILD_UNKNOWN,(struct strerr *) 0);
+ }
+ oputs("</DIV>\n");
+ }
+ oputs("</BODY>\n</HTML>\n");
+ substdio_flush(&ssout);
+}
+
+/* DATE functions */
+
+void datelink(struct msginfo *infop,unsigned long d,char direction)
+/* output a date with link back to thread index */
+{
+ oput(url.s,url.len);
+ cmdstr[0] = ITEM[ITEM_DATE];
+ cmdstr[1] = ITEM[ITEM_DATE];
+ cmdstr[2] = DIRECT[direction + 1];
+ oputs(cmdstr);
+ if (direction == DIRECT_LAST)
+ oput("0",1); /* suppress msgnum to avoid going there */
+ else
+ oput(strnum,fmt_ulong(strnum,infop->target));
+ oputs(":");
+ oput(strnum,fmt_ulong(strnum,d));
+ oputs("#b\">");
+ switch (direction) {
+ case DIRECT_SAME:
+ if (dateline(&dtline,d) < 0) die_nomem();
+ oput(dtline.s,dtline.len);
+ break;
+ case DIRECT_PREV:
+ oputs("[<-]");
+ break;
+ case DIRECT_NEXT:
+ oputs("[->]");
+ break;
+ case DIRECT_FIRST:
+ oputs("[<<-]");
+ break;
+ case DIRECT_LAST:
+ oputs("[->>]");
+ break;
+ }
+ oputs("</A>");
+}
+
+void finddate(struct msginfo *infop)
+/* DIRECT_SAME works as DIRECT_PREV, dvs returns previous date or last date */
+{
+ DIR *archivedir;
+ direntry *d;
+ unsigned long ddate, startdate;
+ unsigned long below, above;
+
+ below = 0L;
+ above = MAXULONG; /* creating a Y 0xffffff problem */
+ startdate = infop->date;
+ archivedir = opendir("archive/threads/");
+ if (!archivedir)
+ if (errno != error_noent)
+ strerr_die4sys(111,FATAL,ERR_OPEN,dir,"/archive/threads: ");
+ else
+ strerr_die4sys(100,FATAL,ERR_OPEN,dir,"/archive/threads: ");
+
+ while ((d = readdir(archivedir))) { /* dxxx/ */
+ if (str_equal(d->d_name,".")) continue;
+ if (str_equal(d->d_name,"..")) continue;
+ scan_ulong(d->d_name,&ddate);
+ if (!ddate) continue; /* just in case some smart guy ... */
+ if (startdate) {
+ if (ddate > startdate && ddate < above) above = ddate;
+ if (ddate < startdate && ddate > below) below = ddate;
+ } else {
+ if (ddate < above) above = ddate;
+ if (ddate > below) below = ddate;
+ }
+ }
+ closedir(archivedir);
+
+ if (infop->direction == DIRECT_NEXT && above != MAXULONG || !below)
+ /* we always give a valid date as long as there is at least one */
+ infop->date = above;
+ else
+ infop->date = below;
+ return;
+}
+
+void latestdate(struct msginfo *infop,int flagfail)
+{
+ if (!flagfail) {
+ datetime_tai(&dt,now());
+ infop->date = ((unsigned long) dt.year + 1900) * 100 + dt.mon + 1;
+ } else {
+ infop->date = 0;
+ infop->direction = DIRECT_PREV;
+ finddate(infop);
+ }
+}
+
+void firstdate(struct msginfo *infop,int flagfail)
+{
+ infop->date = 0;
+ infop->direction = DIRECT_NEXT;
+ finddate(infop);
+}
+
+void getdate(struct msginfo *infop,int flagfail)
+/* infop->date has to be 0 or valid on entry. Month outside of [1-12] on */
+/* entry causes GIGO */
+{
+ if (!flagfail) { /* guess */
+ if (infop->direction == DIRECT_NEXT) {
+ infop->date++;
+ if (infop->date % 100 > 12) infop->date += (100 - 12);
+ } else if (infop->direction == DIRECT_PREV) {
+ infop->date--;
+ if (!infop->date % 100) infop->date -= (100 - 12);
+ }
+ } else
+ finddate(infop);
+ return;
+}
+
+indexlinks(struct msginfo *infop)
+{
+ unsigned long tmpmsg;
+
+ tmpmsg = infop->target;
+ infop->target = 1;
+ oputs("<DIV class=idxlinks><STRONG>");
+ linktoindex(infop,ITEM_INDEX);
+ oputs(">[<<-]</A>\n");
+ if (tmpmsg >= 100) infop->target = tmpmsg - 100;
+ linktoindex(infop,ITEM_INDEX);
+ oputs(">[<-]</A>\n");
+ infop->target = tmpmsg + 100;
+ linktoindex(infop,ITEM_INDEX);
+ oputs(">[->]</A>\n");
+ infop->target = MAXULONG;
+ linktoindex(infop,ITEM_INDEX);
+ oputs(">[->>]</A> |\n");
+ infop->target = tmpmsg;
+ linktoindex(infop,ITEM_DATE);
+ oputs(">[Threads by date]</A>\n");
+ subfaqlinks();
+ homelink();
+ oputs("</STRONG></DIV>\n");
+}
+
+int show_index(struct msginfo *infop)
+{
+ unsigned long thismsg;
+ unsigned int pos,l;
+ char ch;
+
+ (void) makefn(&fn,ITEM_INDEX,msginfo.target,"");
+ if ((fd = open_read(fn.s)) == -1)
+ if (errno == error_noent)
+ return 0;
+ else
+ strerr_die4sys(111,FATAL,ERR_OPEN,fn.s,": ");
+ substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
+ if (!stralloc_copyb(&line,strnum,
+ fmt_ulong(strnum,(unsigned long) (infop->target / 100))))
+ die_nomem();
+ if (!stralloc_cats(&line,"xx")) die_nomem();
+ html_header("Messages ",line.s,line.len,"idxbody",SPC_BANNER | SPC_BASE);
+ indexlinks(infop);
+ oputs("<HR><H1 id=\"idxhdr\">");
+ oputs("Messages ");
+ oput(line.s,line.len);
+ oputs("</H1>\n");
+ oputs("<HR><DIV class=idx>\n");
+ for (;;) {
+ if (getln(&ssin,&line,&match,'\n') == -1)
+ strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
+ if (!match)
+ break;
+ pos = scan_ulong(line.s,&thismsg);
+ l = pos;
+ ch = line.s[pos++];
+ pos++;
+ if (line.len < pos + 1 + HASHLEN)
+ strerr_die2x(100,FATAL,"index line with truncated subject entry");
+ if (!stralloc_copyb(&subject,line.s+pos,HASHLEN)) die_nomem();
+ if (!stralloc_0(&subject)) die_nomem();
+ infop->axis = ITEM_SUBJECT;
+ infop->subject = subject.s;
+ oput(strnum,fmt_uint0(strnum,(unsigned int) thismsg % 100,2));
+ oputs(": ");
+ link(infop,ITEM_MESSAGE,ITEM_SUBJECT,thismsg,line.s+pos,line.len - pos - 1);
+ oputs("\n");
+ if (ch == ':') {
+ if (getln(&ssin,&line,&match,'\n') == -1)
+ strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
+ if (!match)
+ break;
+ pos = byte_chr(line.s,line.len,';');
+ if (pos != line.len) {
+ infop->date = date2yyyymm(line.s);
+ oputs("(");
+ link(infop,ITEM_AUTHOR,ITEM_AUTHOR,thismsg,line.s+pos+1,
+ line.len - pos - 2);
+ oputs(")<BR>\n");
+ }
+ }
+ }
+ close(fd);
+ oputs("\n</DIV><HR>\n");
+ indexlinks(infop);
+ html_footer(SPC_BANNER);
+ return 1;
+}
+
+void objectlinks(struct msginfo *infop, char item)
+{
+ oputs("<DIV class=objlinks><STRONG>\n");
+ if (item == ITEM_DATE) {
+ datelink(infop,0,DIRECT_FIRST);
+ datelink(infop,infop->date,DIRECT_PREV);
+ datelink(infop,infop->date,DIRECT_NEXT);
+ datelink(infop,0,DIRECT_LAST);
+ oputs("\n");
+ } else {
+ if (!infop->target) infop->axis = ITEM_DATE;
+ linktoindex(infop,ITEM_DATE);
+ oputs(">[Threads by date]</A>\n");
+ }
+ if (item != ITEM_INDEX) {
+ linktoindex(infop,ITEM_INDEX);
+ oputs(">[Messages by date]</A>\n");
+ }
+ homelink();
+ subfaqlinks();
+ oputs("</STRONG></DIV>\n");
+}
+
+int show_object(struct msginfo *infop,char item)
+/* shows thread, threads, author */
+/* infop has the info needed to access the author/subject/thread file */
+{
+ unsigned long lastdate,thisdate,thismsg;
+ char linkitem;
+ char targetitem;
+ unsigned int pos;
+
+ lastdate = 0L;
+ targetitem = ITEM_MESSAGE; /* default message is target */
+ switch (item) {
+ case ITEM_SUBJECT:
+ if (!makefn(&fn,ITEM_SUBJECT,0L,infop->subject)) return 0;
+ break;
+ case ITEM_AUTHOR:
+ if (!makefn(&fn,ITEM_AUTHOR,0L,infop->author)) return 0;
+ break;
+ case ITEM_DATE:
+ if (!makefn(&fn,ITEM_DATE,infop->date,"")) return 0;
+ break;
+ default:
+ die_prog("Bad object type in show_object");
+ }
+if ((fd = open_read(fn.s)) == -1)
+ if (errno == error_noent)
+ return 0;
+ else
+ strerr_die4sys(111,FATAL,ERR_OPEN,fn.s,": ");
+ substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
+ if (item != ITEM_DATE) {
+ if (getln(&ssin,&line,&match,'\n') == -1) /* read subject */
+ strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
+ if (!match || line.len < HASHLEN + 2)
+ strerr_die4x(111,FATAL,ERR_READ,fn.s,": nothing there");
+ }
+ switch (item) {
+ case ITEM_SUBJECT:
+ html_header("Thread on: ",line.s + HASHLEN + 1,
+ line.len - HASHLEN - 2,"subjbody",SPC_BANNER | SPC_BASE);
+ objectlinks(infop,item);
+ oputs("<HR><H1>On: ");
+ oput(line.s+HASHLEN+1,line.len-HASHLEN-2);
+ oputs("</H1>\n");
+ break;
+ case ITEM_AUTHOR:
+ html_header("Posts by: ",line.s + HASHLEN + 1,
+ line.len - HASHLEN - 2,"authbody",SPC_BANNER | SPC_BASE);
+ objectlinks(infop,item);
+ oputs("<HR><H1>By: ");
+ oput(line.s+HASHLEN+1,line.len-HASHLEN-2);
+ oputs("</H1>\n");
+ break;
+ case ITEM_DATE:
+/* targetitem = ITEM_SUBJECT;*/ /* thread index is target */
+ thisdate = infop->date;
+ if (dateline(&dtline,infop->date) < 0) die_nomem();
+ html_header("Threads for ",
+ dtline.s,dtline.len,"threadsbody",SPC_BANNER | SPC_BASE);
+ objectlinks(infop,item);
+ oputs("<HR><H1>Threads for ");
+ oput(dtline.s,dtline.len);
+ oputs("</H1>\n");
+ break;
+ default: die_prog("unrecognized object type in show_object");
+ }
+
+ oputs("<DIV class=obj>\n");
+ for (;;) {
+ if (getln(&ssin,&line,&match,'\n') == -1) /* read subject */
+ strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
+ if (!match)
+ break;
+ pos = scan_ulong(line.s,&thismsg);
+ if (line.s[pos++] != ':')
+ strerr_die4x(100,FATAL,"entry in ",fn.s," lacks message number");
+ if (item != ITEM_DATE) { /* no date for threads by date */
+ pos += scan_ulong(line.s+pos,&thisdate);
+ infop->date = thisdate;
+ if (line.s[pos++] != ':')
+ strerr_die4x(100,FATAL,"entry in ",fn.s," lacks date");
+ }
+ if (line.len < pos + HASHLEN + 2)
+ strerr_die4x(100,FATAL,"entry in ",fn.s," lacks hash");
+ if (thisdate != lastdate) {
+ if (!lastdate)
+ oputs("<UL>\n");
+ else
+ oputs("<P>");
+ oputs("<LI><H2>");
+ datelink(infop,thisdate,DIRECT_SAME);
+ lastdate = thisdate;
+ oputs("</H2>\n");
+ }
+ if (item == ITEM_SUBJECT)
+ linkitem = ITEM_AUTHOR;
+ else
+ linkitem = ITEM_SUBJECT;
+ link(infop,targetitem,linkitem,thismsg,line.s+pos,line.len - pos - 1);
+ oputs("<BR>\n");
+ }
+ close(fd);
+ oputs("</UL>\n");
+ if (!infop->target)
+ oputs("<a name=b></a>");
+ oputs("<HR></DIV>\n");
+ objectlinks(infop,item);
+ html_footer(SPC_BANNER);
+ return 1;
+}
+
+void clear_mime()
+{
+ mime_current->charset.len = 0; /* exist but need emptying */
+ mime_current->boundary.len = 0;
+ mime_current->ctype.len = 0;
+ mime_current->mimetype = MIME_NONE;
+ mime_current->ctenc = CTENC_NONE;
+ mime_current->cs = CS_NONE;
+}
+
+void new_mime()
+{
+ mime_tmp = mime_current;
+ if (mime_current)
+ mime_current = mime_current->next;
+ if (!mime_current) {
+ if (!(mime_current = (mime_info *) alloc(sizeof (mime_info))))
+ die_nomem();
+ mime_current->charset = sainit; /* init */
+ mime_current->boundary = sainit;
+ mime_current->ctype = sainit;
+ mime_current->next = (mime_info *) 0;
+ mime_current->previous = mime_tmp;
+ }
+ clear_mime();
+ if (mime_tmp)
+ mime_current->level = mime_tmp->level + 1;
+ else
+ mime_current->level = 1;
+}
+
+void mime_getarg(stralloc *sa,char **s, unsigned int *l)
+/* copies next token or "token" into sa and sets s & l appropriately */
+/* for continuing the search */
+{
+ char *cp, *cpafter, *cpnext;
+
+ if (!*l || !**s) return;
+ if (**s == '"') {
+ (*s)++; (*l)--;
+ cp = *s; cpnext = cp + *l; cpafter = cpnext;
+ while (cp < cpafter) {
+ if (*cp == '"') {
+ break;
+ }
+ cp++;
+ }
+ cpnext = cp;
+ } else {
+ cp = *s; cpnext = cp + *l; cpafter = cpnext;
+ while (cp < cpafter) {
+ if (*cp == ' ' || *cp == '\t' || *cp == '\n' || *cp == ';') {
+ break;
+ }
+ cp++;
+ }
+ cpnext = cp;
+ }
+ if (!stralloc_copyb(sa,*s,cp - *s)) die_nomem();
+ *l = cpafter - cpnext; /* always >= 0 */
+ *s = cpnext;
+ return;
+}
+
+void decode_mime_type(char *s,unsigned int l,unsigned int flagmime)
+{
+ char *st;
+ unsigned int r,lt;
+ if (!flagmime || !l) { /* treat non-MIME as plain text */
+ mime_current->mimetype = MIME_TEXT_PLAIN;
+ if (!stralloc_copys(&curcharset,charset)) die_nomem();
+ /* should be us-ascii, but this is very likely better */
+ return;
+ }
+ r = MIME_APPLICATION_OCTETSTREAM;
+ while (l && (*s == ' ' || *s == '\t')) { s++; l--; } /* skip LWSP */
+ mime_getarg(&(mime_current->ctype),&s,&l);
+ st = mime_current->ctype.s;
+ lt = mime_current->ctype.len;
+ if (case_startb(st,lt,"text")) { /* text types */
+ r = MIME_TEXT; st+= 4; lt-= 4;
+ if (case_startb(st,lt,"/plain")) {
+ r = MIME_TEXT_PLAIN; st+= 6; lt-= 6;
+ } else if (case_startb(st,lt,"/html")) {
+ r = MIME_TEXT_HTML; st+= 5; lt-= 5;
+ } else if (case_startb(st,lt,"/enriched")) {
+ r = MIME_TEXT_ENRICHED; st+= 9; lt-= 9;
+ } else if (case_startb(st,lt,"/x-vcard")) {
+ r = MIME_TEXT_ENRICHED; st+= 8; lt-= 8;
+ }
+ } else if (case_startb(st,lt,"multipart")) { /* multipart types */
+ r = MIME_MULTI; st += 9; lt-= 9;
+ if (case_startb(st,lt,"/alternative")) {
+ r = MIME_MULTI_ALTERNATIVE; st+= 12; lt-= 12;
+ } else if (case_startb(st,lt,"/mixed")) {
+ r = MIME_MULTI_MIXED; st+= 6; lt-= 6;
+ } else if (case_startb(st,lt,"/digest")) {
+ r = MIME_MULTI_DIGEST; st+= 7; lt-= 7;
+ } else if (case_startb(st,lt,"/signed")) {
+ r = MIME_MULTI_SIGNED; st+= 7; lt-= 7;
+ }
+ } else if (case_startb(st,lt,"message")) { /* message types */
+ r = MIME_MESSAGE; st += 7; lt -= 7;
+ if (case_startb(st,lt,"/rfc822")) {
+ r = MIME_MESSAGE_RFC822; st+= 7; lt-= 7;
+ }
+ }
+ mime_current->mimetype = r;
+ while (l) {
+ while (l && (*s == ' ' || *s == '\t' || *s == ';' || *s == '\n')) {
+ s++; l--; } /* skip ;LWSP */
+ if (case_startb(s,l,"boundary=")) {
+ s += 9; l-= 9;
+ mime_getarg(&(mime_current->boundary),&s,&l);
+ } else if (case_startb(s,l,"charset=")) {
+ s += 8; l-= 8;
+ mime_getarg(&(mime_current->charset),&s,&l);
+ cs = decode_charset(mime_current->charset.s,
+ mime_current->charset.len);
+ if (cs == CS_BAD) cs = csbase; /* keep base cs */
+ else
+ if (!stralloc_copy(&curcharset,&mime_current->charset)) die_nomem();
+ } else { /* skip non LWSP */
+ for (;;) {
+ if (!l) break;
+ if (*s == '"') {
+ s++, l--;
+ while (l && *s != '"') { s++, l--; }
+ if (l) { s++, l--; }
+ break;
+ } else {
+ if (!l || *s == ' ' || *s == '\t' || *s == '\n') break;
+ s++; l--;
+ }
+ }
+ }
+ }
+ return;
+}
+
+void decode_transfer_encoding(register char *s,register unsigned int l)
+{
+ unsigned int r;
+ mime_current->ctenc = CTENC_NONE;
+ if (!l || (mime_current->mimetype & MIME_MULTI)) return;
+ /* base64/QP ignored for multipart */
+ r = CTENC_NONE;
+ while (l && (*s == ' ' || *s == '\t')) { s++; l--; } /* skip LWSP */
+ if (case_startb(s,l,"quoted-printable")) {
+ r = CTENC_QP;
+ } else if (case_startb(s,l,"base64")) {
+ r = CTENC_BASE64;
+ }
+ mime_current->ctenc = r;
+ return;
+}
+
+int check_boundary()
+/* return 0 if no boundary, 1 if start, 2 if end */
+{
+ mime_info *tmp;
+
+ if (*line.s != '-' || line.s[1] != '-') return 0;
+ tmp = mime_current;
+ while (tmp) {
+ if (tmp->boundary.len) {
+ if (line.len > tmp->boundary.len + 2 &&
+ !case_diffb(line.s+2,tmp->boundary.len,tmp->boundary.s)) {
+ if (line.s[tmp->boundary.len + 2] == '-' &&
+ line.s[tmp->boundary.len + 3] == '-') { /* end */
+ mime_current = tmp;
+ clear_mime();
+ return 2;
+
+ } else { /* start */
+ mime_current = tmp;
+ new_mime();
+ return 1;
+ }
+ }
+ }
+ tmp = tmp->previous;
+ }
+ if (!stralloc_copys(&curcharset,charset)) die_nomem();
+ /* suprtfluous since header done by now */
+ cs = csbase;
+ return 0;
+}
+
+void start_message_page(struct msginfo *infop)
+/* header etc for message. Delayed to collect subject so that we can put */
+/* that in TITLE. This in turn needed for good looking robot index. */
+/* Yep, not pretty, but it works and it's abhorrent to seek()/rewind */
+/* and another hack: it's hard to mix charsets within a doc. So, we disp */
+/* messages entirely in the charset of the message. This is ok, since */
+/* headers will be us-ascii or have encoded segments usually matching */
+/* the charset in the message. Of course, we should be able to used e.g. */
+/* <DIV charset=iso-2022-jp> with internal resources as well as internal */
+/* ones. One might make other-charset messages external resources as well*/
+/* Now, the problem is that we need to "preview" MIME info _before_ */
+/* seeing the start boundary. */
+{
+ if (!stralloc_copyb(&decline,strnum,fmt_ulong(strnum,infop->target)))
+ die_nomem();
+ if (!stralloc_cats(&decline,":")) die_nomem();
+ if (!stralloc_0(&decline)) die_nomem();
+ decodeHDR(hdr[HDR_SUBJECT - 1].s,hdr[HDR_SUBJECT - 1].len,&line,"",FATAL);
+ if (!mime_current)
+ new_mime(); /* allocate */
+ else
+ clear_mime();
+ decode_mime_type(hdr[HDR_CT - 1].s,hdr[HDR_CT - 1].len,
+ hdr[HDR_VERSION - 1].len);
+ html_header(decline.s,line.s,line.len - 1,
+ "msgbody",SPC_BASE);
+ msglinks(infop);
+ oputs("<DIV class=message>\n");
+}
+
+void show_part(struct msginfo *infop,int flagshowheaders,
+ int flagskip,int flagstartseen)
+/* if flagshowheaders we display headers, otherwise not */
+/* if flagstartseen we've already see the start boundary for this part, */
+/* if not we'll ignore what's there up to it */
+/* if flagskip we skip this part */
+{
+ char *cp;
+ int flaginheader;
+ int whatheader;
+ int flaggoodfield;
+ int flaghtml;
+ int btype,i;
+ unsigned int colpos,l,pos;
+ char linetype;
+
+ flaginheader = 1;
+ for (i = 0; i < NO_HDRS; i++) hdr[i].len = 0;
+ flaggoodfield = 1;
+ match = 1;
+ recursion_level++; /* one up */
+ for (;;) {
+ if (!match) return;
+ if (getln(&ssin,&line,&match,'\n') == -1)
+ strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
+ if (!match) return;
+ if ((btype = check_boundary())) {
+ if (flagpre) {
+ oputs("</PRE>");
+ toggle_flagpre(0);
+ }
+ if (mime_current->level < recursion_level) {
+ return;
+ }
+ if (btype == 1) {
+ flagstartseen = 1;
+ flaggoodfield = 1;
+ flaginheader = 1;
+ } else
+ flagstartseen = 0;
+ continue;
+ }
+ if (!flagstartseen) continue; /* skip to start */
+ if (flaginheader) {
+ if (line.len == 1) {
+ if (flagshowheaders) { /* rfc822hdr only */
+ if (flagtoplevel)
+ start_message_page(infop); /* so we can put subj in TITLE */
+ oputs("<DIV class=rfc822hdr><HR>\n");
+ for (i = 0; i < NO_HDRS; i++) {
+ if (!hdr[i].len || !headers_shown[i]) continue;
+ if (i == HDR_SUBJECT - 1 && flagtoplevel)
+ oputs("<SPAN class=subject>");
+ oputs("<EM>");
+ oputs(constmap_get(&headermap,i + 1));
+ oputs(":</EM>");
+ decodeHDR(hdr[i].s,hdr[i].len,&line,"",FATAL);
+ if (i == HDR_SUBJECT - 1 && flagtoplevel) {
+ oputs("<A class=relk href=\"mailto:");
+ oputs(local);
+ oput("@",1);
+ oputs(host);
+ oputs("?subject=");
+ urlencode_put(line.s + 1,line.len - 2);
+ oputs("\">");
+ }
+ if (flagobscure && i == HDR_FROM - 1) {
+ oputs(" ");
+ decodeHDR(cp,author_name(&cp,line.s,line.len),&decline,"",FATAL);
+ htmlencode_put(decline.s,decline.len);
+ } else {
+ decodeHDR(hdr[i].s,hdr[i].len,&decline,"",FATAL);
+ htmlencode_put(decline.s,decline.len - 1);
+ }
+ if (i == HDR_SUBJECT - 1 && flagtoplevel)
+ oputs("</A></SPAN>");
+ oputs("\n<BR>");
+ }
+ oputs("</DIV>\n");
+ }
+ flaginheader = 0;
+ flagtoplevel = 0;
+ flaggoodfield = 1;
+ flaghtml = 0;
+ if (!flagmime)
+ flagmime = hdr[HDR_VERSION - 1].len; /* MIME-Version header */
+ decode_mime_type(hdr[HDR_CT - 1].s,hdr[HDR_CT - 1].len,flagmime);
+ decode_transfer_encoding(hdr[HDR_CTENC - 1].s,hdr[HDR_CTENC - 1].len);
+ content.len = 0; encoding.len = 0;
+ switch (mime_current->mimetype) {
+ case MIME_MULTI_SIGNED:
+ case MIME_MULTI_MIXED:
+ case MIME_MULTI_ALTERNATIVE:
+ case MIME_MULTI_DIGEST:
+ show_part(infop,0,0,0);
+ recursion_level--;
+ flagstartseen = 0;
+ flaginheader = 1;
+ continue;
+ case MIME_MESSAGE_RFC822:
+ oputs("\n<PRE>");
+ toggle_flagpre(1);
+ flagshowheaders = 1;
+ flaginheader = 1;
+ flagmime = 0; /* need new MIME-Version header */
+ continue;
+ case MIME_TEXT_HTML:
+ if (flagshowhtml) {
+ oputs("<HR>\n");
+ flaghtml = 1;
+ } else {
+ oputs("<strong>[\"");
+ oput(mime_current->ctype.s,mime_current->ctype.len);
+ oputs("\" not shown]</strong>\n");
+ flaggoodfield = 0; /* hide */
+ }
+ continue;
+ case MIME_TEXT_PLAIN:
+ case MIME_TEXT: /* in honor of Phil using "text" on */
+ case MIME_NONE: /* the qmail list and rfc2045:5.2 */
+ oputs("<HR>\n<PRE>\n");
+ toggle_flagpre(1);
+ continue;
+ case MIME_TEXT_VCARD:
+ default: /* application/octetstream...*/
+ oputs("<HR><strong>[\"");
+ oput(mime_current->ctype.s,mime_current->ctype.len);
+ oputs("\" not shown]</strong>\n");
+ flaggoodfield = 0; /* hide */
+ continue;
+ }
+ } else if (line.s[0] != ' ' && line.s[0] != '\t') {
+ linetype = ' ';
+ flaggoodfield = 0;
+ colpos = byte_chr(line.s,line.len,':');
+ if ((whatheader = constmap_index(&headermap,line.s,colpos))) {
+ flaggoodfield = 1;
+ if (!stralloc_copyb(&hdr[whatheader - 1],line.s + colpos + 1,
+ line.len - colpos - 1)) die_nomem();
+ }
+ } else {
+ if (whatheader)
+ if (!stralloc_catb(&hdr[whatheader - 1],line.s,line.len))
+ die_nomem();
+ }
+ } else {
+ if (flaggoodfield) {
+ if (mime_current->ctenc) {
+ if (!stralloc_copy(&decline,&line)) die_nomem();
+ line.len = 0;
+ if (mime_current->ctenc == CTENC_QP)
+ decodeQ(decline.s,decline.len,&line);
+ else
+ decodeB(decline.s,decline.len,&line);
+ }
+ if (flaghtml)
+ oput(line.s,line.len);
+ else {
+ htmlencode_put(line.s,line.len); /* body */
+ }
+ }
+ }
+ }
+}
+
+int show_message(struct msginfo *infop)
+{
+ char *psz;
+
+ if(!stralloc_copys(&headers,(char *) headers_used)) die_nomem();
+ if (!stralloc_0(&headers)) die_nomem();
+ psz = headers.s;
+ while (*psz) {
+ if (*psz == '\\') *psz = '\0';
+ ++psz;
+ }
+ if (!constmap_init(&headermap,headers.s,headers.len,0))
+ die_nomem();
+
+ (void) makefn(&fn,ITEM_MESSAGE,msginfo.target,"");
+ if ((fd = open_read(fn.s)) == -1)
+ if (errno == error_noent)
+ return 0;
+ else
+ strerr_die4sys(111,FATAL,ERR_OPEN,fn.s,": ");
+ substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
+ toggle_flagpre(0);
+ recursion_level = 0; /* recursion level for show_part */
+ flagmime = 0; /* no active mime */
+ flagtoplevel = 1; /* top message/rfc822 get special rx */
+ new_mime(); /* initiate a MIME info storage slot */
+
+ show_part(infop,1,0,1); /* do real work, including html header etc */
+ if (flagpre)
+ oputs("</PRE>\n");
+ close(fd);
+ oputs("<HR></DIV>\n");
+ msglinks(infop);
+ html_footer(0);
+ return 1;
+}
+
+char decode_item(char ch)
+{
+ switch (ch) {
+ case 'm': return ITEM_MESSAGE;
+ case 'a': return ITEM_AUTHOR ;
+ case 's': return ITEM_SUBJECT;
+ case 'd': return ITEM_DATE ;
+ case 'i': return ITEM_INDEX ;
+ default: cgierr("Navigation command contains ",
+ "illegal item code","");
+ }
+ return 0; /* never reached */
+}
+
+char decode_direction(char ch)
+{
+ switch (ch) {
+ case 's': return DIRECT_SAME;
+ case 'n': return DIRECT_NEXT;
+ case 'p': return DIRECT_PREV;
+ default: cgierr("Navigation command contains ",
+ "illegal direction code","");
+ }
+ return 0; /* never reached */
+}
+
+int decode_cmd(char *s,struct msginfo *infop)
+/* decodes s into infop. Assures that no security problems slip through by */
+/* checking everything */
+/* commands xyd:123[:abc]. x what we want, y is the axis, d the direction. */
+/* 123 is the current message number. abc is a date/subject/author hash, */
+/* depending on axis, or empty if not available. */
+/* returns: 0 no command+msgnum. */
+/* 1 empty or at least cmd + msgnum. */
+/* Guarantee: Only legal values accepted */
+{
+ register char ch;
+
+ infop->source = 0L;
+ infop->date = 0L;
+ infop->author = (char *)0;
+ infop->subject = (char *)0;
+ infop->cgiarg = (char *)0;
+
+ if (!s || !*s) { /* main index */
+ infop->item = ITEM_DATE;
+ infop->axis = ITEM_DATE;
+ infop->direction = DIRECT_SAME;
+ latestdate(&msginfo,0);
+ infop->target = MAXULONG;
+ return 1;
+ }
+ ch = *(s++);
+ if (ch >= '0' && ch <= '9') { /* numeric - simplified cmd: msgnum ... */
+ s--;
+ infop->item = ITEM_MESSAGE;
+ infop->axis = ITEM_MESSAGE;
+ infop->direction = DIRECT_SAME;
+ } else { /* what:axis:direction:msgnum ... */
+ infop->item = decode_item(ch);
+ ch = *(s++);
+ infop->axis = decode_item(ch);
+ ch = *(s++);
+ infop->direction = decode_direction(ch);
+ if (*(s++) != ':') return 0;
+ }
+ s+= scan_ulong(s,&(infop->source));
+ if (*(s++) != ':') return 0;
+ if (*s >= '0' && *s <= '9') { /* numeric nav hint [date] */
+ s+= scan_ulong(s,&(infop->date));
+ if (!*s++) return 1; /* skip any char - should be ':' unless NUL */
+ }
+ if (checkhash(s)) { /* Ignore if illegal rather than complaining*/
+ if (!stralloc_copyb(&charg,s,HASHLEN)) die_nomem();
+ if (!stralloc_0(&charg)) die_nomem();
+ infop->cgiarg = charg.s;
+ }
+ return 1;
+}
+
+int msg2hash(struct msginfo *infop)
+{
+ unsigned int pos;
+ unsigned long tmpmsg;
+
+ if (!infop->source) die_prog("source is 0 in msg2hash");
+ (void) makefn(&fn,ITEM_INDEX,infop->source,"");
+ if ((fd = open_read(fn.s)) == -1) {
+ if (errno == error_noent)
+ return 0;
+ else
+ strerr_die4sys(111,FATAL,ERR_OPEN,fn.s,": ");
+ }
+ substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
+ for (;;) {
+ if (getln(&ssin,&line,&match,'\n') == -1)
+ strerr_die3sys(111,FATAL,ERR_READ,"index: ");
+ if (!match)
+ return 0; /* didn't find message */
+ if (*line.s == '\t') continue; /* author line */
+ pos = scan_ulong(line.s,&tmpmsg);
+ if (tmpmsg == infop->source) {
+ if (line.s[pos++] != ':' || line.s[pos++] != ' ')
+ strerr_die3x(100,ERR_SYNTAX,fn.s,": missing subject separator");
+ if (line.len < HASHLEN + pos)
+ strerr_die3x(100,ERR_SYNTAX,fn.s,": missing subject hash");
+ if (!stralloc_copyb(&subject,line.s+pos,HASHLEN)) die_nomem();
+ if (!stralloc_0(&subject)) die_nomem();
+ infop->subject = subject.s;
+ if (getln(&ssin,&line,&match,'\n') == -1)
+ strerr_die3sys(111,FATAL,ERR_READ,"index: ");
+ if (!match)
+ strerr_die3x(100,ERR_SYNTAX,fn.s,
+ ": author info missing. Truncated?");
+ pos = byte_chr(line.s,line.len,';');
+ if (pos == line.len)
+ strerr_die3x(100,ERR_SYNTAX,fn.s,"missing ';' after date");
+ if (pos > 1)
+ infop->date = date2yyyymm(line.s+1); /* ';' marks end ok */
+ pos++;
+ if (line.len < HASHLEN + pos)
+ strerr_die3x(100,ERR_SYNTAX,fn.s,": missing author hash");
+ if (!stralloc_copyb(&author,line.s+pos,HASHLEN)) die_nomem();
+ if (!stralloc_0(&author)) die_nomem();
+ infop->author = author.s;
+ close(fd);
+ return 1; /* success */
+ }
+ }
+ close(fd);
+ return 0; /* failed to match */
+}
+
+void setmsg(struct msginfo *infop)
+/* Reads the file corresponding to infop->axis and assumes fn.s is set */
+/* correctly for this. Sets up a msgnav structure and links it in */
+/* correction for axis=author/subject. For axis=date it supports also */
+/* direction=DIRECT_FIRST which will return the first message of the */
+/* first thread in the date file. DIRECT_LAST is not supported. */
+/* DIRECT_FIRST is supported ONLY for date. */
+{
+ if (infop->direction == DIRECT_SAME) {
+ infop->target = infop->source;
+ return;
+ }
+ if ((fd = open_read(fn.s)) == -1) {
+ if (errno == error_noent)
+ strerr_die4x(100,FATAL,ERR_OPEN,fn.s,
+ " in listmsgs. Rerun ezmlm-archive!");
+ else
+ strerr_die4sys(111,FATAL,ERR_OPEN,fn.s,": ");
+ }
+ substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
+ if (infop->source != ITEM_DATE) {
+ if (getln(&ssin,&line,&match,'\n') == -1) /* first line */
+ strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
+ if (!match)
+ strerr_die3x(100,ERR_SYNTAX,fn.s,": first line missing");
+ }
+ msgnav[3] = 0L; /* next */
+ msgnav[4] = 0L; /* after */
+ infop->target = 0L;
+ for (;;) {
+ if (getln(&ssin,&line,&match,'\n') == -1)
+ strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
+ if (!match) break;
+ msgnav[0] = msgnav[1];
+ msgnav[1] = msgnav[2];
+ (void) scan_ulong(line.s,&(msgnav[2]));
+ if (infop->direction == DIRECT_FIRST) break;
+ if (msgnav[2] == infop->source) {
+ if (getln(&ssin,&line,&match,'\n') == -1)
+ strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
+ if (!match) break;
+ (void) scan_ulong(line.s,&(msgnav[3]));
+ if (getln(&ssin,&line,&match,'\n') == -1)
+ strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
+ if (!match) break;
+ (void) scan_ulong(line.s,&(msgnav[4]));
+ break;
+ }
+ }
+ close(fd);
+ switch (infop->axis) {
+ case ITEM_AUTHOR:
+ infop->authnav = msgnav + 2 + infop->direction;
+ infop->target = *(infop->authnav);
+ infop->subject = (char *)0; /* what we know is not for this msg */
+ infop->date = 0;
+ break;
+ case ITEM_SUBJECT:
+ infop->subjnav = msgnav + 2 + infop->direction;
+ infop->target = *(infop->subjnav);
+ infop->author = (char *)0; /* what we know is not for this msg */
+ infop->date = 0;
+ break;
+ case ITEM_DATE:
+ infop->target = msgnav[2];
+ infop->subject = (char *)0; /* what we know is not for this msg */
+ infop->author = (char *)0; /* what we know is not for this msg */
+ default:
+ die_prog("Bad item in setmsg");
+ }
+ return;
+}
+
+void auth2msg(struct msginfo *infop)
+{
+ if (!infop->author) die_prog("no such author in authmsg");
+ if (!makefn(&fn,ITEM_AUTHOR,0L,infop->author)) die_prog("auth2msg");
+ setmsg(infop);
+}
+
+void subj2msg(struct msginfo *infop)
+{
+ if (!infop->subject) die_prog("no such subject in subj2msg");
+ if (!makefn(&fn,ITEM_SUBJECT,0L,infop->subject)) die_prog("subj2msg");
+ setmsg(infop);
+}
+
+void date2msg(struct msginfo *infop)
+{
+ (void) makefn(&fn,ITEM_DATE,infop->date,"");
+ setmsg(infop);
+}
+
+void findlastmsg(struct msginfo *infop)
+{
+ if (!getconf_line(&line,"num",dir,0,FATAL))
+ cgierr("Sorry, there are no messages in the archive","","");
+ if (!stralloc_0(&line)) die_nomem();
+ (void) scan_ulong(line.s,&(infop->target));
+}
+
+int do_cmd(struct msginfo *infop)
+/* interprets msginfo to create msginfo. Upon return, msginfo can be trusted */
+/* to have all info needed, and that all info is correct. There may be more */
+/* info than needed. This can be used to build more specific links. NOTE: */
+/* there is no guarantee that a message meeting the criteria actually exists*/
+{
+ infop->target = infop->source;
+
+ switch (infop->item) {
+ case ITEM_MESSAGE: /* we want to get a message back */
+ {
+ switch (infop->axis) {
+ case ITEM_MESSAGE:
+ if (infop->direction == DIRECT_SAME)
+ break;
+ else if (infop->direction == DIRECT_NEXT)
+ (infop->target)++;
+ else { /* previous */
+ cache = 2;
+ if (infop->target >= 2)
+ (infop->target)--;
+ else
+ infop->target = 1;
+ }
+ break;
+ case ITEM_AUTHOR:
+ infop->author = infop->cgiarg;
+ if (!infop->author) /* we don't know author hash */
+ if (!msg2hash(infop)) return 0;
+ auth2msg(infop);
+ break;
+ case ITEM_SUBJECT:
+ infop->subject = infop->cgiarg;
+ if (!infop->subject) /* we don't know Subject hash */
+ if (!msg2hash(infop)) return 0;
+ subj2msg(infop);
+ break;
+ }
+ break;
+ }
+ case ITEM_AUTHOR:
+ switch (infop->axis) {
+ case ITEM_MESSAGE:
+ if (!infop->author)
+ if (!msg2hash(infop)) return 0;
+ break;
+ case ITEM_AUTHOR:
+ infop->author = infop->cgiarg;
+ if (!infop->author)
+ if (!msg2hash(infop)) return 0;
+ auth2msg(infop);
+ break;
+ case ITEM_SUBJECT:
+ infop->subject = infop->cgiarg;
+ if (!infop->subject) /* we don't know Subject hash */
+ if (!msg2hash(infop)) return 0;
+ subj2msg(infop);
+ break;
+ }
+ break;
+ case ITEM_SUBJECT:
+ switch (infop->axis) {
+ case ITEM_MESSAGE:
+ if (!msg2hash(infop)) return 0;
+ break;
+ case ITEM_AUTHOR:
+ infop->author = infop->cgiarg;
+ if (!infop->author)
+ if (!msg2hash(infop)) return 0;
+ auth2msg(infop);
+ break;
+ case ITEM_SUBJECT:
+ infop->subject = infop->cgiarg;
+ if (!infop->subject) /* we don't know Subject hash */
+ if (!msg2hash(infop)) return 0;
+ subj2msg(infop);
+ break;
+ }
+ break;
+ case ITEM_DATE: /* want a date reference */
+ switch (infop->axis) {
+ case ITEM_MESSAGE:
+ case ITEM_AUTHOR:
+ case ITEM_SUBJECT:
+ case ITEM_DATE:
+ if (!infop->date && infop->source)
+ if (!msg2hash(infop)) return 0;
+ getdate(infop,0);
+ break;
+ }
+ break;
+ case ITEM_INDEX: /* ignore direction etc - only for index */
+ if (!infop->target)
+ infop->target = infop->source;
+ if (!infop->target)
+ findlastmsg(infop);
+ break;
+ }
+ return 1;
+}
+
+void list_lists()
+{
+ unsigned long lno;
+ cache = 2;
+ flagrobot = 2;
+ html_header("Robot index of lists",0,0,0,0);
+ for (;;) {
+ if (getln(&ssin,&cfline,&match,'\n') == -1) /* read line */
+ strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
+ if (!match)
+ break;
+ if (cfline.s[0] == '#') continue; /* skip comment */
+ cfline.s[cfline.len - 1] = '\0'; /* so all are sz */
+ (void) scan_ulong(cfline.s,&lno); /* listno for line */
+ if (lno) { /* don't expose default list */
+ oputs("<A href=\"");
+ oput(strnum,fmt_ulong(strnum,lno));
+ oputs("/index\">[link]</a>\n");
+ }
+ }
+ html_footer(0);
+}
+
+void list_list(unsigned long listno)
+/* Make one link [for list_set()] per set of 100 archive messages. */
+/* Assumption: Any directory DIR/archive/xxx where 'xxx' is a numeric,*/
+/* is part of the list archive and has in it an index file and one */
+/* or more messages. */
+{
+ DIR *archivedir;
+ direntry *d;
+ unsigned long msgset;
+
+ flagrobot = 2;
+ strnum[fmt_ulong(strnum,listno)] = '\0';
+ archivedir = opendir("archive/");
+ if (!archivedir)
+ if (errno != error_noent)
+ strerr_die4sys(111,FATAL,ERR_OPEN,dir,"/archive: ");
+ else
+ strerr_die4sys(100,FATAL,ERR_OPEN,dir,"/archive: ");
+
+ cache = 1;
+ html_header("Robot index for message sets in list",0,0,0,0);
+
+ while ((d = readdir(archivedir))) {
+ if (d->d_name[scan_ulong(d->d_name,&msgset)])
+ continue; /* not numeric */
+ oputs("<a href=\"../"); /* from /ezcgi/0/index to /ezcgi/listno/index*/
+ oputs(strnum);
+ oputs("/index/");
+ oputs(d->d_name);
+ oputs("\">[link]</a>\n");
+ }
+ closedir(archivedir);
+ html_footer(0);
+}
+
+void list_set(unsigned long listno,unsigned long msgset)
+{
+ unsigned int msgfirst,msgmax;
+ unsigned long lastset;
+
+ flagrobot = 2;
+ findlastmsg(&msginfo);
+ if (!stralloc_copys(&line,"<a href=\"../")) die_nomem();
+ if (!stralloc_catb(&line,strnum,fmt_ulong(strnum,msgset))) die_nomem();
+ lastset = msginfo.target / 100;
+ cache = 2;
+ msgfirst = 0;
+ if (!msgset)
+ msgfirst = 1;
+ msgmax = 99;
+ if (msgset > lastset) { /* assure empty list */
+ msgmax = 0;
+ msgfirst = 1;
+ } else if (msgset == lastset) {
+ cache = 0; /* still changing */
+ msgmax = msginfo.target % 100;
+ }
+ html_header("Robot index for messages in set",0,0,0,0);
+ while (msgfirst <= msgmax) {
+ oput(line.s,line.len);
+ oput(strnum,fmt_uint0(strnum,msgfirst,2));
+ oputs("\">[link]</a>\n");
+ msgfirst++;
+ }
+ html_footer(0);
+}
+
+/**************** MAY BE SUID ROOT HERE ****************************/
+void drop_priv(int flagchroot)
+{
+ if (!uid) strerr_die2x(100,FATAL,ERR_SUID); /* not as root */
+ if (!euid) {
+ if (flagchroot)
+ if (chroot(dir) == -1) /* chroot listdir */
+ strerr_die4sys(111,FATAL,"failed to chroot ",dir,": ");
+ if (setuid(uid) == -1) /* setuid */
+ strerr_die2sys(111,FATAL,ERR_SETUID);
+ }
+ euid = (unsigned long) geteuid();
+ if (!euid) strerr_die2x(100,FATAL,ERR_SUID); /* setuid didn't do it*/
+}
+/*******************************************************************/
+
+int main(argc,argv)
+int argc;
+char **argv;
+{
+ char *cp,*cppath;
+ unsigned long listno,thislistno,tmpuid,msgset;
+ unsigned long msgnum = 0;
+ unsigned long port = 0L;
+ unsigned long tmptarget;
+ unsigned int pos,l;
+ int flagindex = 0;
+ int flagchroot = 1; /* chroot listdir if SUID root */
+ int ret;
+ char sep;
+
+/******************** we may be SUID ROOT ******************************/
+ uid = (unsigned long) getuid(); /* should be http */
+ euid = (unsigned long) geteuid(); /* chroot only if 0 */
+
+ if (!euid) {
+ if ((fd = open_read(EZ_CGIRC)) == -1) /* open config */
+ strerr_die4sys(111,FATAL,ERR_OPEN,EZ_CGIRC,": ");
+ } else {
+ if ((fd = open_read(EZ_CGIRC_LOC)) == -1) /* open local config */
+ strerr_die4sys(111,FATAL,ERR_OPEN,EZ_CGIRC_LOC,": ");
+ }
+
+ substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf)); /* set up buffer */
+ /* ##### tainted info #####*/
+
+ cmd = env_get("QUERY_STRING"); /* get command */
+ cppath = env_get("PATH_INFO"); /* get path_info */
+
+ if (!cppath || !*cppath) {
+ if (cmd && *cmd) {
+ cmd += scan_ulong(cmd,&thislistno);
+ if (*cmd == ':') cmd++; /* allow ':' after ln*/
+ } else
+ thislistno = 0L;
+ } else {
+ cppath++;
+ cppath += scan_ulong(cppath,&thislistno); /* this listno */
+ if (!thislistno || *cppath++ == '/') {
+ if (str_start(cppath,"index")) {
+ cppath += 5;
+ flagindex = 1;
+ if (!thislistno) { /* list index */
+ drop_priv(0); /* <---- dropping privs */
+ list_lists();
+ close(fd);
+ _exit(0);
+ }
+ }
+ } /* rest done per list */
+ }
+
+ for (;;) {
+ if (getln(&ssin,&cfline,&match,'\n') == -1) /* read line */
+ strerr_die4sys(111,FATAL,ERR_READ,fn.s,": ");
+ if (!match)
+ break;
+ if (*cfline.s == '#' || cfline.len == 1) continue; /* skip comment/blank */
+ cfline.s[cfline.len - 1] = '\0'; /* so all are sz */
+ pos = scan_ulong(cfline.s,&listno); /* listno for line */
+ if (thislistno != listno) continue;
+ sep = cfline.s[pos++];
+ if (cfline.s[pos] == '-') { /* no chroot if -uid*/
+ flagchroot = 0;
+ pos++;
+ }
+ pos += scan_ulong(cfline.s+pos,&tmpuid); /* listno for line */
+ if (tmpuid) uid = tmpuid; /* override default */
+ if (!cfline.s[pos++] == sep)
+ die_syntax("missing separator after user id");
+ if (cfline.s[pos] != '/')
+ die_syntax("dir"); /* absolute path */
+ l = byte_chr(cfline.s + pos, cfline.len - pos,sep);
+ if (l == cfline.len - pos) /* listno:path:...*/
+ die_syntax("missing separator after path");
+ dir = cfline.s + pos;
+ pos += l;
+ cfline.s[pos++] = '\0'; /* .../dir\0 */
+ break; /* do rest after dropping priv */
+ }
+ close(fd); /* don't accept uid 0*/
+ if (!dir) {
+ drop_priv(0); /* don't trust cgierr. No dir, no chroot */
+ cgierr("list ",ERR_NOEXIST,"");
+ }
+ if (chdir(dir) == -1) /* chdir listdir */
+ strerr_die4sys(111,FATAL,ERR_SWITCH,dir,": ");
+ drop_priv(flagchroot);
+
+/******************************* RELAX **********************************/
+
+/********************* continue to process config line ******************/
+
+ flagrobot = 0;
+ if (cfline.s[pos] == '-') {
+ flagobscure = 1;
+ pos++;
+ }
+ local = cfline.s + pos;
+ l = byte_chr(cfline.s + pos, cfline.len - pos,sep); /* ... home */
+ if (l < cfline.len - pos) { /* optional */
+ pos += l;
+ cfline.s[pos++] = '\0';
+ home = cfline.s + pos;
+ l = byte_chr(cfline.s + pos, cfline.len - pos,sep); /* ... charset */
+ if (l < cfline.len - pos) { /* optional */
+ pos += l;
+ cfline.s[pos++] = '\0';
+ charset = cfline.s + pos;
+ l = byte_chr(cfline.s+pos,cfline.len - pos,sep); /* ... stylesheet */
+ if (l < cfline.len - pos) { /* optional */
+ pos += l;
+ cfline.s[pos++] = '\0';
+ stylesheet = cfline.s + pos;
+ l = byte_chr(cfline.s+pos,cfline.len-pos,sep); /* ... bannerURL */
+ if (l < cfline.len - pos) { /* optional */
+ pos += l;
+ cfline.s[pos++] = '\0';
+ banner = cfline.s + pos;
+ }
+ }
+ }
+ }
+ if (!charset || !*charset) /* rfc822 default */
+ charset = EZ_CHARSET;
+ if (!stralloc_copys(&curcharset,charset)) die_nomem();
+ csbase = decode_charset(curcharset.s,curcharset.len);
+ if (csbase == CS_BAD) csbase = CS_NONE;
+ cs = csbase;
+ pos = + str_rchr(local,'@');
+ if (!local[pos])
+ die_syntax("listaddress lacks '@'"); /* require host */
+ local[pos++] = '\0';
+ host = local + pos;
+
+/********************* Accomodate robots and PATH_INFO ****************/
+
+ if (flagindex) {
+ if (*(cppath++) == '/') { /* /2/index/123 */
+ cppath += scan_ulong(cppath,&msgset);
+ list_set(thislistno,msgset);
+ } else /* /2/index */
+ list_list(thislistno);
+ _exit(0);
+ }
+
+ if (cppath && *cppath) { /* /2/msgnum */
+ flagrobot = 1; /* allow index, but "nofollow" */
+ scan_ulong(cppath,&msgnum);
+ } /* dealt with normally */
+
+/********************* Get info from server on BASE etc ****************/
+
+ if (!stralloc_copys(&base,"<BASE href=\"http://")) die_nomem();
+ cp = env_get("SERVER_PORT");
+ if (cp) { /* port */
+ (void) scan_ulong(cp,&port);
+ if ((unsigned int) port == 443) { /* https: */
+ if (!stralloc_copys(&base,"<BASE href=\"https://")) die_nomem();
+ }
+ }
+ if (port && (unsigned int) port != 80 && (unsigned int) port != 443) {
+ if (!stralloc_cats(&base,":")) die_nomem();
+ if (!stralloc_catb(&base,strnum,fmt_ulong(strnum,port))) die_nomem();
+ }
+ if (!(cp = env_get("HTTP_HOST")))
+ if (!(cp = env_get("SERVER_NAME")))
+ strerr_die2x(100,FATAL,"both HTTP_HOST and SERVER_NAME are empty");
+ if (!stralloc_cats(&base,cp)) die_nomem();
+ if (!(cp = env_get("SCRIPT_NAME")))
+ strerr_die2x(100,FATAL,"empty SCRIPT_NAME");
+ if (!stralloc_cats(&base,cp)) die_nomem();
+ if (!stralloc_cats(&base,"\">\n")) die_nomem();
+ if (!stralloc_copys(&url,"<A HREF=\"")) die_nomem();
+ pos = str_rchr(cp,'/');
+ if (cp[pos])
+ if (!stralloc_cats(&url,cp + pos + 1)) die_nomem();
+ if (!stralloc_cats(&url,"?")) die_nomem();
+ if (thislistno) {
+ if (!stralloc_catb(&url,strnum,fmt_ulong(strnum,thislistno))) die_nomem();
+ if (!stralloc_cats(&url,":")) die_nomem();
+ }
+
+ cache = 1; /* don't know if we want to cache */
+
+/****************************** Get command ****************************/
+
+ if (msgnum) { /* to support /listno/msgno */
+ msginfo.target = msgnum;
+ msginfo.item = ITEM_MESSAGE;
+ cache = 2;
+ } else {
+ (void) decode_cmd(cmd,&msginfo);
+ if (!do_cmd(&msginfo))
+ cgierr("I'm sorry, Dave ... I can't do that, Dave ...","","");
+ }
+
+ switch (msginfo.item) {
+ case ITEM_MESSAGE:
+ if (!show_message(&msginfo)) { /* assume next exists ... */
+ cache = 0; /* border cond. - no cache */
+ msginfo.target = msginfo.source; /* show same */
+ msginfo.subjnav = 0;
+ msginfo.authnav = 0;
+ ret = show_message(&msginfo);
+ }
+ break;
+ case ITEM_AUTHOR:
+ if (!show_object(&msginfo,ITEM_AUTHOR))
+ cgierr ("I couldn't find the author for that message","","");
+ break;
+ case ITEM_SUBJECT:
+ if (!show_object(&msginfo,ITEM_SUBJECT))
+ cgierr ("I couldn't find the subject for that message","","");
+ break;
+ case ITEM_DATE:
+ if (!show_object(&msginfo,ITEM_DATE)) {
+ finddate(&msginfo);
+ ret = show_object(&msginfo,ITEM_DATE);
+ }
+ break;
+ case ITEM_INDEX:
+ if (!show_index(&msginfo)) {
+ tmptarget = msginfo.target;
+ findlastmsg(&msginfo);
+ cache = 0; /* latest one - no cache */
+ if (!msginfo.target || msginfo.target > tmptarget) {
+ cache = 2; /* won't change */
+ firstdate(&msginfo,1); /* first thread index */
+ msginfo.direction = DIRECT_FIRST;
+ date2msg(&msginfo); /* (may not be 1 if parts removed) */
+ }
+ ret = show_index(&msginfo);
+ }
+ break;
+ default:
+ strerr_die2x(100,FATAL,"bad item in main");
+ }
+ if (!ret) {
+ findlastmsg(&msginfo); /* as last resort; last msgindex */
+ cache = 0;
+ ret = show_message(&msginfo);
+ }
+
+ _exit(0);
+}