X-Git-Url: https://git.distorted.org.uk/~mdw/ezmlm/blobdiff_plain/5b62e993b0af39700031c2875d7f6654e6a02850..f8beb284087c279acfb30506f5bb32baa4949b44:/ezmlm-cgi.c diff --git a/ezmlm-cgi.c b/ezmlm-cgi.c new file mode 100644 index 0000000..9549aed --- /dev/null +++ b/ezmlm-cgi.c @@ -0,0 +1,2238 @@ +/*$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 "(c) 1999 Lin-De, Inc" +/******/ +#include +#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]\n" +#define TXT_CGI_FAQ "\">[eFAQ]\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(""); + 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(""); +} + +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 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(""); + oput(cp,cp1 - cp); + oputs("\n"); + if (!*cp2) break; + cp = cp2 + 1; + } + } +} + +void subfaqlinks() +{ + oputs("Msg by: "); + link_msg(infop,ITEM_SUBJECT,DIRECT_PREV); + oputs("[<- "); + linktoindex(infop,ITEM_SUBJECT); + oputs(">thread "); + link_msg(infop,ITEM_SUBJECT,DIRECT_NEXT); + oputs("->] \n"); + link_msg(infop,ITEM_MESSAGE,DIRECT_PREV); + oputs("[<- "); + linktoindex(infop,ITEM_INDEX); + oputs(">time "); + link_msg(infop,ITEM_MESSAGE,DIRECT_NEXT); + oputs("->] \n"); + link_msg(infop,ITEM_AUTHOR,DIRECT_PREV); + oputs("[<- "); + linktoindex(infop,ITEM_AUTHOR); + oputs(">author "); + link_msg(infop,ITEM_AUTHOR,DIRECT_NEXT); + oputs("->] |\n"); + linktoindex(infop,ITEM_DATE); + oputs(">[Threads]\n"); + homelink(); + oputs("\ntarget)] = '\0'; + oputs(strnum); + oputs("@"); + oputs(host); + justpress(); + oputs("\">[eMsg]\n"); + oputs("[eThread]\n"); + subfaqlinks(); + oputs("\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("\n"); + oputs("\n"); + if (local) { + oputs(local); + oputs("@"); + oputs(host); + oputs(": "); + } + if (t) oputs(t); + if (s) htmlencode_put(s,l); + oputs("\n"); + if (class && *class && stylesheet && *stylesheet) { + oputs("\n"); + } + if (!flagrobot) /* robot access allowed to follow */ + oputs("\n"); + if (flagrobot < 2) + oputs("\n"); + if (flagspecial & SPC_BASE) + oput(base.s,base.len); + oputs("\n"); + if (class && *class) { + oputs("\n"); + } else + oputs("\n"); + +} + +void html_footer(int flagspecial) +{ + oputs("
"); + if ((flagspecial & SPC_BANNER) && banner && *banner) { + oputs("\n"); + } + oputs("\n\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(""); +} + +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("\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("

"); + oputs("Messages "); + oput(line.s,line.len); + oputs("

\n"); + oputs("
\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(")
\n"); + } + } + } + close(fd); + oputs("\n

\n"); + indexlinks(infop); + html_footer(SPC_BANNER); + return 1; +} + +void objectlinks(struct msginfo *infop, char item) +{ + oputs("\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("

On: "); + oput(line.s+HASHLEN+1,line.len-HASHLEN-2); + oputs("

\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("

By: "); + oput(line.s+HASHLEN+1,line.len-HASHLEN-2); + oputs("

\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("

Threads for "); + oput(dtline.s,dtline.len); + oputs("

\n"); + break; + default: die_prog("unrecognized object type in show_object"); + } + + oputs("
\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("\n"); + if (!infop->target) + oputs(""); + oputs("
\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. */ +/*
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("
\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(""); + 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("

\n"); + for (i = 0; i < NO_HDRS; i++) { + if (!hdr[i].len || !headers_shown[i]) continue; + if (i == HDR_SUBJECT - 1 && flagtoplevel) + oputs(""); + oputs(""); + oputs(constmap_get(&headermap,i + 1)); + oputs(":"); + decodeHDR(hdr[i].s,hdr[i].len,&line,"",FATAL); + if (i == HDR_SUBJECT - 1 && flagtoplevel) { + 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(""); + oputs("\n
"); + } + oputs("
\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
");
+		toggle_flagpre(1);
+		flagshowheaders = 1;
+		flaginheader = 1;
+		flagmime = 0;		/* need new MIME-Version header */
+		continue;
+	  case MIME_TEXT_HTML:
+		if (flagshowhtml) {
+		  oputs("
\n"); + flaghtml = 1; + } else { + oputs("[\""); + oput(mime_current->ctype.s,mime_current->ctype.len); + oputs("\" not shown]\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("
\n
\n");
+		toggle_flagpre(1);
+		continue;
+	  case MIME_TEXT_VCARD:
+	  default:		/* application/octetstream...*/
+		oputs("
[\""); + oput(mime_current->ctype.s,mime_current->ctype.len); + oputs("\" not shown]\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("
\n"); + close(fd); + oputs("
\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("[link]\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("d_name); + oputs("\">[link]\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," 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]\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,"\n")) die_nomem(); + if (!stralloc_copys(&url," 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); +}