1 /*$Id: ezmlm-cgi.c,v 1.17 1999/12/24 04:21:26 lindberg Exp $*/
2 /*$Name: ezmlm-idx-040 $*/
4 /* Please leave. Will hopefully help pay for further improvement. */
5 #define EZ_CRIGHT "<a href=\"http://www.lindeinc.com\">(c) 1999 Lin-De, Inc</a>"
22 #include "readwrite.h"
28 #include "gen_alloc.h"
29 #include "gen_allocdefs.h"
32 #include "subscribe.h"
39 #define FATAL "ezmlm-cgi: fatal: "
41 #define THREAD "-threadv"
42 #define SUBSCRIBE "-subscribe"
44 #define TXT_CGI_SUBSCRIBE "\">[eSubscribe]</a>\n"
45 #define TXT_CGI_FAQ "\">[eFAQ]</a>\n"
47 int flagshowhtml
= 1; /* show text/html parts. This leads to duplication */
48 /* when both text/plain and text/html are in a */
49 /* multipart/alternative message, but it is assumed*/
50 /* that text/html is not frivolous, but only used */
51 /* when the formatting is important. */
52 int flagobscure
= 0; /* Don't remove Sender's E-mail address in message */
53 /* view. Overridden by config file (- before list */
56 /**************** Header processing ***********************/
57 char headers_used
[] = "Subject\\From\\Date\\content-type\\"
58 "content-transfer-encoding\\mime-version";
59 /* index of headers displayed (shown in order listed above) */
60 int headers_shown
[] = {1,1,1,0,0,0};
61 /* index of specific headers */
69 /* Need to add inits if you increase NO_HDRS */
70 stralloc hdr
[NO_HDRS
] = { {0},{0},{0},{0},{0},{0} };
71 /**************** Header processing ***********************/
74 /* index of subject in above, first = 1 */
76 /* TODO: Sort headers before display. Find a way to display the body with the*/
77 /* correct charset, ideally letting the browser do the work (should really */
78 /* be able to specify charset for DIV ! */
80 /* ulong at least 32 bits. (Creating a Year 0xffffff problem ;-) */
81 #define MAXULONG 0xffffffff
83 char cmdstr
[5] = "xxx:";
84 #define ITEM "-msadiz"
85 #define ITEM_MESSAGE 1
86 #define ITEM_SUBJECT 2
91 #define DIRECT "psnpn"
94 #define DIRECT_PREV -1
95 /* use only as the argument for some functions. Terrible hack for date links */
96 #define DIRECT_FIRST 3
105 char *stylesheet
= 0;
107 char strnum
[FMT_ULONG
];
108 /* these are the only headers we really care about for message display */
109 /* one can always retrieve the complete message by E-mail */
110 stralloc charg
= {0};
112 stralloc author
= {0};
113 stralloc subject
= {0};
116 stralloc decline
= {0}; /* for rfc2047-decoded headers and QP/base64 */
117 stralloc cfline
= {0}; /* from config file */
119 stralloc dtline
= {0};
120 stralloc headers
= {0};
121 stralloc encoding
= {0};
122 stralloc content
= {0};
123 stralloc charsetbase
= {0};
124 stralloc curcharset
= {0};
125 stralloc sainit
= {0};
126 struct constmap headermap
;
127 unsigned long uid
,euid
;
133 int match
; /* used everywhere and no overlap */
134 int fd
; /* same; never >1 open */
135 int cache
; /* 0 = don't; 1 = don't know; 2 = do */
138 unsigned int flagmime
;
139 unsigned int cs
,csbase
;
145 char lastjp
[] = "B"; /* to get back to the correct JP after line break */
149 mime_info
*mime_current
= 0;
150 mime_info
*mime_tmp
= 0;
158 void die_nomem() { strerr_die2x(111,FATAL
,ERR_NOMEM
); }
160 void die_syntax(char *s
)
162 strerr_die4x(100,FATAL
,ERR_SYNTAX
,"config file: ",s
);
166 substdio ssout
= SUBSTDIO_FDBUF(write
,1,outbuf
,sizeof(outbuf
));
168 void oput(register char *s
, register unsigned int l
)
169 /* unbuffered. Avoid extra copy as httpd buffers */
171 if (substdio_put(&ssout
,s
,l
) == -1)
172 strerr_die3sys(111,FATAL
,ERR_WRITE
,"stdout: ");
175 void oputs(register char *s
)
180 /* this error is for things that happen only if program logic is screwed up */
181 void die_prog(char *s
) { strerr_die5x(100,FATAL
,"program error (please send bug report to bugs@ezmlm.org): ",s
," Command: ",cmd
); }
183 /* If we already issued a header than this will look ugly */
184 void cgierr(char *s
,char *s1
,char *s2
)
186 strerr_warn4(FATAL
,s
,s1
,s2
,(struct strerr
*)0);
187 oputs("Content-type: text/plain\n");
188 oputs("Status: 500 Couldn't do it\n\n");
189 oputs("I tried my best, but:\n\n");
194 substdio_flush(&ssout
);
198 unsigned long msgnav
[5]; /* 0 prev prev 1 prev 2 this 3 next 4 next-next */
200 struct msginfo
{ /* clean info on the target message */
201 char item
; /* What we want */
202 char direction
; /* Relation to current msg */
203 char axis
; /* Axis of desired movement [may be calculated] */
204 unsigned long source
; /* reference message number */
205 unsigned long target
;
207 unsigned long *authnav
; /* msgnav structure */
208 unsigned long *subjnav
; /* msgnav structure */
211 char *cgiarg
; /* sub/auth as expected from axis */
214 void toggle_flagpre(int flag
)
218 cn1
= 0; cn2
= 0; /* just in case */
221 unsigned int decode_charset(register char *s
, register unsigned int l
)
222 /* return charset code. CS_BAD means that base charset should be used, i.e. */
223 /* that charset is empty or likely invalid. CS_NONE are charsets for which */
224 /* we don't need to do anything special. */
228 if (case_startb(s
,l
,"iso-8859") || case_startb(s
,l
,"us-ascii") ||
229 case_startb(s
,l
,"utf")) /* at the moment, we can do utf-8 right */
230 return CS_NONE
; /* what is utf-7 (used by OE)? */
231 if (case_startb(s
,l
,"x-cp") ||
232 case_startb(s
,l
,"cp") ||
233 case_startb(s
,l
,"x-mac") ||
234 case_startb(s
,l
,"koi8")) return CS_NONE
;
235 if (!l
|| *s
== 'x' || *s
== 'X') return CS_BAD
;
236 if (case_startb(s
,l
,"iso-2022")) {
237 if (case_startb(s
+8,l
-8,"-cn"))
239 if (case_startb(s
+8,l
-8,"-jp"))
243 if (case_startb(s
,l
,"cn-") ||
244 case_startb(s
,l
,"hz-gb") ||
245 case_startb(s
,l
,"gb") ||
246 case_startb(s
,l
,"big5"))
247 return CS_CN
; /* Only consideration for linebreak */
248 if (case_startb(s
,l
,"iso_8859") ||
249 case_startb(s
,l
,"latin") ||
250 case_startb(s
,l
,"windows")) return CS_NONE
;
251 /* Add other charsets here. Later we will add code to replace a detected */
252 /* charset name with another, and to connect conversion routines, such as */
253 /* between windows-1251/koi-8r/iso-8859-5 */
257 void htmlencode_put (register char *s
,register unsigned int l
)
258 /* At this time, us-ascii, iso-8859-? create no problems. We just encode */
259 /* some html chars. iso-2022 may have these chars as character components.*/
260 /* cs is set for these, 3 for CN, 2 for others. Bit 0 set means 2 byte */
261 /* chars for SS2/SS3 shiftouts (JP doesn't use them, KR has single byte. */
262 /* If cs is set and we're shifted out (so set) we don't substitute. We */
263 /* also look for SI/SO to adjust so, and ESC to detect SS2/SS3. Need to */
264 /* ignore other ESC seqs correctly. JP doesn't use SI/SO, but uses */
265 /* ESC ( B/J and ESC $ B/@ analogously, so we use these to toggle so. */
266 /* "Roman", i.e. ESC ( J is treated as ascii - no differences in html- */
267 /* relevant chars. Together, this allows us to deal with all iso-2022-* */
268 /* as a package. see rfc1468, 1554, 1557, 1922 for more info. */
269 /* line break at 84 to avoid splits with lines just a little too long. */
271 if (!cs
) { /* us-ascii & iso-8859- & unrecognized */
275 case '>': oputs(">"); break;
276 case '<': oputs("<"); break;
277 case '"': oputs("""); break;
278 case '&': oputs("&"); break;
279 case '\n': precharcount
= 0; oput(s
,1); break;
281 if (precharcount
>= 84 && flagpre
) {
282 oput("\n",1); /* in place of ' ' */
285 oput(s
,1); /* otherwise out with it. */
287 default: oput(s
,1); break;
290 } else if (cs
== CS_CN
) { /* cn-, gb*, big5 */
293 if (cn1
) { cn2
= cn1
; cn1
= 0; } /* this is byte 2 */
294 else { cn2
= 0; cn1
= *s
& 0x80; } /* this is byte 1/2 or ascii */
295 if (!cn1
&& !cn2
) { /* ascii */
297 case '>': oputs(">"); break;
298 case '<': oputs("<"); break;
299 case '"': oputs("""); break;
300 case '&': oputs("&"); break;
301 case '\n': precharcount
= 0; oput(s
,1); break;
303 if (precharcount
>= 84 && flagpre
) {
304 oput("\n",1); /* break in ascii sequence */
309 default: oput(s
,1); break;
311 } else if (precharcount
>= 84 && flagpre
&& cn2
) {
312 oput("\n",1); /* break after 2-byte code */
316 } else { /* iso-2022 => PAIN! */
319 if (ss23
) { /* ss2/ss3 character */
324 if (so
) { /* = 0 ascii, = 1 SO charset */
325 if (!(*s
& 0xe0)) { /* ctrl-char */
327 case ESC
: state
= 1; break;
328 case SI
: so
= 0; break;
329 case '\n': precharcount
= 0; break;
334 } else { /* check only ascii */
336 case '>': oputs(">"); break;
337 case '<': oputs("<"); break;
338 case '"': oputs("""); break;
339 case '&': oputs("&"); break;
341 if (precharcount
>= 84 && flagpre
) {
342 oput("\n",1); /* break in ascii sequence */
351 case SO
: so
= 1; break;
352 case ESC
: state
= 1; break;
353 case SI
: so
= 0; break; /* shouldn't happen */
354 case '\n': precharcount
= 0; break;
359 } /* by now all output is done, now ESC interpretation */
361 /* ESC code - don't count */
362 if (precharcount
) precharcount
--;
365 case 2: break; /* this was the ESC */
366 case 3: switch (*s
) {
367 case 'N': ss23
= (cs
& 1) + 1; state
= 0; break;
368 case 'O': ss23
= 2; state
= 0; break;
369 case '(': state
= 20; so
= 0; break; /* JP ascii */
370 case '$': break; /* var S2/SS2/SS3 des*/
371 case '.': state
= 10; /* g3 settings, one more char */
372 default: state
= 0; break; /* or JP */
375 case 4: switch (*s
) { /* s2/ss2/ss3 or JP 2 byte shift */
377 case '@': lastjp
[0] = *s
;
378 so
= 1; state
= 0; break; /* JP */
379 default: break; /* other SS2/3 des */
382 case 5: state
= 0; break; /* 4th char of ESC $ *|+|) X */
383 case 11: state
= 0; break; /* 3nd char of ESC . */
384 case 21: state
= 0; break; /* ESC ( X for JP */
385 default: die_prog("bad state in htmlencode_put"); break;
387 } else if (so
&& flagpre
&& precharcount
>= 84) {
388 /* 84 is nicer than 78/80 since most use GUI browser */
389 /* iso-2022-* line splitter here. SO only, SI done above */
390 /* For JP need even precharcount, add ESC ( B \n ESC $B */
391 if (so
&& !(precharcount
& 1)) { /* even */
392 precharcount
= 0; /* reset */
393 if (cs
== CS_2022_JP
) { /* JP uses ESC like SI/SO */
400 /* For iso-2022-CN: nothing if SI, otherwise SI \n SO */
401 /* For iso-2022-KR same */
412 char hexchar
[] = "0123456789ABCDEF";
413 char enc_url
[] = "%00";
415 void urlencode_put (register char *s
,register unsigned int l
)
418 register unsigned char ch
;
419 ch
= (unsigned char) *s
;
420 if (ch
<= 32 || ch
> 127 || byte_chr("?<>=/:%+#\"",10,ch
) != 10) {
421 enc_url
[2] = hexchar
[ch
& 0xf];
422 enc_url
[1] = hexchar
[(ch
>> 4) & 0xf];
429 void urlencode_puts(register char *s
)
431 urlencode_put(s
,str_len(s
));
434 int checkhash(register char *s
)
436 register int l
= HASHLEN
;
438 if (*s
< 'a' || *s
> 'p') return 0; /* illegal */
441 if (*s
) return 0; /* extraneous junk */
445 int makefn(stralloc
*sa
,char item
, unsigned long n
, char *hash
)
447 if (!stralloc_copys(sa
,"archive/")) die_nomem();
448 if (item
== ITEM_MESSAGE
) {
449 if (!stralloc_catb(sa
,strnum
,fmt_ulong(strnum
, n
/ 100))) die_nomem();
450 if (!stralloc_cats(sa
,"/")) die_nomem();
451 if (!stralloc_catb(sa
,strnum
,fmt_uint0(strnum
,(unsigned int) (n
% 100),2)))
453 } else if (item
== ITEM_DATE
) {
454 if (!stralloc_cats(sa
,"threads/")) die_nomem();
455 if (!stralloc_catb(sa
,strnum
,fmt_ulong(strnum
,n
)))
457 } else if (item
== ITEM_INDEX
) {
458 if (!stralloc_catb(sa
,strnum
,fmt_ulong(strnum
, n
/ 100))) die_nomem();
459 if (!stralloc_cats(sa
,"/index")) die_nomem();
461 if (item
== ITEM_AUTHOR
) {
462 if (!stralloc_cats(sa
,"authors/")) die_nomem();
464 if (!stralloc_cats(sa
,"subjects/")) die_nomem();
467 if (!stralloc_catb(sa
,hash
,2)) die_nomem();
468 if (!stralloc_cats(sa
,"/")) die_nomem();
469 if (!stralloc_catb(sa
,hash
+2,HASHLEN
-2)) die_nomem();
471 if (!stralloc_0(sa
)) die_nomem();
475 void link(struct msginfo
*infop
,char item
,char axis
,unsigned long msg
,
476 char *data
,unsigned int l
)
477 /* links with targets other msg -> msg. If the link is for author, we */
478 /* still supply subject, since most navigation at the message level will */
479 /* be along threads rather than author and we don't have an author index.*/
484 /* this should be separate routine. Works because all index views */
485 /* have at least a subject link */
486 if (axis
== ITEM_SUBJECT
&& infop
->target
== msg
)
487 oputs("<a name=b></a>");
489 cmdstr
[0] = ITEM
[item
];
490 cmdstr
[1] = ITEM
[axis
];
491 cmdstr
[2] = DIRECT
[DIRECT_SAME
+ 1];
492 if (item
== ITEM_MESSAGE
&& axis
== ITEM_AUTHOR
) {
493 if (infop
->subject
) {
494 cmdstr
[1] = ITEM
[ITEM_SUBJECT
];
495 cp
= infop
->subject
; /* always HASLEN in length due to decode_cmd */
498 oputs(cmdstr
); /* e.g. map: */
499 oput(strnum
,fmt_ulong(strnum
,msg
));
500 if (!cp
&& l
>= HASHLEN
)
504 oput(strnum
,fmt_ulong(strnum
,infop
->date
));
511 case ITEM_MESSAGE
: oputs("\" class=mlk>"); break;
512 case ITEM_AUTHOR
: oputs("#b\" class=alk>"); break;
513 case ITEM_SUBJECT
: oputs("#b\" class=slk>"); break;
514 default: oputs("#b\">"); break;
517 htmlencode_put(data
+ HASHLEN
+ 1,l
- HASHLEN
- 1);
523 void linktoindex(struct msginfo
*infop
,char item
)
524 /* for links from message view back to author/subject/threads index */
527 cmdstr
[0] = ITEM
[item
];
528 cmdstr
[1] = ITEM
[item
];
529 cmdstr
[2] = DIRECT
[DIRECT_SAME
+ 1];
530 oputs(cmdstr
); /* e.g. map: */
531 oput(strnum
,fmt_ulong(strnum
,infop
->target
));
534 oput(strnum
,fmt_ulong(strnum
,infop
->date
));
540 oputs(infop
->author
);
544 if (infop
->subject
) {
546 oputs(infop
->subject
);
555 void link_msg(struct msginfo
*infop
,char axis
,char direction
)
556 /* Creates <a href="mapa:123:aaaaa...."> using a maximum of available */
557 /* info only for links where the target is a message */
562 cmdstr
[0] = ITEM
[ITEM_MESSAGE
];
563 cmdstr
[1] = ITEM
[axis
];
564 cmdstr
[2] = DIRECT
[direction
+ 1];
570 acc
= infop
->subject
;
571 if (infop
->subjnav
) /* translate to message navigation */
572 if (infop
->subjnav
[direction
]) {
573 msg
= infop
->subjnav
[direction
];
574 cmdstr
[2] = DIRECT
[DIRECT_SAME
+ 1];
576 acc
= infop
->subject
;
581 if (infop
->authnav
) /* translate to message navigation */
582 if (infop
->authnav
[direction
]) {
583 msg
= infop
->authnav
[direction
];
584 cmdstr
[2] = DIRECT
[DIRECT_SAME
+ 1];
592 oput(strnum
,fmt_ulong(strnum
,msg
));
603 urlencode_puts("Just Click \"SEND\"!");
608 register char *cp
,*cp1
,*cp2
;
614 while(*cp1
&& *cp1
!= '=') cp1
++;
617 while(*cp2
&& *cp2
!= ',') cp2
++;
619 oput(cp1
+ 1,cp2
- cp1
- 1);
631 oputs("<a href=\"mailto:");
637 oputs(TXT_CGI_SUBSCRIBE
);
638 oputs("<a href=\"mailto:");
647 void msglinks(struct msginfo
*infop
)
648 /* Creates the html for all links from one message view */
650 oputs("<DIV class=msglinks><STRONG>Msg by: ");
651 link_msg(infop
,ITEM_SUBJECT
,DIRECT_PREV
);
652 oputs("[<-</A> ");
653 linktoindex(infop
,ITEM_SUBJECT
);
654 oputs(">thread</A> ");
655 link_msg(infop
,ITEM_SUBJECT
,DIRECT_NEXT
);
656 oputs("->]</A> \n");
657 link_msg(infop
,ITEM_MESSAGE
,DIRECT_PREV
);
658 oputs("[<-</A> ");
659 linktoindex(infop
,ITEM_INDEX
);
661 link_msg(infop
,ITEM_MESSAGE
,DIRECT_NEXT
);
662 oputs("->]</A> \n");
663 link_msg(infop
,ITEM_AUTHOR
,DIRECT_PREV
);
664 oputs("[<-</A> ");
665 linktoindex(infop
,ITEM_AUTHOR
);
666 oputs(">author</A> ");
667 link_msg(infop
,ITEM_AUTHOR
,DIRECT_NEXT
);
668 oputs("->]</A> |\n");
669 linktoindex(infop
,ITEM_DATE
);
670 oputs(">[Threads]</A>\n");
672 oputs("\n<a href=\"mailto:");
675 strnum
[fmt_ulong(strnum
,infop
->target
)] = '\0';
680 oputs("\">[eMsg]</A>\n");
681 oputs("<a href=\"mailto:");
688 oputs("\">[eThread]</A>\n");
690 oputs("</STRONG></DIV>\n");
696 void html_header(char *t
,char *s
, unsigned int l
,char *class,int flagspecial
)
697 /* flagspecial: 0x1 => robot index; no style sheet, no BASE */
698 /* flagspecial: 0x2 => banner, if available */
700 oputs("Content-Type: text/html; charset=");
701 oput(curcharset
.s
,curcharset
.len
);
703 oputs("\nCache-Control: ");
706 oputs("no-cache"); /* known upper border */
709 oputs("max-age=300"); /* 5 min - most lists aren't that fast*/
712 oputs("max-age=1209600"); /* 14 days is a long time */
716 oputs("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\">\n");
717 oputs("<HTML><HEAD>\n<TITLE>");
725 if (s
) htmlencode_put(s
,l
);
727 if (class && *class && stylesheet
&& *stylesheet
) {
728 oputs("<LINK href=\"");
730 oputs("\" rel=\"stylesheet\" type=\"text/css\">\n");
732 if (!flagrobot
) /* robot access allowed to follow */
733 oputs("<META NAME=\"robots\" CONTENT=\"noindex\">\n");
735 oputs("<META NAME=\"robots\" CONTENT=\"nofollow\">\n");
736 if (flagspecial
& SPC_BASE
)
737 oput(base
.s
,base
.len
);
739 if (class && *class) {
740 oputs("<BODY class=");
748 void html_footer(int flagspecial
)
750 oputs("<HR><DIV class=copyright>");
753 if ((flagspecial
& SPC_BANNER
) && banner
&& *banner
) {
754 oputs("<DIV class=banner>\n");
755 if (*banner
== '<') oputs(banner
);
757 substdio_flush(&ssout
);
759 bannerargs
[0] = banner
;
760 bannerargs
[1] = host
;
761 bannerargs
[2] = local
;
763 /* We log errors but just complete the page anyway, since we're */
764 /* already committed to output something. */
765 switch(child
= fork()) {
767 strerr_warn3(FATAL
,ERR_FORK
,"banner program: ",&strerr_sys
);
770 execv(*bannerargs
,bannerargs
);
771 strerr_die3x(100,FATAL
,ERR_EXECUTE
,"banner program: ");
775 wait_pid(&wstat
,child
);
776 if (wait_crashed(wstat
))
777 strerr_warn2(FATAL
,ERR_CHILD_CRASHED
,(struct strerr
*) 0);
778 if (wait_exitcode(wstat
))
779 strerr_warn2(FATAL
,ERR_CHILD_UNKNOWN
,(struct strerr
*) 0);
783 oputs("</BODY>\n</HTML>\n");
784 substdio_flush(&ssout
);
789 void datelink(struct msginfo
*infop
,unsigned long d
,char direction
)
790 /* output a date with link back to thread index */
793 cmdstr
[0] = ITEM
[ITEM_DATE
];
794 cmdstr
[1] = ITEM
[ITEM_DATE
];
795 cmdstr
[2] = DIRECT
[direction
+ 1];
797 if (direction
== DIRECT_LAST
)
798 oput("0",1); /* suppress msgnum to avoid going there */
800 oput(strnum
,fmt_ulong(strnum
,infop
->target
));
802 oput(strnum
,fmt_ulong(strnum
,d
));
806 if (dateline(&dtline
,d
) < 0) die_nomem();
807 oput(dtline
.s
,dtline
.len
);
816 oputs("[<<-]");
819 oputs("[->>]");
825 void finddate(struct msginfo
*infop
)
826 /* DIRECT_SAME works as DIRECT_PREV, dvs returns previous date or last date */
830 unsigned long ddate
, startdate
;
831 unsigned long below
, above
;
834 above
= MAXULONG
; /* creating a Y 0xffffff problem */
835 startdate
= infop
->date
;
836 archivedir
= opendir("archive/threads/");
838 if (errno
!= error_noent
)
839 strerr_die4sys(111,FATAL
,ERR_OPEN
,dir
,"/archive/threads: ");
841 strerr_die4sys(100,FATAL
,ERR_OPEN
,dir
,"/archive/threads: ");
843 while ((d
= readdir(archivedir
))) { /* dxxx/ */
844 if (str_equal(d
->d_name
,".")) continue;
845 if (str_equal(d
->d_name
,"..")) continue;
846 scan_ulong(d
->d_name
,&ddate
);
847 if (!ddate
) continue; /* just in case some smart guy ... */
849 if (ddate
> startdate
&& ddate
< above
) above
= ddate
;
850 if (ddate
< startdate
&& ddate
> below
) below
= ddate
;
852 if (ddate
< above
) above
= ddate
;
853 if (ddate
> below
) below
= ddate
;
856 closedir(archivedir
);
858 if (infop
->direction
== DIRECT_NEXT
&& above
!= MAXULONG
|| !below
)
859 /* we always give a valid date as long as there is at least one */
866 void latestdate(struct msginfo
*infop
,int flagfail
)
869 datetime_tai(&dt
,now());
870 infop
->date
= ((unsigned long) dt
.year
+ 1900) * 100 + dt
.mon
+ 1;
873 infop
->direction
= DIRECT_PREV
;
878 void firstdate(struct msginfo
*infop
,int flagfail
)
881 infop
->direction
= DIRECT_NEXT
;
885 void getdate(struct msginfo
*infop
,int flagfail
)
886 /* infop->date has to be 0 or valid on entry. Month outside of [1-12] on */
887 /* entry causes GIGO */
889 if (!flagfail
) { /* guess */
890 if (infop
->direction
== DIRECT_NEXT
) {
892 if (infop
->date
% 100 > 12) infop
->date
+= (100 - 12);
893 } else if (infop
->direction
== DIRECT_PREV
) {
895 if (!infop
->date
% 100) infop
->date
-= (100 - 12);
902 indexlinks(struct msginfo
*infop
)
904 unsigned long tmpmsg
;
906 tmpmsg
= infop
->target
;
908 oputs("<DIV class=idxlinks><STRONG>");
909 linktoindex(infop
,ITEM_INDEX
);
910 oputs(">[<<-]</A>\n");
911 if (tmpmsg
>= 100) infop
->target
= tmpmsg
- 100;
912 linktoindex(infop
,ITEM_INDEX
);
913 oputs(">[<-]</A>\n");
914 infop
->target
= tmpmsg
+ 100;
915 linktoindex(infop
,ITEM_INDEX
);
916 oputs(">[->]</A>\n");
917 infop
->target
= MAXULONG
;
918 linktoindex(infop
,ITEM_INDEX
);
919 oputs(">[->>]</A> |\n");
920 infop
->target
= tmpmsg
;
921 linktoindex(infop
,ITEM_DATE
);
922 oputs(">[Threads by date]</A>\n");
925 oputs("</STRONG></DIV>\n");
928 int show_index(struct msginfo
*infop
)
930 unsigned long thismsg
;
934 (void) makefn(&fn
,ITEM_INDEX
,msginfo
.target
,"");
935 if ((fd
= open_read(fn
.s
)) == -1)
936 if (errno
== error_noent
)
939 strerr_die4sys(111,FATAL
,ERR_OPEN
,fn
.s
,": ");
940 substdio_fdbuf(&ssin
,read
,fd
,inbuf
,sizeof(inbuf
));
941 if (!stralloc_copyb(&line
,strnum
,
942 fmt_ulong(strnum
,(unsigned long) (infop
->target
/ 100))))
944 if (!stralloc_cats(&line
,"xx")) die_nomem();
945 html_header("Messages ",line
.s
,line
.len
,"idxbody",SPC_BANNER
| SPC_BASE
);
947 oputs("<HR><H1 id=\"idxhdr\">");
949 oput(line
.s
,line
.len
);
951 oputs("<HR><DIV class=idx>\n");
953 if (getln(&ssin
,&line
,&match
,'\n') == -1)
954 strerr_die4sys(111,FATAL
,ERR_READ
,fn
.s
,": ");
957 pos
= scan_ulong(line
.s
,&thismsg
);
961 if (line
.len
< pos
+ 1 + HASHLEN
)
962 strerr_die2x(100,FATAL
,"index line with truncated subject entry");
963 if (!stralloc_copyb(&subject
,line
.s
+pos
,HASHLEN
)) die_nomem();
964 if (!stralloc_0(&subject
)) die_nomem();
965 infop
->axis
= ITEM_SUBJECT
;
966 infop
->subject
= subject
.s
;
967 oput(strnum
,fmt_uint0(strnum
,(unsigned int) thismsg
% 100,2));
969 link(infop
,ITEM_MESSAGE
,ITEM_SUBJECT
,thismsg
,line
.s
+pos
,line
.len
- pos
- 1);
972 if (getln(&ssin
,&line
,&match
,'\n') == -1)
973 strerr_die4sys(111,FATAL
,ERR_READ
,fn
.s
,": ");
976 pos
= byte_chr(line
.s
,line
.len
,';');
977 if (pos
!= line
.len
) {
978 infop
->date
= date2yyyymm(line
.s
);
980 link(infop
,ITEM_AUTHOR
,ITEM_AUTHOR
,thismsg
,line
.s
+pos
+1,
987 oputs("\n</DIV><HR>\n");
989 html_footer(SPC_BANNER
);
993 void objectlinks(struct msginfo
*infop
, char item
)
995 oputs("<DIV class=objlinks><STRONG>\n");
996 if (item
== ITEM_DATE
) {
997 datelink(infop
,0,DIRECT_FIRST
);
998 datelink(infop
,infop
->date
,DIRECT_PREV
);
999 datelink(infop
,infop
->date
,DIRECT_NEXT
);
1000 datelink(infop
,0,DIRECT_LAST
);
1003 if (!infop
->target
) infop
->axis
= ITEM_DATE
;
1004 linktoindex(infop
,ITEM_DATE
);
1005 oputs(">[Threads by date]</A>\n");
1007 if (item
!= ITEM_INDEX
) {
1008 linktoindex(infop
,ITEM_INDEX
);
1009 oputs(">[Messages by date]</A>\n");
1013 oputs("</STRONG></DIV>\n");
1016 int show_object(struct msginfo
*infop
,char item
)
1017 /* shows thread, threads, author */
1018 /* infop has the info needed to access the author/subject/thread file */
1020 unsigned long lastdate
,thisdate
,thismsg
;
1026 targetitem
= ITEM_MESSAGE
; /* default message is target */
1029 if (!makefn(&fn
,ITEM_SUBJECT
,0L,infop
->subject
)) return 0;
1032 if (!makefn(&fn
,ITEM_AUTHOR
,0L,infop
->author
)) return 0;
1035 if (!makefn(&fn
,ITEM_DATE
,infop
->date
,"")) return 0;
1038 die_prog("Bad object type in show_object");
1040 if ((fd
= open_read(fn
.s
)) == -1)
1041 if (errno
== error_noent
)
1044 strerr_die4sys(111,FATAL
,ERR_OPEN
,fn
.s
,": ");
1045 substdio_fdbuf(&ssin
,read
,fd
,inbuf
,sizeof(inbuf
));
1046 if (item
!= ITEM_DATE
) {
1047 if (getln(&ssin
,&line
,&match
,'\n') == -1) /* read subject */
1048 strerr_die4sys(111,FATAL
,ERR_READ
,fn
.s
,": ");
1049 if (!match
|| line
.len
< HASHLEN
+ 2)
1050 strerr_die4x(111,FATAL
,ERR_READ
,fn
.s
,": nothing there");
1054 html_header("Thread on: ",line
.s
+ HASHLEN
+ 1,
1055 line
.len
- HASHLEN
- 2,"subjbody",SPC_BANNER
| SPC_BASE
);
1056 objectlinks(infop
,item
);
1057 oputs("<HR><H1>On: ");
1058 oput(line
.s
+HASHLEN
+1,line
.len
-HASHLEN
-2);
1062 html_header("Posts by: ",line
.s
+ HASHLEN
+ 1,
1063 line
.len
- HASHLEN
- 2,"authbody",SPC_BANNER
| SPC_BASE
);
1064 objectlinks(infop
,item
);
1065 oputs("<HR><H1>By: ");
1066 oput(line
.s
+HASHLEN
+1,line
.len
-HASHLEN
-2);
1070 /* targetitem = ITEM_SUBJECT;*/ /* thread index is target */
1071 thisdate
= infop
->date
;
1072 if (dateline(&dtline
,infop
->date
) < 0) die_nomem();
1073 html_header("Threads for ",
1074 dtline
.s
,dtline
.len
,"threadsbody",SPC_BANNER
| SPC_BASE
);
1075 objectlinks(infop
,item
);
1076 oputs("<HR><H1>Threads for ");
1077 oput(dtline
.s
,dtline
.len
);
1080 default: die_prog("unrecognized object type in show_object");
1083 oputs("<DIV class=obj>\n");
1085 if (getln(&ssin
,&line
,&match
,'\n') == -1) /* read subject */
1086 strerr_die4sys(111,FATAL
,ERR_READ
,fn
.s
,": ");
1089 pos
= scan_ulong(line
.s
,&thismsg
);
1090 if (line
.s
[pos
++] != ':')
1091 strerr_die4x(100,FATAL
,"entry in ",fn
.s
," lacks message number");
1092 if (item
!= ITEM_DATE
) { /* no date for threads by date */
1093 pos
+= scan_ulong(line
.s
+pos
,&thisdate
);
1094 infop
->date
= thisdate
;
1095 if (line
.s
[pos
++] != ':')
1096 strerr_die4x(100,FATAL
,"entry in ",fn
.s
," lacks date");
1098 if (line
.len
< pos
+ HASHLEN
+ 2)
1099 strerr_die4x(100,FATAL
,"entry in ",fn
.s
," lacks hash");
1100 if (thisdate
!= lastdate
) {
1106 datelink(infop
,thisdate
,DIRECT_SAME
);
1107 lastdate
= thisdate
;
1110 if (item
== ITEM_SUBJECT
)
1111 linkitem
= ITEM_AUTHOR
;
1113 linkitem
= ITEM_SUBJECT
;
1114 link(infop
,targetitem
,linkitem
,thismsg
,line
.s
+pos
,line
.len
- pos
- 1);
1120 oputs("<a name=b></a>");
1121 oputs("<HR></DIV>\n");
1122 objectlinks(infop
,item
);
1123 html_footer(SPC_BANNER
);
1129 mime_current
->charset
.len
= 0; /* exist but need emptying */
1130 mime_current
->boundary
.len
= 0;
1131 mime_current
->ctype
.len
= 0;
1132 mime_current
->mimetype
= MIME_NONE
;
1133 mime_current
->ctenc
= CTENC_NONE
;
1134 mime_current
->cs
= CS_NONE
;
1139 mime_tmp
= mime_current
;
1141 mime_current
= mime_current
->next
;
1142 if (!mime_current
) {
1143 if (!(mime_current
= (mime_info
*) alloc(sizeof (mime_info
))))
1145 mime_current
->charset
= sainit
; /* init */
1146 mime_current
->boundary
= sainit
;
1147 mime_current
->ctype
= sainit
;
1148 mime_current
->next
= (mime_info
*) 0;
1149 mime_current
->previous
= mime_tmp
;
1153 mime_current
->level
= mime_tmp
->level
+ 1;
1155 mime_current
->level
= 1;
1158 void mime_getarg(stralloc
*sa
,char **s
, unsigned int *l
)
1159 /* copies next token or "token" into sa and sets s & l appropriately */
1160 /* for continuing the search */
1162 char *cp
, *cpafter
, *cpnext
;
1164 if (!*l
|| !**s
) return;
1167 cp
= *s
; cpnext
= cp
+ *l
; cpafter
= cpnext
;
1168 while (cp
< cpafter
) {
1176 cp
= *s
; cpnext
= cp
+ *l
; cpafter
= cpnext
;
1177 while (cp
< cpafter
) {
1178 if (*cp
== ' ' || *cp
== '\t' || *cp
== '\n' || *cp
== ';') {
1185 if (!stralloc_copyb(sa
,*s
,cp
- *s
)) die_nomem();
1186 *l
= cpafter
- cpnext
; /* always >= 0 */
1191 void decode_mime_type(char *s
,unsigned int l
,unsigned int flagmime
)
1195 if (!flagmime
|| !l
) { /* treat non-MIME as plain text */
1196 mime_current
->mimetype
= MIME_TEXT_PLAIN
;
1197 if (!stralloc_copys(&curcharset
,charset
)) die_nomem();
1198 /* should be us-ascii, but this is very likely better */
1201 r
= MIME_APPLICATION_OCTETSTREAM
;
1202 while (l
&& (*s
== ' ' || *s
== '\t')) { s
++; l
--; } /* skip LWSP */
1203 mime_getarg(&(mime_current
->ctype
),&s
,&l
);
1204 st
= mime_current
->ctype
.s
;
1205 lt
= mime_current
->ctype
.len
;
1206 if (case_startb(st
,lt
,"text")) { /* text types */
1207 r
= MIME_TEXT
; st
+= 4; lt
-= 4;
1208 if (case_startb(st
,lt
,"/plain")) {
1209 r
= MIME_TEXT_PLAIN
; st
+= 6; lt
-= 6;
1210 } else if (case_startb(st
,lt
,"/html")) {
1211 r
= MIME_TEXT_HTML
; st
+= 5; lt
-= 5;
1212 } else if (case_startb(st
,lt
,"/enriched")) {
1213 r
= MIME_TEXT_ENRICHED
; st
+= 9; lt
-= 9;
1214 } else if (case_startb(st
,lt
,"/x-vcard")) {
1215 r
= MIME_TEXT_ENRICHED
; st
+= 8; lt
-= 8;
1217 } else if (case_startb(st
,lt
,"multipart")) { /* multipart types */
1218 r
= MIME_MULTI
; st
+= 9; lt
-= 9;
1219 if (case_startb(st
,lt
,"/alternative")) {
1220 r
= MIME_MULTI_ALTERNATIVE
; st
+= 12; lt
-= 12;
1221 } else if (case_startb(st
,lt
,"/mixed")) {
1222 r
= MIME_MULTI_MIXED
; st
+= 6; lt
-= 6;
1223 } else if (case_startb(st
,lt
,"/digest")) {
1224 r
= MIME_MULTI_DIGEST
; st
+= 7; lt
-= 7;
1225 } else if (case_startb(st
,lt
,"/signed")) {
1226 r
= MIME_MULTI_SIGNED
; st
+= 7; lt
-= 7;
1228 } else if (case_startb(st
,lt
,"message")) { /* message types */
1229 r
= MIME_MESSAGE
; st
+= 7; lt
-= 7;
1230 if (case_startb(st
,lt
,"/rfc822")) {
1231 r
= MIME_MESSAGE_RFC822
; st
+= 7; lt
-= 7;
1234 mime_current
->mimetype
= r
;
1236 while (l
&& (*s
== ' ' || *s
== '\t' || *s
== ';' || *s
== '\n')) {
1237 s
++; l
--; } /* skip ;LWSP */
1238 if (case_startb(s
,l
,"boundary=")) {
1240 mime_getarg(&(mime_current
->boundary
),&s
,&l
);
1241 } else if (case_startb(s
,l
,"charset=")) {
1243 mime_getarg(&(mime_current
->charset
),&s
,&l
);
1244 cs
= decode_charset(mime_current
->charset
.s
,
1245 mime_current
->charset
.len
);
1246 if (cs
== CS_BAD
) cs
= csbase
; /* keep base cs */
1248 if (!stralloc_copy(&curcharset
,&mime_current
->charset
)) die_nomem();
1249 } else { /* skip non LWSP */
1254 while (l
&& *s
!= '"') { s
++, l
--; }
1255 if (l
) { s
++, l
--; }
1258 if (!l
|| *s
== ' ' || *s
== '\t' || *s
== '\n') break;
1267 void decode_transfer_encoding(register char *s
,register unsigned int l
)
1270 mime_current
->ctenc
= CTENC_NONE
;
1271 if (!l
|| (mime_current
->mimetype
& MIME_MULTI
)) return;
1272 /* base64/QP ignored for multipart */
1274 while (l
&& (*s
== ' ' || *s
== '\t')) { s
++; l
--; } /* skip LWSP */
1275 if (case_startb(s
,l
,"quoted-printable")) {
1277 } else if (case_startb(s
,l
,"base64")) {
1280 mime_current
->ctenc
= r
;
1284 int check_boundary()
1285 /* return 0 if no boundary, 1 if start, 2 if end */
1289 if (*line
.s
!= '-' || line
.s
[1] != '-') return 0;
1292 if (tmp
->boundary
.len
) {
1293 if (line
.len
> tmp
->boundary
.len
+ 2 &&
1294 !case_diffb(line
.s
+2,tmp
->boundary
.len
,tmp
->boundary
.s
)) {
1295 if (line
.s
[tmp
->boundary
.len
+ 2] == '-' &&
1296 line
.s
[tmp
->boundary
.len
+ 3] == '-') { /* end */
1301 } else { /* start */
1308 tmp
= tmp
->previous
;
1310 if (!stralloc_copys(&curcharset
,charset
)) die_nomem();
1311 /* suprtfluous since header done by now */
1316 void start_message_page(struct msginfo
*infop
)
1317 /* header etc for message. Delayed to collect subject so that we can put */
1318 /* that in TITLE. This in turn needed for good looking robot index. */
1319 /* Yep, not pretty, but it works and it's abhorrent to seek()/rewind */
1320 /* and another hack: it's hard to mix charsets within a doc. So, we disp */
1321 /* messages entirely in the charset of the message. This is ok, since */
1322 /* headers will be us-ascii or have encoded segments usually matching */
1323 /* the charset in the message. Of course, we should be able to used e.g. */
1324 /* <DIV charset=iso-2022-jp> with internal resources as well as internal */
1325 /* ones. One might make other-charset messages external resources as well*/
1326 /* Now, the problem is that we need to "preview" MIME info _before_ */
1327 /* seeing the start boundary. */
1329 if (!stralloc_copyb(&decline
,strnum
,fmt_ulong(strnum
,infop
->target
)))
1331 if (!stralloc_cats(&decline
,":")) die_nomem();
1332 if (!stralloc_0(&decline
)) die_nomem();
1333 decodeHDR(hdr
[HDR_SUBJECT
- 1].s
,hdr
[HDR_SUBJECT
- 1].len
,&line
,"",FATAL
);
1335 new_mime(); /* allocate */
1338 decode_mime_type(hdr
[HDR_CT
- 1].s
,hdr
[HDR_CT
- 1].len
,
1339 hdr
[HDR_VERSION
- 1].len
);
1340 html_header(decline
.s
,line
.s
,line
.len
- 1,
1341 "msgbody",SPC_BASE
);
1343 oputs("<DIV class=message>\n");
1346 void show_part(struct msginfo
*infop
,int flagshowheaders
,
1347 int flagskip
,int flagstartseen
)
1348 /* if flagshowheaders we display headers, otherwise not */
1349 /* if flagstartseen we've already see the start boundary for this part, */
1350 /* if not we'll ignore what's there up to it */
1351 /* if flagskip we skip this part */
1359 unsigned int colpos
,l
,pos
;
1363 for (i
= 0; i
< NO_HDRS
; i
++) hdr
[i
].len
= 0;
1366 recursion_level
++; /* one up */
1369 if (getln(&ssin
,&line
,&match
,'\n') == -1)
1370 strerr_die4sys(111,FATAL
,ERR_READ
,fn
.s
,": ");
1372 if ((btype
= check_boundary())) {
1377 if (mime_current
->level
< recursion_level
) {
1388 if (!flagstartseen
) continue; /* skip to start */
1390 if (line
.len
== 1) {
1391 if (flagshowheaders
) { /* rfc822hdr only */
1393 start_message_page(infop
); /* so we can put subj in TITLE */
1394 oputs("<DIV class=rfc822hdr><HR>\n");
1395 for (i
= 0; i
< NO_HDRS
; i
++) {
1396 if (!hdr
[i
].len
|| !headers_shown
[i
]) continue;
1397 if (i
== HDR_SUBJECT
- 1 && flagtoplevel
)
1398 oputs("<SPAN class=subject>");
1400 oputs(constmap_get(&headermap
,i
+ 1));
1402 decodeHDR(hdr
[i
].s
,hdr
[i
].len
,&line
,"",FATAL
);
1403 if (i
== HDR_SUBJECT
- 1 && flagtoplevel
) {
1404 oputs("<A class=relk href=\"mailto:");
1409 urlencode_put(line
.s
+ 1,line
.len
- 2);
1412 if (flagobscure
&& i
== HDR_FROM
- 1) {
1414 decodeHDR(cp
,author_name(&cp
,line
.s
,line
.len
),&decline
,"",FATAL
);
1415 htmlencode_put(decline
.s
,decline
.len
);
1417 decodeHDR(hdr
[i
].s
,hdr
[i
].len
,&decline
,"",FATAL
);
1418 htmlencode_put(decline
.s
,decline
.len
- 1);
1420 if (i
== HDR_SUBJECT
- 1 && flagtoplevel
)
1421 oputs("</A></SPAN>");
1431 flagmime
= hdr
[HDR_VERSION
- 1].len
; /* MIME-Version header */
1432 decode_mime_type(hdr
[HDR_CT
- 1].s
,hdr
[HDR_CT
- 1].len
,flagmime
);
1433 decode_transfer_encoding(hdr
[HDR_CTENC
- 1].s
,hdr
[HDR_CTENC
- 1].len
);
1434 content
.len
= 0; encoding
.len
= 0;
1435 switch (mime_current
->mimetype
) {
1436 case MIME_MULTI_SIGNED
:
1437 case MIME_MULTI_MIXED
:
1438 case MIME_MULTI_ALTERNATIVE
:
1439 case MIME_MULTI_DIGEST
:
1440 show_part(infop
,0,0,0);
1445 case MIME_MESSAGE_RFC822
:
1448 flagshowheaders
= 1;
1450 flagmime
= 0; /* need new MIME-Version header */
1452 case MIME_TEXT_HTML
:
1457 oputs("<strong>[\"");
1458 oput(mime_current
->ctype
.s
,mime_current
->ctype
.len
);
1459 oputs("\" not shown]</strong>\n");
1460 flaggoodfield
= 0; /* hide */
1463 case MIME_TEXT_PLAIN
:
1464 case MIME_TEXT
: /* in honor of Phil using "text" on */
1465 case MIME_NONE
: /* the qmail list and rfc2045:5.2 */
1466 oputs("<HR>\n<PRE>\n");
1469 case MIME_TEXT_VCARD
:
1470 default: /* application/octetstream...*/
1471 oputs("<HR><strong>[\"");
1472 oput(mime_current
->ctype
.s
,mime_current
->ctype
.len
);
1473 oputs("\" not shown]</strong>\n");
1474 flaggoodfield
= 0; /* hide */
1477 } else if (line
.s
[0] != ' ' && line
.s
[0] != '\t') {
1480 colpos
= byte_chr(line
.s
,line
.len
,':');
1481 if ((whatheader
= constmap_index(&headermap
,line
.s
,colpos
))) {
1483 if (!stralloc_copyb(&hdr
[whatheader
- 1],line
.s
+ colpos
+ 1,
1484 line
.len
- colpos
- 1)) die_nomem();
1488 if (!stralloc_catb(&hdr
[whatheader
- 1],line
.s
,line
.len
))
1492 if (flaggoodfield
) {
1493 if (mime_current
->ctenc
) {
1494 if (!stralloc_copy(&decline
,&line
)) die_nomem();
1496 if (mime_current
->ctenc
== CTENC_QP
)
1497 decodeQ(decline
.s
,decline
.len
,&line
);
1499 decodeB(decline
.s
,decline
.len
,&line
);
1502 oput(line
.s
,line
.len
);
1504 htmlencode_put(line
.s
,line
.len
); /* body */
1511 int show_message(struct msginfo
*infop
)
1515 if(!stralloc_copys(&headers
,(char *) headers_used
)) die_nomem();
1516 if (!stralloc_0(&headers
)) die_nomem();
1519 if (*psz
== '\\') *psz
= '\0';
1522 if (!constmap_init(&headermap
,headers
.s
,headers
.len
,0))
1525 (void) makefn(&fn
,ITEM_MESSAGE
,msginfo
.target
,"");
1526 if ((fd
= open_read(fn
.s
)) == -1)
1527 if (errno
== error_noent
)
1530 strerr_die4sys(111,FATAL
,ERR_OPEN
,fn
.s
,": ");
1531 substdio_fdbuf(&ssin
,read
,fd
,inbuf
,sizeof(inbuf
));
1533 recursion_level
= 0; /* recursion level for show_part */
1534 flagmime
= 0; /* no active mime */
1535 flagtoplevel
= 1; /* top message/rfc822 get special rx */
1536 new_mime(); /* initiate a MIME info storage slot */
1538 show_part(infop
,1,0,1); /* do real work, including html header etc */
1542 oputs("<HR></DIV>\n");
1548 char decode_item(char ch
)
1551 case 'm': return ITEM_MESSAGE
;
1552 case 'a': return ITEM_AUTHOR
;
1553 case 's': return ITEM_SUBJECT
;
1554 case 'd': return ITEM_DATE
;
1555 case 'i': return ITEM_INDEX
;
1556 default: cgierr("Navigation command contains ",
1557 "illegal item code","");
1559 return 0; /* never reached */
1562 char decode_direction(char ch
)
1565 case 's': return DIRECT_SAME
;
1566 case 'n': return DIRECT_NEXT
;
1567 case 'p': return DIRECT_PREV
;
1568 default: cgierr("Navigation command contains ",
1569 "illegal direction code","");
1571 return 0; /* never reached */
1574 int decode_cmd(char *s
,struct msginfo
*infop
)
1575 /* decodes s into infop. Assures that no security problems slip through by */
1576 /* checking everything */
1577 /* commands xyd:123[:abc]. x what we want, y is the axis, d the direction. */
1578 /* 123 is the current message number. abc is a date/subject/author hash, */
1579 /* depending on axis, or empty if not available. */
1580 /* returns: 0 no command+msgnum. */
1581 /* 1 empty or at least cmd + msgnum. */
1582 /* Guarantee: Only legal values accepted */
1588 infop
->author
= (char *)0;
1589 infop
->subject
= (char *)0;
1590 infop
->cgiarg
= (char *)0;
1592 if (!s
|| !*s
) { /* main index */
1593 infop
->item
= ITEM_DATE
;
1594 infop
->axis
= ITEM_DATE
;
1595 infop
->direction
= DIRECT_SAME
;
1596 latestdate(&msginfo
,0);
1597 infop
->target
= MAXULONG
;
1601 if (ch
>= '0' && ch
<= '9') { /* numeric - simplified cmd: msgnum ... */
1603 infop
->item
= ITEM_MESSAGE
;
1604 infop
->axis
= ITEM_MESSAGE
;
1605 infop
->direction
= DIRECT_SAME
;
1606 } else { /* what:axis:direction:msgnum ... */
1607 infop
->item
= decode_item(ch
);
1609 infop
->axis
= decode_item(ch
);
1611 infop
->direction
= decode_direction(ch
);
1612 if (*(s
++) != ':') return 0;
1614 s
+= scan_ulong(s
,&(infop
->source
));
1615 if (*(s
++) != ':') return 0;
1616 if (*s
>= '0' && *s
<= '9') { /* numeric nav hint [date] */
1617 s
+= scan_ulong(s
,&(infop
->date
));
1618 if (!*s
++) return 1; /* skip any char - should be ':' unless NUL */
1620 if (checkhash(s
)) { /* Ignore if illegal rather than complaining*/
1621 if (!stralloc_copyb(&charg
,s
,HASHLEN
)) die_nomem();
1622 if (!stralloc_0(&charg
)) die_nomem();
1623 infop
->cgiarg
= charg
.s
;
1628 int msg2hash(struct msginfo
*infop
)
1631 unsigned long tmpmsg
;
1633 if (!infop
->source
) die_prog("source is 0 in msg2hash");
1634 (void) makefn(&fn
,ITEM_INDEX
,infop
->source
,"");
1635 if ((fd
= open_read(fn
.s
)) == -1) {
1636 if (errno
== error_noent
)
1639 strerr_die4sys(111,FATAL
,ERR_OPEN
,fn
.s
,": ");
1641 substdio_fdbuf(&ssin
,read
,fd
,inbuf
,sizeof(inbuf
));
1643 if (getln(&ssin
,&line
,&match
,'\n') == -1)
1644 strerr_die3sys(111,FATAL
,ERR_READ
,"index: ");
1646 return 0; /* didn't find message */
1647 if (*line
.s
== '\t') continue; /* author line */
1648 pos
= scan_ulong(line
.s
,&tmpmsg
);
1649 if (tmpmsg
== infop
->source
) {
1650 if (line
.s
[pos
++] != ':' || line
.s
[pos
++] != ' ')
1651 strerr_die3x(100,ERR_SYNTAX
,fn
.s
,": missing subject separator");
1652 if (line
.len
< HASHLEN
+ pos
)
1653 strerr_die3x(100,ERR_SYNTAX
,fn
.s
,": missing subject hash");
1654 if (!stralloc_copyb(&subject
,line
.s
+pos
,HASHLEN
)) die_nomem();
1655 if (!stralloc_0(&subject
)) die_nomem();
1656 infop
->subject
= subject
.s
;
1657 if (getln(&ssin
,&line
,&match
,'\n') == -1)
1658 strerr_die3sys(111,FATAL
,ERR_READ
,"index: ");
1660 strerr_die3x(100,ERR_SYNTAX
,fn
.s
,
1661 ": author info missing. Truncated?");
1662 pos
= byte_chr(line
.s
,line
.len
,';');
1663 if (pos
== line
.len
)
1664 strerr_die3x(100,ERR_SYNTAX
,fn
.s
,"missing ';' after date");
1666 infop
->date
= date2yyyymm(line
.s
+1); /* ';' marks end ok */
1668 if (line
.len
< HASHLEN
+ pos
)
1669 strerr_die3x(100,ERR_SYNTAX
,fn
.s
,": missing author hash");
1670 if (!stralloc_copyb(&author
,line
.s
+pos
,HASHLEN
)) die_nomem();
1671 if (!stralloc_0(&author
)) die_nomem();
1672 infop
->author
= author
.s
;
1674 return 1; /* success */
1678 return 0; /* failed to match */
1681 void setmsg(struct msginfo
*infop
)
1682 /* Reads the file corresponding to infop->axis and assumes fn.s is set */
1683 /* correctly for this. Sets up a msgnav structure and links it in */
1684 /* correction for axis=author/subject. For axis=date it supports also */
1685 /* direction=DIRECT_FIRST which will return the first message of the */
1686 /* first thread in the date file. DIRECT_LAST is not supported. */
1687 /* DIRECT_FIRST is supported ONLY for date. */
1689 if (infop
->direction
== DIRECT_SAME
) {
1690 infop
->target
= infop
->source
;
1693 if ((fd
= open_read(fn
.s
)) == -1) {
1694 if (errno
== error_noent
)
1695 strerr_die4x(100,FATAL
,ERR_OPEN
,fn
.s
,
1696 " in listmsgs. Rerun ezmlm-archive!");
1698 strerr_die4sys(111,FATAL
,ERR_OPEN
,fn
.s
,": ");
1700 substdio_fdbuf(&ssin
,read
,fd
,inbuf
,sizeof(inbuf
));
1701 if (infop
->source
!= ITEM_DATE
) {
1702 if (getln(&ssin
,&line
,&match
,'\n') == -1) /* first line */
1703 strerr_die4sys(111,FATAL
,ERR_READ
,fn
.s
,": ");
1705 strerr_die3x(100,ERR_SYNTAX
,fn
.s
,": first line missing");
1707 msgnav
[3] = 0L; /* next */
1708 msgnav
[4] = 0L; /* after */
1711 if (getln(&ssin
,&line
,&match
,'\n') == -1)
1712 strerr_die4sys(111,FATAL
,ERR_READ
,fn
.s
,": ");
1714 msgnav
[0] = msgnav
[1];
1715 msgnav
[1] = msgnav
[2];
1716 (void) scan_ulong(line
.s
,&(msgnav
[2]));
1717 if (infop
->direction
== DIRECT_FIRST
) break;
1718 if (msgnav
[2] == infop
->source
) {
1719 if (getln(&ssin
,&line
,&match
,'\n') == -1)
1720 strerr_die4sys(111,FATAL
,ERR_READ
,fn
.s
,": ");
1722 (void) scan_ulong(line
.s
,&(msgnav
[3]));
1723 if (getln(&ssin
,&line
,&match
,'\n') == -1)
1724 strerr_die4sys(111,FATAL
,ERR_READ
,fn
.s
,": ");
1726 (void) scan_ulong(line
.s
,&(msgnav
[4]));
1731 switch (infop
->axis
) {
1733 infop
->authnav
= msgnav
+ 2 + infop
->direction
;
1734 infop
->target
= *(infop
->authnav
);
1735 infop
->subject
= (char *)0; /* what we know is not for this msg */
1739 infop
->subjnav
= msgnav
+ 2 + infop
->direction
;
1740 infop
->target
= *(infop
->subjnav
);
1741 infop
->author
= (char *)0; /* what we know is not for this msg */
1745 infop
->target
= msgnav
[2];
1746 infop
->subject
= (char *)0; /* what we know is not for this msg */
1747 infop
->author
= (char *)0; /* what we know is not for this msg */
1749 die_prog("Bad item in setmsg");
1754 void auth2msg(struct msginfo
*infop
)
1756 if (!infop
->author
) die_prog("no such author in authmsg");
1757 if (!makefn(&fn
,ITEM_AUTHOR
,0L,infop
->author
)) die_prog("auth2msg");
1761 void subj2msg(struct msginfo
*infop
)
1763 if (!infop
->subject
) die_prog("no such subject in subj2msg");
1764 if (!makefn(&fn
,ITEM_SUBJECT
,0L,infop
->subject
)) die_prog("subj2msg");
1768 void date2msg(struct msginfo
*infop
)
1770 (void) makefn(&fn
,ITEM_DATE
,infop
->date
,"");
1774 void findlastmsg(struct msginfo
*infop
)
1776 if (!getconf_line(&line
,"num",dir
,0,FATAL
))
1777 cgierr("Sorry, there are no messages in the archive","","");
1778 if (!stralloc_0(&line
)) die_nomem();
1779 (void) scan_ulong(line
.s
,&(infop
->target
));
1782 int do_cmd(struct msginfo
*infop
)
1783 /* interprets msginfo to create msginfo. Upon return, msginfo can be trusted */
1784 /* to have all info needed, and that all info is correct. There may be more */
1785 /* info than needed. This can be used to build more specific links. NOTE: */
1786 /* there is no guarantee that a message meeting the criteria actually exists*/
1788 infop
->target
= infop
->source
;
1790 switch (infop
->item
) {
1791 case ITEM_MESSAGE
: /* we want to get a message back */
1793 switch (infop
->axis
) {
1795 if (infop
->direction
== DIRECT_SAME
)
1797 else if (infop
->direction
== DIRECT_NEXT
)
1799 else { /* previous */
1801 if (infop
->target
>= 2)
1808 infop
->author
= infop
->cgiarg
;
1809 if (!infop
->author
) /* we don't know author hash */
1810 if (!msg2hash(infop
)) return 0;
1814 infop
->subject
= infop
->cgiarg
;
1815 if (!infop
->subject
) /* we don't know Subject hash */
1816 if (!msg2hash(infop
)) return 0;
1823 switch (infop
->axis
) {
1826 if (!msg2hash(infop
)) return 0;
1829 infop
->author
= infop
->cgiarg
;
1831 if (!msg2hash(infop
)) return 0;
1835 infop
->subject
= infop
->cgiarg
;
1836 if (!infop
->subject
) /* we don't know Subject hash */
1837 if (!msg2hash(infop
)) return 0;
1843 switch (infop
->axis
) {
1845 if (!msg2hash(infop
)) return 0;
1848 infop
->author
= infop
->cgiarg
;
1850 if (!msg2hash(infop
)) return 0;
1854 infop
->subject
= infop
->cgiarg
;
1855 if (!infop
->subject
) /* we don't know Subject hash */
1856 if (!msg2hash(infop
)) return 0;
1861 case ITEM_DATE
: /* want a date reference */
1862 switch (infop
->axis
) {
1867 if (!infop
->date
&& infop
->source
)
1868 if (!msg2hash(infop
)) return 0;
1873 case ITEM_INDEX
: /* ignore direction etc - only for index */
1875 infop
->target
= infop
->source
;
1888 html_header("Robot index of lists",0,0,0,0);
1890 if (getln(&ssin
,&cfline
,&match
,'\n') == -1) /* read line */
1891 strerr_die4sys(111,FATAL
,ERR_READ
,fn
.s
,": ");
1894 if (cfline
.s
[0] == '#') continue; /* skip comment */
1895 cfline
.s
[cfline
.len
- 1] = '\0'; /* so all are sz */
1896 (void) scan_ulong(cfline
.s
,&lno
); /* listno for line */
1897 if (lno
) { /* don't expose default list */
1898 oputs("<A href=\"");
1899 oput(strnum
,fmt_ulong(strnum
,lno
));
1900 oputs("/index\">[link]</a>\n");
1906 void list_list(unsigned long listno
)
1907 /* Make one link [for list_set()] per set of 100 archive messages. */
1908 /* Assumption: Any directory DIR/archive/xxx where 'xxx' is a numeric,*/
1909 /* is part of the list archive and has in it an index file and one */
1910 /* or more messages. */
1914 unsigned long msgset
;
1917 strnum
[fmt_ulong(strnum
,listno
)] = '\0';
1918 archivedir
= opendir("archive/");
1920 if (errno
!= error_noent
)
1921 strerr_die4sys(111,FATAL
,ERR_OPEN
,dir
,"/archive: ");
1923 strerr_die4sys(100,FATAL
,ERR_OPEN
,dir
,"/archive: ");
1926 html_header("Robot index for message sets in list",0,0,0,0);
1928 while ((d
= readdir(archivedir
))) {
1929 if (d
->d_name
[scan_ulong(d
->d_name
,&msgset
)])
1930 continue; /* not numeric */
1931 oputs("<a href=\"../"); /* from /ezcgi/0/index to /ezcgi/listno/index*/
1935 oputs("\">[link]</a>\n");
1937 closedir(archivedir
);
1941 void list_set(unsigned long listno
,unsigned long msgset
)
1943 unsigned int msgfirst
,msgmax
;
1944 unsigned long lastset
;
1947 findlastmsg(&msginfo
);
1948 if (!stralloc_copys(&line
,"<a href=\"../")) die_nomem();
1949 if (!stralloc_catb(&line
,strnum
,fmt_ulong(strnum
,msgset
))) die_nomem();
1950 lastset
= msginfo
.target
/ 100;
1956 if (msgset
> lastset
) { /* assure empty list */
1959 } else if (msgset
== lastset
) {
1960 cache
= 0; /* still changing */
1961 msgmax
= msginfo
.target
% 100;
1963 html_header("Robot index for messages in set",0,0,0,0);
1964 while (msgfirst
<= msgmax
) {
1965 oput(line
.s
,line
.len
);
1966 oput(strnum
,fmt_uint0(strnum
,msgfirst
,2));
1967 oputs("\">[link]</a>\n");
1973 /**************** MAY BE SUID ROOT HERE ****************************/
1974 void drop_priv(int flagchroot
)
1976 if (!uid
) strerr_die2x(100,FATAL
,ERR_SUID
); /* not as root */
1979 if (chroot(dir
) == -1) /* chroot listdir */
1980 strerr_die4sys(111,FATAL
,"failed to chroot ",dir
,": ");
1981 if (setuid(uid
) == -1) /* setuid */
1982 strerr_die2sys(111,FATAL
,ERR_SETUID
);
1984 euid
= (unsigned long) geteuid();
1985 if (!euid
) strerr_die2x(100,FATAL
,ERR_SUID
); /* setuid didn't do it*/
1987 /*******************************************************************/
1994 unsigned long listno
,thislistno
,tmpuid
,msgset
;
1995 unsigned long msgnum
= 0;
1996 unsigned long port
= 0L;
1997 unsigned long tmptarget
;
2000 int flagchroot
= 1; /* chroot listdir if SUID root */
2004 /******************** we may be SUID ROOT ******************************/
2005 uid
= (unsigned long) getuid(); /* should be http */
2006 euid
= (unsigned long) geteuid(); /* chroot only if 0 */
2009 if ((fd
= open_read(EZ_CGIRC
)) == -1) /* open config */
2010 strerr_die4sys(111,FATAL
,ERR_OPEN
,EZ_CGIRC
,": ");
2012 if ((fd
= open_read(EZ_CGIRC_LOC
)) == -1) /* open local config */
2013 strerr_die4sys(111,FATAL
,ERR_OPEN
,EZ_CGIRC_LOC
,": ");
2016 substdio_fdbuf(&ssin
,read
,fd
,inbuf
,sizeof(inbuf
)); /* set up buffer */
2017 /* ##### tainted info #####*/
2019 cmd
= env_get("QUERY_STRING"); /* get command */
2020 cppath
= env_get("PATH_INFO"); /* get path_info */
2022 if (!cppath
|| !*cppath
) {
2024 cmd
+= scan_ulong(cmd
,&thislistno
);
2025 if (*cmd
== ':') cmd
++; /* allow ':' after ln*/
2030 cppath
+= scan_ulong(cppath
,&thislistno
); /* this listno */
2031 if (!thislistno
|| *cppath
++ == '/') {
2032 if (str_start(cppath
,"index")) {
2035 if (!thislistno
) { /* list index */
2036 drop_priv(0); /* <---- dropping privs */
2042 } /* rest done per list */
2046 if (getln(&ssin
,&cfline
,&match
,'\n') == -1) /* read line */
2047 strerr_die4sys(111,FATAL
,ERR_READ
,fn
.s
,": ");
2050 if (*cfline
.s
== '#' || cfline
.len
== 1) continue; /* skip comment/blank */
2051 cfline
.s
[cfline
.len
- 1] = '\0'; /* so all are sz */
2052 pos
= scan_ulong(cfline
.s
,&listno
); /* listno for line */
2053 if (thislistno
!= listno
) continue;
2054 sep
= cfline
.s
[pos
++];
2055 if (cfline
.s
[pos
] == '-') { /* no chroot if -uid*/
2059 pos
+= scan_ulong(cfline
.s
+pos
,&tmpuid
); /* listno for line */
2060 if (tmpuid
) uid
= tmpuid
; /* override default */
2061 if (!cfline
.s
[pos
++] == sep
)
2062 die_syntax("missing separator after user id");
2063 if (cfline
.s
[pos
] != '/')
2064 die_syntax("dir"); /* absolute path */
2065 l
= byte_chr(cfline
.s
+ pos
, cfline
.len
- pos
,sep
);
2066 if (l
== cfline
.len
- pos
) /* listno:path:...*/
2067 die_syntax("missing separator after path");
2068 dir
= cfline
.s
+ pos
;
2070 cfline
.s
[pos
++] = '\0'; /* .../dir\0 */
2071 break; /* do rest after dropping priv */
2073 close(fd
); /* don't accept uid 0*/
2075 drop_priv(0); /* don't trust cgierr. No dir, no chroot */
2076 cgierr("list ",ERR_NOEXIST
,"");
2078 if (chdir(dir
) == -1) /* chdir listdir */
2079 strerr_die4sys(111,FATAL
,ERR_SWITCH
,dir
,": ");
2080 drop_priv(flagchroot
);
2082 /******************************* RELAX **********************************/
2084 /********************* continue to process config line ******************/
2087 if (cfline
.s
[pos
] == '-') {
2091 local
= cfline
.s
+ pos
;
2092 l
= byte_chr(cfline
.s
+ pos
, cfline
.len
- pos
,sep
); /* ... home */
2093 if (l
< cfline
.len
- pos
) { /* optional */
2095 cfline
.s
[pos
++] = '\0';
2096 home
= cfline
.s
+ pos
;
2097 l
= byte_chr(cfline
.s
+ pos
, cfline
.len
- pos
,sep
); /* ... charset */
2098 if (l
< cfline
.len
- pos
) { /* optional */
2100 cfline
.s
[pos
++] = '\0';
2101 charset
= cfline
.s
+ pos
;
2102 l
= byte_chr(cfline
.s
+pos
,cfline
.len
- pos
,sep
); /* ... stylesheet */
2103 if (l
< cfline
.len
- pos
) { /* optional */
2105 cfline
.s
[pos
++] = '\0';
2106 stylesheet
= cfline
.s
+ pos
;
2107 l
= byte_chr(cfline
.s
+pos
,cfline
.len
-pos
,sep
); /* ... bannerURL */
2108 if (l
< cfline
.len
- pos
) { /* optional */
2110 cfline
.s
[pos
++] = '\0';
2111 banner
= cfline
.s
+ pos
;
2116 if (!charset
|| !*charset
) /* rfc822 default */
2117 charset
= EZ_CHARSET
;
2118 if (!stralloc_copys(&curcharset
,charset
)) die_nomem();
2119 csbase
= decode_charset(curcharset
.s
,curcharset
.len
);
2120 if (csbase
== CS_BAD
) csbase
= CS_NONE
;
2122 pos
= + str_rchr(local
,'@');
2124 die_syntax("listaddress lacks '@'"); /* require host */
2125 local
[pos
++] = '\0';
2128 /********************* Accomodate robots and PATH_INFO ****************/
2131 if (*(cppath
++) == '/') { /* /2/index/123 */
2132 cppath
+= scan_ulong(cppath
,&msgset
);
2133 list_set(thislistno
,msgset
);
2134 } else /* /2/index */
2135 list_list(thislistno
);
2139 if (cppath
&& *cppath
) { /* /2/msgnum */
2140 flagrobot
= 1; /* allow index, but "nofollow" */
2141 scan_ulong(cppath
,&msgnum
);
2142 } /* dealt with normally */
2144 /********************* Get info from server on BASE etc ****************/
2146 if (!stralloc_copys(&base
,"<BASE href=\"http://")) die_nomem();
2147 cp
= env_get("SERVER_PORT");
2148 if (cp
) { /* port */
2149 (void) scan_ulong(cp
,&port
);
2150 if ((unsigned int) port
== 443) { /* https: */
2151 if (!stralloc_copys(&base
,"<BASE href=\"https://")) die_nomem();
2154 if (port
&& (unsigned int) port
!= 80 && (unsigned int) port
!= 443) {
2155 if (!stralloc_cats(&base
,":")) die_nomem();
2156 if (!stralloc_catb(&base
,strnum
,fmt_ulong(strnum
,port
))) die_nomem();
2158 if (!(cp
= env_get("HTTP_HOST")))
2159 if (!(cp
= env_get("SERVER_NAME")))
2160 strerr_die2x(100,FATAL
,"both HTTP_HOST and SERVER_NAME are empty");
2161 if (!stralloc_cats(&base
,cp
)) die_nomem();
2162 if (!(cp
= env_get("SCRIPT_NAME")))
2163 strerr_die2x(100,FATAL
,"empty SCRIPT_NAME");
2164 if (!stralloc_cats(&base
,cp
)) die_nomem();
2165 if (!stralloc_cats(&base
,"\">\n")) die_nomem();
2166 if (!stralloc_copys(&url
,"<A HREF=\"")) die_nomem();
2167 pos
= str_rchr(cp
,'/');
2169 if (!stralloc_cats(&url
,cp
+ pos
+ 1)) die_nomem();
2170 if (!stralloc_cats(&url
,"?")) die_nomem();
2172 if (!stralloc_catb(&url
,strnum
,fmt_ulong(strnum
,thislistno
))) die_nomem();
2173 if (!stralloc_cats(&url
,":")) die_nomem();
2176 cache
= 1; /* don't know if we want to cache */
2178 /****************************** Get command ****************************/
2180 if (msgnum
) { /* to support /listno/msgno */
2181 msginfo
.target
= msgnum
;
2182 msginfo
.item
= ITEM_MESSAGE
;
2185 (void) decode_cmd(cmd
,&msginfo
);
2186 if (!do_cmd(&msginfo
))
2187 cgierr("I'm sorry, Dave ... I can't do that, Dave ...","","");
2190 switch (msginfo
.item
) {
2192 if (!show_message(&msginfo
)) { /* assume next exists ... */
2193 cache
= 0; /* border cond. - no cache */
2194 msginfo
.target
= msginfo
.source
; /* show same */
2195 msginfo
.subjnav
= 0;
2196 msginfo
.authnav
= 0;
2197 ret
= show_message(&msginfo
);
2201 if (!show_object(&msginfo
,ITEM_AUTHOR
))
2202 cgierr ("I couldn't find the author for that message","","");
2205 if (!show_object(&msginfo
,ITEM_SUBJECT
))
2206 cgierr ("I couldn't find the subject for that message","","");
2209 if (!show_object(&msginfo
,ITEM_DATE
)) {
2211 ret
= show_object(&msginfo
,ITEM_DATE
);
2215 if (!show_index(&msginfo
)) {
2216 tmptarget
= msginfo
.target
;
2217 findlastmsg(&msginfo
);
2218 cache
= 0; /* latest one - no cache */
2219 if (!msginfo
.target
|| msginfo
.target
> tmptarget
) {
2220 cache
= 2; /* won't change */
2221 firstdate(&msginfo
,1); /* first thread index */
2222 msginfo
.direction
= DIRECT_FIRST
;
2223 date2msg(&msginfo
); /* (may not be 1 if parts removed) */
2225 ret
= show_index(&msginfo
);
2229 strerr_die2x(100,FATAL
,"bad item in main");
2232 findlastmsg(&msginfo
); /* as last resort; last msgindex */
2234 ret
= show_message(&msginfo
);