1 #include <sys/types.h>
2 #include <unistd.h>
3 #include "alloc.h"
4 #include "direntry.h"
5 #include "datetime.h"
6 #include "now.h"
7 #include "stralloc.h"
8 #include "strerr.h"
9 #include "error.h"
10 #include "env.h"
11 #include "sig.h"
12 #include "open.h"
13 #include "getln.h"
14 #include "case.h"
15 #include "scan.h"
16 #include "str.h"
17 #include "fmt.h"
18 #include "readwrite.h"
19 #include "fork.h"
20 #include "wait.h"
21 #include "exit.h"
22 #include "substdio.h"
23 #include "getconf.h"
24 #include "gen_alloc.h"
25 #include "gen_allocdefs.h"
26 #include "constmap.h"
27 #include "byte.h"
28 #include "subdb.h"
29 #include "messages.h"
30 #include "makehash.h"
31 #include "mime.h"
32 #include "wrap.h"
33 #include "die.h"
34 #include "idx.h"
35 #include "yyyymm.h"
36 #include "cgi.h"
37 #include "auto_etc.h"
38
39 const char FATAL[] = "ezmlm-cgi: fatal: ";
40 const char USAGE[] =
41 "ezmlm-cgi: usage: ezmlm-cgi";
42 #define GET "-getv"
43 #define THREAD "-threadv"
44 #define SUBSCRIBE "-subscribe"
45 #define FAQ "-faq"
46 #define TXT_CGI_SUBSCRIBE "\">[Subscribe To List]</a>\n"
47 #define TXT_CGI_FAQ "\">[List FAQ]</a>\n"
48
49 int flagshowhtml = 1; /* show text/html parts. This leads to duplication */
50 /* when both text/plain and text/html are in a */
51 /* multipart/alternative message, but it is assumed*/
52 /* that text/html is not frivolous, but only used */
53 /* when the formatting is important. */
54 int flagobscure = 0; /* Don't remove Sender's E-mail address in message */
55 /* view. Overridden by config file (- before list */
56 /* name). */
57
58 /**************** Header processing ***********************/
59 static char headers_used[] = "Subject\\From\\Date\\content-type\\"
60 "content-transfer-encoding\\mime-version";
61 /* index of headers displayed (shown in order listed above) */
62 static int headers_shown[] = {1,1,1,0,0,0};
63 /* index of specific headers */
64 #define NO_HDRS 6
65 #define HDR_SUBJECT 1
66 #define HDR_FROM 2
67 #define HDR_CT 4
68 #define HDR_CTENC 5
69 #define HDR_VERSION 6
70
71 /* Need to add inits if you increase NO_HDRS */
72 static stralloc hdr[NO_HDRS] = { {0},{0},{0},{0},{0},{0} };
73 /**************** Header processing ***********************/
74
75
76 /* index of subject in above, first = 1 */
77
78 /* TODO: Sort headers before display. Find a way to display the body with the*/
79 /* correct charset, ideally letting the browser do the work (should really */
80 /* be able to specify charset for DIV ! */
81
82 /* ulong at least 32 bits. (Creating a Year 0xffffff problem ;-) */
83 #define MAXULONG 0xffffffff
84
85 static char cmdstr[5] = "xxx:";
86 #define ITEM "-msadiz"
87 #define ITEM_MESSAGE 1
88 #define ITEM_SUBJECT 2
89 #define ITEM_AUTHOR 3
90 #define ITEM_DATE 4
91 #define ITEM_INDEX 5
92
93 #define DIRECT "psnpnz"
94 #define DIRECT_SAME 0
95 #define DIRECT_NEXT 1
96 #define DIRECT_PREV -1
97 /* use only as the argument for some functions. Terrible hack for date links */
98 #define DIRECT_FIRST 3
99 #define DIRECT_LAST 2
100
101 static char *dir = 0;
102 static char *local = 0;
103 static char *host = 0;
104 static char *home = 0;
105 static char *banner = 0;
106 static const char *charset = 0;
107 static char *stylesheet = 0;
108 static char *cmd;
109 static char strnum[FMT_ULONG];
110 /* these are the only headers we really care about for message display */
111 /* one can always retrieve the complete message by E-mail */
112 static stralloc charg = {0};
113 static stralloc url = {0};
114 static stralloc author = {0};
115 static stralloc subject = {0};
116 static stralloc base = {0};
117 static stralloc line = {0};
118 static stralloc decline = {0}; /* for rfc2047-decoded headers and QP/base64 */
119 static stralloc cfline = {0}; /* from config file */
120 static stralloc fn = {0};
121 static stralloc dtline = {0};
122 static stralloc headers = {0};
123 static stralloc encoding = {0};
124 static stralloc content = {0};
125 static stralloc curcharset = {0};
126 static stralloc sainit = {0};
127 static struct constmap headermap;
128 static unsigned long uid,euid;
129 static int recursion_level;
130 static int so = 0;
131 static int ss23 = 0;
132 static int state = 0;
133 static int match; /* used everywhere and no overlap */
134 static int fd; /* same; never >1 open */
135 static int cache; /* 0 = don't; 1 = don't know; 2 = do */
136 static int flagtoplevel;
137 static unsigned int flagmime;
138 static unsigned int cs,csbase;
139 static int flagrobot;
140 static int flagpre;
141 static int precharcount;
142 static char cn1 = 0;
143 static char cn2 = 0;
144 static char lastjp[] = "B";/* to get back to the correct JP after line break */
145
146
147 static struct msginfo { /* clean info on the target message */
148 int item; /* What we want */
149 int direction; /* Relation to current msg */
150 int axis; /* Axis of desired movement [may be calculated] */
151 unsigned long source; /* reference message number */
152 unsigned long target;
153 unsigned long date;
154 unsigned long *authnav; /* msgnav structure */
155 unsigned long *subjnav; /* msgnav structure */
156 char *author;
157 char *subject;
158 char *cgiarg; /* sub/auth as expected from axis */
159 } msginfo;
160
161 static mime_info *mime_current = 0;
162 static mime_info *mime_tmp = 0;
163
164 static struct datetime dt;
165
166 static char inbuf[4096];
167 static substdio ssin;
168
die_syntax(const char * s)169 void die_syntax(const char *s)
170 {
171 strerr_die2x(100,FATAL,MSG1(ERR_SYNTAX_CONFIG,s));
172 }
173
174 static char outbuf[4096];
175 static substdio ssout = SUBSTDIO_FDBUF(write,1,outbuf,sizeof(outbuf));
176
oput(const char * s,unsigned int l)177 void oput(const char *s, unsigned int l)
178 /* unbuffered. Avoid extra copy as httpd buffers */
179 {
180 if (substdio_put(&ssout,s,l) == -1)
181 strerr_die2sys(111,FATAL,MSG(ERR_WRITE_STDOUT));
182 }
183
oputs(const char * s)184 void oputs(const char *s)
185 {
186 oput(s,str_len(s));
187 }
188
189 /* this error is for things that happen only if program logic is screwed up */
die_prog(const char * s)190 void die_prog(const char *s) { strerr_die5x(100,FATAL,"program error (please send bug report to bugs@ezmlm.org): ",s," Command: ",cmd); }
191
192 /* If we already issued a header than this will look ugly */
cgierr(const char * s,const char * s1,const char * s2)193 void cgierr(const char *s,const char *s1,const char *s2)
194 {
195 strerr_warn4(FATAL,s,s1,s2,(struct strerr *)0);
196 oputs("Content-type: text/plain\n");
197 oputs("Status: 500 Couldn't do it\n\n");
198 oputs("I tried my best, but:\n\n");
199 if (s) oputs(s);
200 if (s1) oputs(s1);
201 if (s2) oputs(s2);
202 oputs("\n");
203 substdio_flush(&ssout);
204 _exit(0);
205 }
206
207 static unsigned long msgnav[5]; /* 0 prev prev 1 prev 2 this 3 next 4 next-next */
208
toggle_flagpre(int flag)209 void toggle_flagpre(int flag)
210 {
211 flagpre = flag;
212 precharcount = 0;
213 cn1 = 0; cn2 = 0; /* just in case */
214 }
215
decode_charset(const char * s,unsigned int l)216 unsigned int decode_charset(const char *s,unsigned int l)
217 /* return charset code. CS_BAD means that base charset should be used, i.e. */
218 /* that charset is empty or likely invalid. CS_NONE are charsets for which */
219 /* we don't need to do anything special. */
220 {
221 if (case_startb(s,l,"iso-8859") || case_startb(s,l,"us-ascii") ||
222 case_startb(s,l,"utf")) /* at the moment, we can do utf-8 right */
223 return CS_NONE; /* what is utf-7 (used by OE)? */
224 if (case_startb(s,l,"x-cp") ||
225 case_startb(s,l,"cp") ||
226 case_startb(s,l,"x-mac") ||
227 case_startb(s,l,"koi8")) return CS_NONE;
228 if (!l || *s == 'x' || *s == 'X') return CS_BAD;
229 if (case_startb(s,l,"iso-2022")) {
230 if (case_startb(s+8,l-8,"-cn"))
231 return CS_2022_CN;
232 if (case_startb(s+8,l-8,"-jp"))
233 return CS_2022_JP;
234 return CS_2022_KR;
235 }
236 if (case_startb(s,l,"cn-") ||
237 case_startb(s,l,"hz-gb") ||
238 case_startb(s,l,"gb") ||
239 case_startb(s,l,"big5"))
240 return CS_CN; /* Only consideration for linebreak */
241 if (case_startb(s,l,"iso_8859") ||
242 case_startb(s,l,"latin") ||
243 case_startb(s,l,"windows")) return CS_NONE;
244 /* Add other charsets here. Later we will add code to replace a detected */
245 /* charset name with another, and to connect conversion routines, such as */
246 /* between windows-1251/koi-8r/iso-8859-5 */
247 return CS_BAD;
248 }
249
html_put(const char * s,unsigned int l)250 void html_put (const char *s,unsigned int l)
251 /* At this time, us-ascii, iso-8859-? create no problems. We just encode */
252 /* some html chars. iso-2022 may have these chars as character components.*/
253 /* cs is set for these, 3 for CN, 2 for others. Bit 0 set means 2 byte */
254 /* chars for SS2/SS3 shiftouts (JP doesn't use them, KR has single byte. */
255 /* If cs is set and we're shifted out (so set) we don't substitute. We */
256 /* also look for SI/SO to adjust so, and ESC to detect SS2/SS3. Need to */
257 /* ignore other ESC seqs correctly. JP doesn't use SI/SO, but uses */
258 /* ESC ( B/J and ESC $ B/@ analogously, so we use these to toggle so. */
259 /* "Roman", i.e. ESC ( J is treated as ascii - no differences in html- */
260 /* relevant chars. Together, this allows us to deal with all iso-2022-* */
261 /* as a package. see rfc1468, 1554, 1557, 1922 for more info. */
262 /* line break at 84 to avoid splits with lines just a little too long. */
263 {
264 if (!cs) { /* us-ascii & iso-8859- & unrecognized */
265 for (;l--;s++) {
266 precharcount++;
267 switch (*s) {
268 case '>': oputs(">"); break;
269 case '<': oputs("<"); break;
270 case '"': oputs("""); break;
271 case '&': oputs("&"); break;
272 case '\n': precharcount = 0; oput(s,1); break;
273 case ' ':
274 if (precharcount >= 84 && flagpre) {
275 oput("\n",1); /* in place of ' ' */
276 precharcount = 0;
277 } else
278 oput(s,1); /* otherwise out with it. */
279 break;
280 default: oput(s,1); break;
281 }
282 }
283 } else if (cs == CS_CN) { /* cn-, gb*, big5 */
284 for (;l--;s++) {
285 precharcount++;
286 if (cn1) { cn2 = cn1; cn1 = 0; } /* this is byte 2 */
287 else { cn2 = 0; cn1 = *s & 0x80; } /* this is byte 1/2 or ascii */
288 if (!cn1 && !cn2) { /* ascii */
289 switch (*s) {
290 case '>': oputs(">"); break;
291 case '<': oputs("<"); break;
292 case '"': oputs("""); break;
293 case '&': oputs("&"); break;
294 case '\n': precharcount = 0; oput(s,1); break;
295 case ' ':
296 if (precharcount >= 84 && flagpre) {
297 oput("\n",1); /* break in ascii sequence */
298 precharcount = 0;
299 } else
300 oput(s,1);
301 break;
302 default: oput(s,1); break;
303 }
304 } else if (precharcount >= 84 && flagpre && cn2) {
305 oput("\n",1); /* break after 2-byte code */
306 precharcount = 0;
307 }
308 }
309 } else { /* iso-2022 => PAIN! */
310 for (;l--;s++) {
311 precharcount++;
312 if (ss23) { /* ss2/ss3 character */
313 ss23--;
314 oput(s,1);
315 continue;
316 }
317 if (so) { /* = 0 ascii, = 1 SO charset */
318 if (!(*s & 0xe0)) { /* ctrl-char */
319 switch (*s) {
320 case ESC: state = 1; break;
321 case SI: so = 0; break;
322 case '\n': precharcount = 0; break;
323 default: break;
324 }
325 }
326 oput(s,1);
327 } else { /* check only ascii */
328 switch (*s) {
329 case '>': oputs(">"); break;
330 case '<': oputs("<"); break;
331 case '"': oputs("""); break;
332 case '&': oputs("&"); break;
333 case ' ':
334 if (precharcount >= 84 && flagpre) {
335 oput("\n",1); /* break in ascii sequence */
336 precharcount = 0;
337 } else
338 oput(s,1);
339 break;
340 default:
341 oput(s,1);
342 if (!(*s & 0xe0)) {
343 switch (*s) {
344 case SO: so = 1; break;
345 case ESC: state = 1; break;
346 case SI: so = 0; break; /* shouldn't happen */
347 case '\n': precharcount = 0; break;
348 default: break;
349 }
350 }
351 }
352 } /* by now all output is done, now ESC interpretation */
353 if (state) {
354 /* ESC code - don't count */
355 if (precharcount) precharcount--;
356 state++;
357 switch (state) {
358 case 2: break; /* this was the ESC */
359 case 3: switch (*s) {
360 case 'N': ss23 = (cs & 1) + 1; state = 0; break;
361 case 'O': ss23 = 2; state = 0; break;
362 case '(': state = 20; so = 0; break; /* JP ascii */
363 case '$': break; /* var S2/SS2/SS3 des*/
364 case '.': state = 10; /* g3 settings, one more char */
365 default: state = 0; break; /* or JP */
366 }
367 break;
368 case 4: switch (*s) { /* s2/ss2/ss3 or JP 2 byte shift */
369 case 'B':
370 case '@': lastjp[0] = *s;
371 so = 1; state = 0; break; /* JP */
372 default: break; /* other SS2/3 des */
373 }
374 break;
375 case 5: state = 0; break; /* 4th char of ESC $ *|+|) X */
376 case 11: state = 0; break; /* 3nd char of ESC . */
377 case 21: state = 0; break; /* ESC ( X for JP */
378 default: die_prog("bad state in html_put"); break;
379 }
380 } else if (so && flagpre && precharcount >= 84) {
381 /* 84 is nicer than 78/80 since most use GUI browser */
382 /* iso-2022-* line splitter here. SO only, SI done above */
383 /* For JP need even precharcount, add ESC ( B \n ESC $B */
384 if (so && !(precharcount & 1)) { /* even */
385 precharcount = 0; /* reset */
386 if (cs == CS_2022_JP) { /* JP uses ESC like SI/SO */
387 oputs(TOASCII);
388 oput("\n",1);
389 oputs(TOJP);
390 oput(lastjp,1);
391 } else {
392 if (so) {
393 /* For iso-2022-CN: nothing if SI, otherwise SI \n SO */
394 /* For iso-2022-KR same */
395 oputs(SI_LF_SO);
396 } else
397 oput("\n",1);
398 }
399 }
400 }
401 }
402 }
403 }
404
405 static const char hexchar[] = "0123456789ABCDEF";
406 static char enc_url[] = "%00";
407
urlencode_put(const char * s,unsigned int l)408 void urlencode_put (const char *s,unsigned int l)
409 {
410 for (;l--;s++) {
411 unsigned char ch;
412 ch = (unsigned char) *s;
413 if (ch <= 32 || ch > 127 || byte_chr("?<>=/:%+#\"",10,ch) != 10) {
414 enc_url[2] = hexchar[ch & 0xf];
415 enc_url[1] = hexchar[(ch >> 4) & 0xf];
416 oput(enc_url,3);
417 } else
418 oput(s,1);
419 }
420 }
421
urlencode_puts(const char * s)422 void urlencode_puts(const char *s)
423 {
424 urlencode_put(s,str_len(s));
425 }
426
anchor_put(char * s,unsigned int l)427 void anchor_put(char *s, unsigned int l)
428 /* http://, ftp:// only */
429 {
430 char *cpl,*cpafter,*cpstart,*cpend;
431 unsigned int pos,i;
432
433 pos = byte_chr(s,l,':');
434 if (pos + 3 >= l || !pos) { /* no ':' no URL (most lines) */
435 html_put(s,l);
436 return;
437 }
438
439 cpl = s;
440 cpafter = s + l;
441 for (;;) {
442 cpstart = (char *) 0;
443 if (s[pos + 1] == '/' && s[pos + 2] == '/') {
444 cpend = s + pos + 2;
445 for (i = pos - 1; i + 6 >= pos; i--) { /* pos always >=1 */
446 if ((s[i] < 'a' || s[i] > 'z') && (s[i] < 'A' || s[i] > 'Z')) {
447 cpstart = s + i + 1; /* "[:alpha:]{1,5}://" accepted */
448 break;
449 }
450 if (!i && i + 6 < pos) {
451 cpstart = s;
452 break;
453 }
454 }
455 }
456 if (cpstart) { /* found URL */
457 while (cpend < cpafter && str_chr(" \t\n",*cpend) == 3) cpend++;
458 cpend--; /* locate end */
459 while (cpend > cpstart && str_chr(".,;])>\"\'",*cpend) != 8) cpend--;
460 html_put(cpl,cpstart - cpl); /* std txt */
461 oputs("<a href=\""); /* link start */
462 oput(cpstart,cpend - cpstart + 1); /* link */
463 oputs("\">");
464 html_put(cpstart,cpend - cpstart + 1); /* visible */
465 oputs("</a>"); /* end */
466 cpl = cpend + 1;
467 pos = cpend - s;
468 if (pos >= l) return;
469 } else
470 pos++;
471 pos += byte_chr(s + pos,l - pos,':');
472 if (pos + 3 >= l) {
473 html_put(cpl,cpafter - cpl); /* std txt */
474 return;
475 }
476 }
477 }
478
checkhash(const char * s)479 int checkhash(const char *s)
480 {
481 int l = HASHLEN;
482 while (l--) {
483 if (*s < 'a' || *s > 'p') return 0; /* illegal */
484 s++;
485 }
486 if (*s) return 0; /* extraneous junk */
487 return 1;
488 }
489
makefn(stralloc * sa,int item,unsigned long n,const char * hash)490 int makefn(stralloc *sa,int item,unsigned long n,const char *hash)
491 {
492 if (!stralloc_copys(sa,"archive/")) die_nomem();
493 if (item == ITEM_MESSAGE) {
494 if (!stralloc_catb(sa,strnum,fmt_ulong(strnum, n / 100))) die_nomem();
495 if (!stralloc_cats(sa,"/")) die_nomem();
496 if (!stralloc_catb(sa,strnum,fmt_uint0(strnum,(unsigned int) (n % 100),2)))
497 die_nomem();
498 } else if (item == ITEM_DATE) {
499 if (!stralloc_cats(sa,"threads/")) die_nomem();
500 if (!stralloc_catb(sa,strnum,fmt_ulong(strnum,n)))
501 die_nomem();
502 } else if (item == ITEM_INDEX) {
503 if (!stralloc_catb(sa,strnum,fmt_ulong(strnum, n / 100))) die_nomem();
504 if (!stralloc_cats(sa,"/index")) die_nomem();
505 } else {
506 if (item == ITEM_AUTHOR) {
507 if (!stralloc_cats(sa,"authors/")) die_nomem();
508 } else {
509 if (!stralloc_cats(sa,"subjects/")) die_nomem();
510 }
511 if (!hash) return 0;
512 if (!stralloc_catb(sa,hash,2)) die_nomem();
513 if (!stralloc_cats(sa,"/")) die_nomem();
514 if (!stralloc_catb(sa,hash+2,HASHLEN-2)) die_nomem();
515 }
516 if (!stralloc_0(sa)) die_nomem();
517 return 1;
518 }
519
alink(struct msginfo * infop,int item,int axis,unsigned long msg,const char * data,unsigned int l)520 void alink(struct msginfo *infop,int item,int axis,
521 unsigned long msg,const char *data,unsigned int l)
522 /* links with targets other msg -> msg. If the link is for author, we */
523 /* still supply subject, since most navigation at the message level will */
524 /* be along threads rather than author and we don't have an author index.*/
525 {
526 const char *cp;
527
528 cp = (const char *) 0;
529 /* this should be separate routine. Works because all index views */
530 /* have at least a subject link */
531 if (axis == ITEM_SUBJECT && infop->target == msg)
532 oputs("<a name=\"b\"></a>");
533 oput(url.s,url.len);
534 cmdstr[0] = ITEM[item];
535 cmdstr[1] = ITEM[axis];
536 cmdstr[2] = DIRECT[DIRECT_SAME + 1];
537 if (item == ITEM_MESSAGE && axis == ITEM_AUTHOR) {
538 if (infop->subject) {
539 cmdstr[1] = ITEM[ITEM_SUBJECT];
540 cp = infop->subject; /* always HASLEN in length due to decode_cmd */
541 }
542 }
543 oputs(cmdstr); /* e.g. map: */
544 oput(strnum,fmt_ulong(strnum,msg));
545 if (!cp && l >= HASHLEN)
546 cp = data;
547 if (infop->date) {
548 oput(":",1);
549 oput(strnum,fmt_ulong(strnum,infop->date));
550 }
551 if (cp) {
552 oput(":",1);
553 oput(cp,HASHLEN);
554 }
555 switch (item) {
556 case ITEM_MESSAGE: oputs("\" class=\"mlk\">"); break;
557 case ITEM_AUTHOR: oputs("#b\" class=\"alk\">"); break;
558 case ITEM_SUBJECT: oputs("#b\" class=\"slk\">"); break;
559 default: oputs("#b\">"); break;
560 }
561 if (HASHLEN + 1 < l)
562 html_put(data + HASHLEN + 1,l - HASHLEN - 1);
563 else
564 oputs("(none)");
565 oputs("</a>");
566 }
567
linktoindex(struct msginfo * infop,int item)568 void linktoindex(struct msginfo *infop,int item)
569 /* for links from message view back to author/subject/threads index */
570 {
571 oput(url.s,url.len);
572 cmdstr[0] = ITEM[item];
573 cmdstr[1] = ITEM[item];
574 cmdstr[2] = DIRECT[DIRECT_SAME + 1];
575 oputs(cmdstr); /* e.g. map: */
576 oput(strnum,fmt_ulong(strnum,infop->target));
577 if (infop->date) {
578 oput(":",1);
579 oput(strnum,fmt_ulong(strnum,infop->date));
580 }
581 switch (item) {
582 case ITEM_AUTHOR:
583 if (infop->author) {
584 oput(":",1);
585 oputs(infop->author);
586 }
587 break;
588 case ITEM_SUBJECT:
589 if (infop->subject) {
590 oput(":",1);
591 oputs(infop->subject);
592 }
593 break;
594 default:
595 break;
596 }
597 oputs("#b\"");
598 }
599
link_msg(struct msginfo * infop,int axis,int direction)600 void link_msg(struct msginfo *infop,int axis,int direction)
601 /* Creates <a href="mapa:123:aaaaa...."> using a maximum of available */
602 /* info only for links where the target is a message */
603 {
604 unsigned long msg;
605 char *acc;
606 oput(url.s,url.len);
607 cmdstr[0] = ITEM[ITEM_MESSAGE];
608 cmdstr[1] = ITEM[axis];
609 cmdstr[2] = DIRECT[direction + 1];
610 msg = infop->target;
611 acc = 0;
612 switch(axis) {
613 case ITEM_SUBJECT:
614 if (infop->subject)
615 acc = infop->subject;
616 if (infop->subjnav) /* translate to message navigation */
617 if (infop->subjnav[direction]) {
618 msg = infop->subjnav[direction];
619 cmdstr[2] = DIRECT[DIRECT_SAME + 1];
620 }
621 acc = infop->subject;
622 break;
623 case ITEM_AUTHOR:
624 if (infop->author)
625 acc = infop->author;
626 if (infop->authnav) /* translate to message navigation */
627 if (infop->authnav[direction]) {
628 msg = infop->authnav[direction];
629 cmdstr[2] = DIRECT[DIRECT_SAME + 1];
630 }
631 acc = infop->author;
632 break;
633 default:
634 break;
635 }
636 oputs(cmdstr);
637 oput(strnum,fmt_ulong(strnum,msg));
638 if (acc) {
639 oputs(":");
640 oputs(acc);
641 }
642 oputs("\">");
643 }
644
justpress()645 void justpress()
646 {
647 oputs("?subject=");
648 urlencode_puts("Just Click \"SEND\"!");
649 }
650
homelink()651 void homelink()
652 {
653 const char *cp,*cp1,*cp2;
654
655 if (home && *home) {
656 cp = home;
657 for(;;) {
658 cp1 = cp;
659 while(*cp1 && *cp1 != '=') cp1++;
660 if (!*cp1) break;
661 cp2 = cp1;
662 while(*cp2 && *cp2 != ',') cp2++;
663 oputs("<a href=\"");
664 oput(cp1 + 1,cp2 - cp1 - 1);
665 oputs("\">");
666 oput(cp,cp1 - cp);
667 oputs("</a>\n");
668 if (!*cp2) break;
669 cp = cp2 + 1;
670 }
671 }
672 }
673
subfaqlinks()674 void subfaqlinks()
675 {
676 oputs("<a href=\"mailto:");
677 oputs(local);
678 oputs(SUBSCRIBE);
679 oputs("@");
680 oputs(host);
681 justpress();
682 oputs(TXT_CGI_SUBSCRIBE);
683 oputs("<a href=\"mailto:");
684 oputs(local);
685 oputs(FAQ);
686 oputs("@");
687 oputs(host);
688 justpress();
689 oputs(TXT_CGI_FAQ);
690 }
691
msglinks(struct msginfo * infop)692 void msglinks(struct msginfo *infop)
693 /* Creates the html for all links from one message view */
694 {
695 oputs("<div class=\"msglinks\"><strong>Msg by: ");
696 link_msg(infop,ITEM_SUBJECT,DIRECT_PREV);
697 oputs("[<-</a> ");
698 linktoindex(infop,ITEM_SUBJECT);
699 oputs(">thread</a> ");
700 link_msg(infop,ITEM_SUBJECT,DIRECT_NEXT);
701 oputs("->]</a> \n");
702 link_msg(infop,ITEM_MESSAGE,DIRECT_PREV);
703 oputs("[<-</a> ");
704 linktoindex(infop,ITEM_INDEX);
705 oputs(">time</a> ");
706 link_msg(infop,ITEM_MESSAGE,DIRECT_NEXT);
707 oputs("->]</a> \n");
708 link_msg(infop,ITEM_AUTHOR,DIRECT_PREV);
709 oputs("[<-</a> ");
710 linktoindex(infop,ITEM_AUTHOR);
711 oputs(">author</a> ");
712 link_msg(infop,ITEM_AUTHOR,DIRECT_NEXT);
713 oputs("->]</a> |\n");
714 linktoindex(infop,ITEM_DATE);
715 oputs(">[Threads]</a>\n");
716 homelink();
717 oputs("\n<a href=\"mailto:");
718 oputs(local);
719 oputs(GET);
720 strnum[fmt_ulong(strnum,infop->target)] = '\0';
721 oputs(strnum);
722 oputs("@");
723 oputs(host);
724 justpress();
725 oputs("\">[Email Msg]</a>\n");
726 oputs("<a href=\"mailto:");
727 oputs(local);
728 oputs(THREAD);
729 oputs(strnum);
730 oputs("@");
731 oputs(host);
732 justpress();
733 oputs("\">[Email Thread]</a>\n");
734 subfaqlinks();
735 oputs("</strong></div>\n");
736 }
737
738 #define SPC_BASE 1
739 #define SPC_BANNER 2
740
html_header(const char * t,const char * s,unsigned int l,const char * class,int flagspecial)741 void html_header(const char *t,const char *s,unsigned int l,const char *class,int flagspecial)
742 /* flagspecial: 0x1 => robot index; no style sheet, no BASE */
743 /* flagspecial: 0x2 => banner, if available */
744 {
745 oputs("Content-Type: text/html; charset=");
746 oput(curcharset.s,curcharset.len);
747
748 oputs("\nCache-Control: ");
749 switch (cache) {
750 case 0:
751 oputs("no-cache"); /* known upper border */
752 break;
753 case 1:
754 oputs("max-age=300"); /* 5 min - most lists aren't that fast*/
755 break;
756 case 2:
757 oputs("max-age=1209600"); /* 14 days is a long time */
758 break;
759 }
760 oputs("\n\n");
761 oputs("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n");
762 oputs("<html xmlns=\"http://www.w3.org/1999/xhtml\"><head>\n<title>");
763 if (local) {
764 oputs(local);
765 oputs("@");
766 oputs(host);
767 oputs(": ");
768 }
769 if (t) oputs(t);
770 if (s) html_put(s,l);
771 oputs("</title>\n");
772 if (class && *class && stylesheet && *stylesheet) {
773 oputs("<link href=\"");
774 oputs(stylesheet);
775 oputs("\" rel=\"stylesheet\" type=\"text/css\" />\n");
776 }
777 if (!flagrobot) /* robot access allowed to follow */
778 oputs("<meta name=\"robots\" content=\"noindex\" />\n");
779 if (flagrobot < 2)
780 oputs("<meta name=\"robots\" content=\"nofollow\" />\n");
781 if (flagspecial & SPC_BASE)
782 oput(base.s,base.len);
783 oputs("</head>\n");
784 if (class && *class) {
785 oputs("<body class=\"");
786 oputs(class);
787 oputs("\">\n");
788 } else
789 oputs("<body>\n");
790
791 }
792
html_footer(int flagspecial)793 void html_footer(int flagspecial)
794 {
795 if ((flagspecial & SPC_BANNER) && banner && *banner) {
796 oputs("<div class=\"banner\">\n");
797 if (*banner == '<') oputs(banner);
798 else
799 strerr_die2x(100,FATAL,"Sorry - banner programs not supported");
800 oputs("</div>\n");
801 }
802 oputs("</body>\n</html>\n");
803 substdio_flush(&ssout);
804 }
805
806 /* DATE functions */
807
datelink(struct msginfo * infop,unsigned long d,int direction)808 void datelink(struct msginfo *infop,unsigned long d,int direction)
809 /* output a date with link back to thread index */
810 {
811 oput(url.s,url.len);
812 cmdstr[0] = ITEM[ITEM_DATE];
813 cmdstr[1] = ITEM[ITEM_DATE];
814 cmdstr[2] = DIRECT[direction + 1];
815 oputs(cmdstr);
816 if (direction == DIRECT_LAST)
817 oput("0",1); /* suppress msgnum to avoid going there */
818 else
819 oput(strnum,fmt_ulong(strnum,infop->target));
820 oputs(":");
821 oput(strnum,fmt_ulong(strnum,d));
822 oputs("#b\">");
823 switch (direction) {
824 case DIRECT_SAME:
825 if (dateline(&dtline,d) < 0) die_nomem();
826 oput(dtline.s,dtline.len);
827 break;
828 case DIRECT_PREV:
829 oputs("[<-]");
830 break;
831 case DIRECT_NEXT:
832 oputs("[->]");
833 break;
834 case DIRECT_FIRST:
835 oputs("[<<-]");
836 break;
837 case DIRECT_LAST:
838 oputs("[->>]");
839 break;
840 }
841 oputs("</a>");
842 }
843
finddate(struct msginfo * infop)844 void finddate(struct msginfo *infop)
845 /* DIRECT_SAME works as DIRECT_PREV, dvs returns previous date or last date */
846 {
847 DIR *archivedir;
848 direntry *d;
849 unsigned long ddate, startdate;
850 unsigned long below, above;
851
852 below = 0L;
853 above = MAXULONG; /* creating a Y 0xffffff problem */
854 startdate = infop->date;
855 archivedir = opendir("archive/threads/");
856 if (!archivedir) {
857 strerr_die2sys((errno == error_noent) ? 100 : 111,
858 FATAL,MSG1(ERR_OPEN,"/archive/threads"));
859 }
860 while ((d = readdir(archivedir))) { /* dxxx/ */
861 if (str_equal(d->d_name,".")) continue;
862 if (str_equal(d->d_name,"..")) continue;
863 scan_ulong(d->d_name,&ddate);
864 if (!ddate) continue; /* just in case some smart guy ... */
865 if (startdate) {
866 if (ddate > startdate && ddate < above) above = ddate;
867 if (ddate < startdate && ddate > below) below = ddate;
868 } else {
869 if (ddate < above) above = ddate;
870 if (ddate > below) below = ddate;
871 }
872 }
873 closedir(archivedir);
874
875 if (infop->direction == DIRECT_NEXT && (above != MAXULONG || !below))
876 /* we always give a valid date as long as there is at least one */
877 infop->date = above;
878 else
879 infop->date = below;
880 return;
881 }
882
latestdate(struct msginfo * infop,int flagfail)883 void latestdate(struct msginfo *infop,int flagfail)
884 {
885 if (!flagfail) {
886 datetime_tai(&dt,now());
887 infop->date = ((unsigned long) dt.year + 1900) * 100 + dt.mon + 1;
888 } else {
889 infop->date = 0;
890 infop->direction = DIRECT_PREV;
891 finddate(infop);
892 }
893 }
894
firstdate(struct msginfo * infop)895 void firstdate(struct msginfo *infop)
896 {
897 infop->date = 0;
898 infop->direction = DIRECT_NEXT;
899 finddate(infop);
900 }
901
gtdate(struct msginfo * infop,int flagfail)902 void gtdate(struct msginfo *infop,int flagfail)
903 /* infop->date has to be 0 or valid on entry. Month outside of [1-12] on */
904 /* entry causes GIGO */
905 {
906 if (!flagfail) { /* guess */
907 if (infop->direction == DIRECT_NEXT) {
908 infop->date++;
909 if (infop->date % 100 > 12) infop->date += (100 - 12);
910 } else if (infop->direction == DIRECT_PREV) {
911 infop->date--;
912 if (!infop->date % 100) infop->date -= (100 - 12);
913 }
914 } else
915 finddate(infop);
916 return;
917 }
918
indexlinks(struct msginfo * infop)919 void indexlinks(struct msginfo *infop)
920 {
921 unsigned long tmpmsg;
922
923 tmpmsg = infop->target;
924 infop->target = 1;
925 oputs("<div class=\"idxlinks\"><strong>");
926 linktoindex(infop,ITEM_INDEX);
927 oputs(">[<<-]</a>\n");
928 if (tmpmsg >= 100) infop->target = tmpmsg - 100;
929 linktoindex(infop,ITEM_INDEX);
930 oputs(">[<-]</a>\n");
931 infop->target = tmpmsg + 100;
932 linktoindex(infop,ITEM_INDEX);
933 oputs(">[->]</a>\n");
934 infop->target = MAXULONG;
935 linktoindex(infop,ITEM_INDEX);
936 oputs(">[->>]</a> |\n");
937 infop->target = tmpmsg;
938 linktoindex(infop,ITEM_DATE);
939 oputs(">[Threads by date]</a>\n");
940 subfaqlinks();
941 homelink();
942 oputs("</strong></div>\n");
943 }
944
show_index(struct msginfo * infop)945 int show_index(struct msginfo *infop)
946 {
947 unsigned long thismsg;
948 unsigned int pos;
949 char ch;
950
951 (void) makefn(&fn,ITEM_INDEX,msginfo.target,"");
952 if ((fd = open_read(fn.s)) == -1) {
953 if (errno == error_noent)
954 return 0;
955 else
956 strerr_die2sys(111,FATAL,MSG1(ERR_OPEN,fn.s));
957 }
958 substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
959 if (!stralloc_copyb(&line,strnum,
960 fmt_ulong(strnum,(unsigned long) (infop->target / 100))))
961 die_nomem();
962 if (!stralloc_cats(&line,"xx")) die_nomem();
963 html_header("Messages ",line.s,line.len,"idxbody",SPC_BANNER | SPC_BASE);
964 indexlinks(infop);
965 oputs("<div><hr /></div><h1 id=\"idxhdr\">");
966 oputs("Messages ");
967 oput(line.s,line.len);
968 oputs("</h1>\n");
969 oputs("<div class=\"idx\"><hr />\n");
970 for (;;) {
971 if (getln(&ssin,&line,&match,'\n') == -1)
972 strerr_die2sys(111,FATAL,MSG1(ERR_READ,fn.s));
973 if (!match)
974 break;
975 pos = scan_ulong(line.s,&thismsg);
976 ch = line.s[pos++];
977 pos++;
978 if (line.len < pos + 1 + HASHLEN)
979 strerr_die2x(100,FATAL,"index line with truncated subject entry");
980 if (!stralloc_copyb(&subject,line.s+pos,HASHLEN)) die_nomem();
981 if (!stralloc_0(&subject)) die_nomem();
982 infop->axis = ITEM_SUBJECT;
983 infop->subject = subject.s;
984 oput(strnum,fmt_uint0(strnum,(unsigned int) thismsg % 100,2));
985 oputs(": ");
986 alink(infop,ITEM_MESSAGE,ITEM_SUBJECT,thismsg,line.s+pos,line.len - pos - 1);
987 oputs("\n");
988 if (ch == ':') {
989 if (getln(&ssin,&line,&match,'\n') == -1)
990 strerr_die2sys(111,FATAL,MSG1(ERR_READ,fn.s));
991 if (!match)
992 break;
993 pos = byte_chr(line.s,line.len,';');
994 if (pos != line.len) {
995 infop->date = date2yyyymm(line.s);
996 oputs("(");
997 alink(infop,ITEM_AUTHOR,ITEM_AUTHOR,thismsg,line.s+pos+1,
998 line.len - pos - 2);
999 oputs(")<br />\n");
1000 }
1001 }
1002 }
1003 close(fd);
1004 oputs("\n<hr /></div>\n");
1005 indexlinks(infop);
1006 html_footer(SPC_BANNER);
1007 return 1;
1008 }
1009
objectlinks(struct msginfo * infop,int item)1010 void objectlinks(struct msginfo *infop,int item)
1011 {
1012 oputs("<div class=\"objlinks\"><strong>\n");
1013 if (item == ITEM_DATE) {
1014 datelink(infop,0,DIRECT_FIRST);
1015 datelink(infop,infop->date,DIRECT_PREV);
1016 datelink(infop,infop->date,DIRECT_NEXT);
1017 datelink(infop,0,DIRECT_LAST);
1018 oputs("\n");
1019 } else {
1020 if (!infop->target) infop->axis = ITEM_DATE;
1021 linktoindex(infop,ITEM_DATE);
1022 oputs(">[Threads by date]</a>\n");
1023 }
1024 if (item != ITEM_INDEX) {
1025 linktoindex(infop,ITEM_INDEX);
1026 oputs(">[Messages by date]</a>\n");
1027 }
1028 homelink();
1029 subfaqlinks();
1030 oputs("</strong></div>\n");
1031 }
1032
show_object(struct msginfo * infop,int item)1033 int show_object(struct msginfo *infop,int item)
1034 /* shows thread, threads, author */
1035 /* infop has the info needed to access the author/subject/thread file */
1036 {
1037 unsigned long lastdate,thisdate,thismsg;
1038 char linkitem;
1039 char targetitem;
1040 unsigned int pos;
1041
1042 lastdate = 0L;
1043 targetitem = ITEM_MESSAGE; /* default message is target */
1044 switch (item) {
1045 case ITEM_SUBJECT:
1046 if (!makefn(&fn,ITEM_SUBJECT,0L,infop->subject)) return 0;
1047 break;
1048 case ITEM_AUTHOR:
1049 if (!makefn(&fn,ITEM_AUTHOR,0L,infop->author)) return 0;
1050 break;
1051 case ITEM_DATE:
1052 if (!makefn(&fn,ITEM_DATE,infop->date,"")) return 0;
1053 break;
1054 default:
1055 die_prog("Bad object type in show_object");
1056 }
1057 if ((fd = open_read(fn.s)) == -1) {
1058 if (errno == error_noent)
1059 return 0;
1060 else
1061 strerr_die2sys(111,FATAL,MSG1(ERR_OPEN,fn.s));
1062 }
1063 substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
1064 if (item != ITEM_DATE) {
1065 if (getln(&ssin,&line,&match,'\n') == -1) /* read subject */
1066 strerr_die2sys(111,FATAL,MSG1(ERR_READ,fn.s));
1067 if (!match || line.len < HASHLEN + 2)
1068 strerr_die2x(111,FATAL,MSG1(ERR_READ_NOTHING,fn.s));
1069 }
1070 switch (item) {
1071 case ITEM_SUBJECT:
1072 html_header("Thread on: ",line.s + HASHLEN + 1,
1073 line.len - HASHLEN - 2,"subjbody",SPC_BANNER | SPC_BASE);
1074 objectlinks(infop,item);
1075 oputs("<div><hr /></div><h1>On: ");
1076 oput(line.s+HASHLEN+1,line.len-HASHLEN-2);
1077 oputs("</h1>\n");
1078 break;
1079 case ITEM_AUTHOR:
1080 html_header("Posts by: ",line.s + HASHLEN + 1,
1081 line.len - HASHLEN - 2,"authbody",SPC_BANNER | SPC_BASE);
1082 objectlinks(infop,item);
1083 oputs("<div><hr /></div><h1>By: ");
1084 oput(line.s+HASHLEN+1,line.len-HASHLEN-2);
1085 oputs("</h1>\n");
1086 break;
1087 case ITEM_DATE:
1088 /* targetitem = ITEM_SUBJECT;*/ /* thread index is target */
1089 thisdate = infop->date;
1090 if (dateline(&dtline,infop->date) < 0) die_nomem();
1091 html_header("Threads for ",
1092 dtline.s,dtline.len,"threadsbody",SPC_BANNER | SPC_BASE);
1093 objectlinks(infop,item);
1094 oputs("<div><hr /></div><h1>Threads for ");
1095 oput(dtline.s,dtline.len);
1096 oputs("</h1>\n");
1097 break;
1098 default: die_prog("unrecognized object type in show_object");
1099 }
1100
1101 oputs("<div class=\"obj\">\n");
1102 for (;;) {
1103 if (getln(&ssin,&line,&match,'\n') == -1) /* read subject */
1104 strerr_die2sys(111,FATAL,MSG1(ERR_READ,fn.s));
1105 if (!match)
1106 break;
1107 pos = scan_ulong(line.s,&thismsg);
1108 if (line.s[pos++] != ':')
1109 strerr_die4x(100,FATAL,"entry in ",fn.s," lacks message number");
1110 if (item != ITEM_DATE) { /* no date for threads by date */
1111 pos += scan_ulong(line.s+pos,&thisdate);
1112 infop->date = thisdate;
1113 if (line.s[pos++] != ':')
1114 strerr_die4x(100,FATAL,"entry in ",fn.s," lacks date");
1115 }
1116 if (line.len < pos + HASHLEN + 2)
1117 strerr_die4x(100,FATAL,"entry in ",fn.s," lacks hash");
1118 if (thisdate != lastdate) {
1119 oputs("<h2>");
1120 datelink(infop,thisdate,DIRECT_SAME);
1121 lastdate = thisdate;
1122 oputs("</h2>\n");
1123 /* moved <h2> out of <ul> */
1124 /* if (!lastdate)
1125 oputs("<ul>\n");
1126 else
1127 oputs("<p>");
1128 oputs("<li>\n"); */
1129
1130 oputs("<ul>\n");
1131
1132 }
1133 /* li at beginning of each link */
1134 oputs("<li>");
1135
1136 if (item == ITEM_SUBJECT)
1137 linkitem = ITEM_AUTHOR;
1138 else
1139 linkitem = ITEM_SUBJECT;
1140 alink(infop,targetitem,linkitem,thismsg,line.s+pos,line.len - pos - 1);
1141 oputs("</li>\n");
1142 }
1143 close(fd);
1144 oputs("</ul>\n");
1145 if (!infop->target)
1146 oputs("<a name=\"b\"></a>");
1147 oputs("<hr /></div>\n");
1148 objectlinks(infop,item);
1149 html_footer(SPC_BANNER);
1150 return 1;
1151 }
1152
clear_mime()1153 void clear_mime()
1154 {
1155 mime_current->charset.len = 0; /* exist but need emptying */
1156 mime_current->boundary.len = 0;
1157 mime_current->ctype.len = 0;
1158 mime_current->mimetype = MIME_NONE;
1159 mime_current->ctenc = CTENC_NONE;
1160 mime_current->cs = CS_NONE;
1161 }
1162
new_mime()1163 void new_mime()
1164 {
1165 mime_tmp = mime_current;
1166 if (mime_current)
1167 mime_current = mime_current->next;
1168 if (!mime_current) {
1169 if (!(mime_current = (mime_info *) alloc(sizeof (mime_info))))
1170 die_nomem();
1171 mime_current->charset = sainit; /* init */
1172 mime_current->boundary = sainit;
1173 mime_current->ctype = sainit;
1174 mime_current->next = (mime_info *) 0;
1175 mime_current->previous = mime_tmp;
1176 }
1177 clear_mime();
1178 if (mime_tmp)
1179 mime_current->level = mime_tmp->level + 1;
1180 else
1181 mime_current->level = 1;
1182 }
1183
mime_getarg(stralloc * sa,char ** s,unsigned int * l)1184 void mime_getarg(stralloc *sa,char **s, unsigned int *l)
1185 /* copies next token or "token" into sa and sets s & l appropriately */
1186 /* for continuing the search */
1187 {
1188 char *cp, *cpafter, *cpnext;
1189
1190 if (!*l || !**s) return;
1191 if (**s == '"') {
1192 (*s)++; (*l)--;
1193 cp = *s; cpnext = cp + *l; cpafter = cpnext;
1194 while (cp < cpafter) {
1195 if (*cp == '"') {
1196 break;
1197 }
1198 cp++;
1199 }
1200 cpnext = cp;
1201 } else {
1202 cp = *s; cpnext = cp + *l; cpafter = cpnext;
1203 while (cp < cpafter) {
1204 if (*cp == ' ' || *cp == '\t' || *cp == '\n' || *cp == ';') {
1205 break;
1206 }
1207 cp++;
1208 }
1209 cpnext = cp;
1210 }
1211 if (!stralloc_copyb(sa,*s,cp - *s)) die_nomem();
1212 *l = cpafter - cpnext; /* always >= 0 */
1213 *s = cpnext;
1214 return;
1215 }
1216
decode_mime_type(char * s,unsigned int l,unsigned int domime)1217 void decode_mime_type(char *s,unsigned int l,unsigned int domime)
1218 {
1219 char *st;
1220 unsigned int r,lt;
1221 if (!domime || !l) { /* treat non-MIME as plain text */
1222 mime_current->mimetype = MIME_TEXT_PLAIN;
1223 if (!stralloc_copys(&curcharset,charset)) die_nomem();
1224 /* should be us-ascii, but this is very likely better */
1225 return;
1226 }
1227 r = MIME_APPLICATION_OCTETSTREAM;
1228 while (l && (*s == ' ' || *s == '\t')) { s++; l--; } /* skip LWSP */
1229 mime_getarg(&(mime_current->ctype),&s,&l);
1230 st = mime_current->ctype.s;
1231 lt = mime_current->ctype.len;
1232 if (case_startb(st,lt,"text")) { /* text types */
1233 r = MIME_TEXT; st+= 4; lt-= 4;
1234 if (case_startb(st,lt,"/plain")) {
1235 r = MIME_TEXT_PLAIN; st+= 6; lt-= 6;
1236 } else if (case_startb(st,lt,"/html")) {
1237 r = MIME_TEXT_HTML; st+= 5; lt-= 5;
1238 } else if (case_startb(st,lt,"/enriched")) {
1239 r = MIME_TEXT_ENRICHED; st+= 9; lt-= 9;
1240 } else if (case_startb(st,lt,"/x-vcard")) {
1241 r = MIME_TEXT_ENRICHED; st+= 8; lt-= 8;
1242 }
1243 } else if (case_startb(st,lt,"multipart")) { /* multipart types */
1244 r = MIME_MULTI; st += 9; lt-= 9;
1245 if (case_startb(st,lt,"/alternative")) {
1246 r = MIME_MULTI_ALTERNATIVE; st+= 12; lt-= 12;
1247 } else if (case_startb(st,lt,"/mixed")) {
1248 r = MIME_MULTI_MIXED; st+= 6; lt-= 6;
1249 } else if (case_startb(st,lt,"/digest")) {
1250 r = MIME_MULTI_DIGEST; st+= 7; lt-= 7;
1251 } else if (case_startb(st,lt,"/signed")) {
1252 r = MIME_MULTI_SIGNED; st+= 7; lt-= 7;
1253 }
1254 } else if (case_startb(st,lt,"message")) { /* message types */
1255 r = MIME_MESSAGE; st += 7; lt -= 7;
1256 if (case_startb(st,lt,"/rfc822")) {
1257 r = MIME_MESSAGE_RFC822; st+= 7; lt-= 7;
1258 }
1259 }
1260 mime_current->mimetype = r;
1261 while (l) {
1262 while (l && (*s == ' ' || *s == '\t' || *s == ';' || *s == '\n')) {
1263 s++; l--; } /* skip ;LWSP */
1264 if (case_startb(s,l,"boundary=")) {
1265 s += 9; l-= 9;
1266 mime_getarg(&(mime_current->boundary),&s,&l);
1267 } else if (case_startb(s,l,"charset=")) {
1268 s += 8; l-= 8;
1269 mime_getarg(&(mime_current->charset),&s,&l);
1270 cs = decode_charset(mime_current->charset.s,
1271 mime_current->charset.len);
1272 if (cs == CS_BAD) cs = csbase; /* keep base cs */
1273 else
1274 if (!stralloc_copy(&curcharset,&mime_current->charset)) die_nomem();
1275 } else { /* skip non LWSP */
1276 for (;;) {
1277 if (!l) break;
1278 if (*s == '"') {
1279 s++, l--;
1280 while (l && *s != '"') { s++, l--; }
1281 if (l) { s++, l--; }
1282 break;
1283 } else {
1284 if (!l || *s == ' ' || *s == '\t' || *s == '\n') break;
1285 s++; l--;
1286 }
1287 }
1288 }
1289 }
1290 return;
1291 }
1292
decode_transfer_encoding(char * s,unsigned int l)1293 void decode_transfer_encoding(char *s,unsigned int l)
1294 {
1295 unsigned int r;
1296 mime_current->ctenc = CTENC_NONE;
1297 if (!l || (mime_current->mimetype & MIME_MULTI)) return;
1298 /* base64/QP ignored for multipart */
1299 r = CTENC_NONE;
1300 while (l && (*s == ' ' || *s == '\t')) { s++; l--; } /* skip LWSP */
1301 s[l-1] = 0;
1302 if (case_startb(s,l,"quoted-printable")) {
1303 r = CTENC_QP;
1304 } else if (case_startb(s,l,"base64")) {
1305 r = CTENC_BASE64;
1306 }
1307 mime_current->ctenc = r;
1308 return;
1309 }
1310
check_boundary()1311 int check_boundary()
1312 /* return 0 if no boundary, 1 if start, 2 if end */
1313 {
1314 mime_info *tmp;
1315
1316 if (*line.s != '-' || line.s[1] != '-') return 0;
1317 tmp = mime_current;
1318 while (tmp) {
1319 if (tmp->boundary.len) {
1320 if (line.len > tmp->boundary.len + 2 &&
1321 !case_diffb(line.s+2,tmp->boundary.len,tmp->boundary.s)) {
1322 if (line.s[tmp->boundary.len + 2] == '-' &&
1323 line.s[tmp->boundary.len + 3] == '-') { /* end */
1324 mime_current = tmp;
1325 clear_mime();
1326 return 2;
1327
1328 } else { /* start */
1329 mime_current = tmp;
1330 new_mime();
1331 return 1;
1332 }
1333 }
1334 }
1335 tmp = tmp->previous;
1336 }
1337 if (!stralloc_copys(&curcharset,charset)) die_nomem();
1338 /* suprtfluous since header done by now */
1339 cs = csbase;
1340 return 0;
1341 }
1342
start_message_page(struct msginfo * infop)1343 void start_message_page(struct msginfo *infop)
1344 /* header etc for message. Delayed to collect subject so that we can put */
1345 /* that in TITLE. This in turn needed for good looking robot index. */
1346 /* Yep, not pretty, but it works and it's abhorrent to seek()/rewind */
1347 /* and another hack: it's hard to mix charsets within a doc. So, we disp */
1348 /* messages entirely in the charset of the message. This is ok, since */
1349 /* headers will be us-ascii or have encoded segments usually matching */
1350 /* the charset in the message. Of course, we should be able to used e.g. */
1351 /* <div charset=iso-2022-jp> with internal resources as well as internal */
1352 /* ones. One might make other-charset messages external resources as well*/
1353 /* Now, the problem is that we need to "preview" MIME info _before_ */
1354 /* seeing the start boundary. */
1355 {
1356 if (!stralloc_copyb(&decline,strnum,fmt_ulong(strnum,infop->target)))
1357 die_nomem();
1358 if (!stralloc_cats(&decline,":")) die_nomem();
1359 if (!stralloc_0(&decline)) die_nomem();
1360 decodeHDR(hdr[HDR_SUBJECT - 1].s,hdr[HDR_SUBJECT - 1].len,&line);
1361 if (!mime_current)
1362 new_mime(); /* allocate */
1363 else
1364 clear_mime();
1365 decode_mime_type(hdr[HDR_CT - 1].s,hdr[HDR_CT - 1].len,
1366 hdr[HDR_VERSION - 1].len);
1367 html_header(decline.s,line.s,line.len - 1,
1368 "msgbody",SPC_BASE);
1369 decline.len = 0; /* reset */
1370 msglinks(infop);
1371 oputs("<div class=\"message\">\n");
1372 }
1373
show_part(struct msginfo * infop,int flagshowheaders,int flagstartseen)1374 void show_part(struct msginfo *infop,int flagshowheaders,int flagstartseen)
1375 /* if flagshowheaders we display headers, otherwise not */
1376 /* if flagstartseen we've already see the start boundary for this part, */
1377 /* if not we'll ignore what's there up to it */
1378 /* if flagskip we skip this part */
1379 {
1380 int flaginheader;
1381 int whatheader;
1382 int flaggoodfield;
1383 int flaghtml;
1384 int btype,i;
1385 unsigned int colpos;
1386
1387 flaginheader = 1;
1388 for (i = 0; i < NO_HDRS; i++) hdr[i].len = 0;
1389 flaggoodfield = 1;
1390 match = 1;
1391 recursion_level++; /* one up */
1392 for (flaghtml = whatheader = 0;;) {
1393 if (!match) return;
1394 if (getln(&ssin,&line,&match,'\n') == -1)
1395 strerr_die2sys(111,FATAL,MSG1(ERR_READ,fn.s));
1396 if (!match) return;
1397 if ((btype = check_boundary())) {
1398 if (decline.len) { /* flush last line that doesn't */
1399 if (flaghtml) /* end in \n for QP/base64 */
1400 oput(decline.s,decline.len);
1401 else
1402 anchor_put(decline.s,decline.len);
1403 decline.len = 0;
1404 }
1405 if (flagpre) { /* ending part was <pre> */
1406 oputs("</pre>");
1407 toggle_flagpre(0);
1408 }
1409 if (mime_current->level < recursion_level) {
1410 return;
1411 }
1412 if (btype == 1) {
1413 flagstartseen = 1;
1414 flaggoodfield = 1;
1415 flaginheader = 1;
1416 } else
1417 flagstartseen = 0;
1418 continue;
1419 }
1420 if (!flagstartseen) continue; /* skip to start */
1421 if (flaginheader) {
1422 if (line.len == 1) {
1423 if (flagshowheaders) { /* rfc822hdr only */
1424 if (flagtoplevel)
1425 start_message_page(infop); /* so we can put subj in TITLE */
1426 oputs("<div class=\"rfc822hdr\"><hr />\n");
1427 for (i = 0; i < NO_HDRS; i++) {
1428 if (!hdr[i].len || !headers_shown[i]) continue;
1429 if (i == HDR_SUBJECT - 1 && flagtoplevel)
1430 oputs("<span class=\"subject\">");
1431 oputs("<em>");
1432 oputs(constmap_get(&headermap,i + 1));
1433 oputs(":</em>");
1434 decodeHDR(hdr[i].s,hdr[i].len,&line);
1435 if (i == HDR_SUBJECT - 1 && flagtoplevel) {
1436 oputs("<a class=\"relk\" href=\"mailto:");
1437 oputs(local);
1438 oput("@",1);
1439 oputs(host);
1440 oputs("?subject=");
1441 urlencode_put(line.s + 1,line.len - 2);
1442 oputs("\">");
1443 }
1444 if (flagobscure && i == HDR_FROM - 1) {
1445 oputs(" ");
1446 author_name(&decline,line.s,line.len);
1447 html_put(decline.s,decline.len);
1448 } else {
1449 decodeHDR(hdr[i].s,hdr[i].len,&decline);
1450 html_put(decline.s,decline.len - 1);
1451 }
1452 if (i == HDR_SUBJECT - 1 && flagtoplevel)
1453 oputs("</a></span>");
1454 oputs("\n<br />");
1455 }
1456 oputs("</div>\n");
1457 }
1458 flaginheader = 0;
1459 flagtoplevel = 0;
1460 flaggoodfield = 1;
1461 flaghtml = 0;
1462 if (!flagmime)
1463 flagmime = hdr[HDR_VERSION - 1].len; /* MIME-Version header */
1464 decode_mime_type(hdr[HDR_CT - 1].s,hdr[HDR_CT - 1].len,flagmime);
1465 decode_transfer_encoding(hdr[HDR_CTENC - 1].s,hdr[HDR_CTENC - 1].len);
1466 content.len = 0; encoding.len = 0;
1467 switch (mime_current->mimetype) {
1468 case MIME_MULTI_SIGNED:
1469 case MIME_MULTI_MIXED:
1470 case MIME_MULTI_ALTERNATIVE:
1471 case MIME_MULTI_DIGEST:
1472 show_part(infop,0,0);
1473 recursion_level--;
1474 flagstartseen = 0;
1475 flaginheader = 1;
1476 continue;
1477 case MIME_MESSAGE_RFC822:
1478 oputs("\n<pre>");
1479 toggle_flagpre(1);
1480 flagshowheaders = 1;
1481 flaginheader = 1;
1482 flagmime = 0; /* need new MIME-Version header */
1483 continue;
1484 case MIME_TEXT_HTML:
1485 if (flagshowhtml) {
1486 oputs("<hr />\n");
1487 flaghtml = 1;
1488 } else {
1489 oputs("<strong>[\"");
1490 oput(mime_current->ctype.s,mime_current->ctype.len);
1491 oputs("\" not shown]</strong>\n");
1492 flaggoodfield = 0; /* hide */
1493 }
1494 continue;
1495 case MIME_TEXT_PLAIN:
1496 case MIME_TEXT: /* in honor of Phil using "text" on */
1497 case MIME_NONE: /* the qmail list and rfc2045:5.2 */
1498 oputs("<hr />\n<pre>\n");
1499 toggle_flagpre(1);
1500 continue;
1501 case MIME_TEXT_VCARD:
1502 default: /* application/octetstream...*/
1503 oputs("<hr /><strong>[\"");
1504 oput(mime_current->ctype.s,mime_current->ctype.len);
1505 oputs("\" not shown]</strong>\n");
1506 flaggoodfield = 0; /* hide */
1507 continue;
1508 }
1509 } else if (line.s[0] != ' ' && line.s[0] != '\t') {
1510 flaggoodfield = 0;
1511 colpos = byte_chr(line.s,line.len,':');
1512 if ((whatheader = constmap_index(&headermap,line.s,colpos))) {
1513 flaggoodfield = 1;
1514 if (!stralloc_copyb(&hdr[whatheader - 1],line.s + colpos + 1,
1515 line.len - colpos - 1)) die_nomem();
1516 }
1517 } else {
1518 if (whatheader)
1519 if (!stralloc_catb(&hdr[whatheader - 1],line.s,line.len))
1520 die_nomem();
1521 }
1522 } else {
1523 if (flaggoodfield) {
1524 if (mime_current->ctenc) {
1525 if (mime_current->ctenc == CTENC_QP)
1526 decodeQ(line.s,line.len,&decline);
1527 else
1528 decodeB(line.s,line.len,&decline);
1529 if (decline.s[decline.len - 1] == '\n') { /* complete line */
1530 if (!stralloc_copy(&line,&decline)) die_nomem();
1531 decline.len = 0;
1532 } else /* incomplete - wait for next */
1533 line.len = 0; /* in case URL is split */
1534 }
1535 if (flaghtml)
1536 oput(line.s,line.len);
1537 else {
1538 anchor_put(line.s,line.len); /* body */
1539 }
1540 }
1541 }
1542 }
1543 }
1544
show_message(struct msginfo * infop)1545 int show_message(struct msginfo *infop)
1546 {
1547 char *psz;
1548
1549 if(!stralloc_copys(&headers,(char *) headers_used)) die_nomem();
1550 if (!stralloc_0(&headers)) die_nomem();
1551 psz = headers.s;
1552 while (*psz) {
1553 if (*psz == '\\') *psz = '\0';
1554 ++psz;
1555 }
1556 if (!constmap_init(&headermap,headers.s,headers.len,0))
1557 die_nomem();
1558
1559 (void) makefn(&fn,ITEM_MESSAGE,msginfo.target,"");
1560 if ((fd = open_read(fn.s)) == -1) {
1561 if (errno == error_noent)
1562 return 0;
1563 else
1564 strerr_die2sys(111,FATAL,MSG1(ERR_OPEN,fn.s));
1565 }
1566 substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
1567 toggle_flagpre(0);
1568 recursion_level = 0; /* recursion level for show_part */
1569 flagmime = 0; /* no active mime */
1570 flagtoplevel = 1; /* top message/rfc822 get special rx */
1571 new_mime(); /* initiate a MIME info storage slot */
1572
1573 show_part(infop,1,1); /* do real work, including html header etc */
1574 if (flagpre)
1575 oputs("</pre>\n");
1576 close(fd);
1577 oputs("<hr /></div>\n");
1578 msglinks(infop);
1579 html_footer(0);
1580 return 1;
1581 }
1582
decode_item(char ch)1583 int decode_item(char ch)
1584 {
1585 switch (ch) {
1586 case 'm': return ITEM_MESSAGE;
1587 case 'a': return ITEM_AUTHOR ;
1588 case 's': return ITEM_SUBJECT;
1589 case 'd': return ITEM_DATE ;
1590 case 'i': return ITEM_INDEX ;
1591 default: cgierr("Navigation command contains ",
1592 "illegal item code","");
1593 }
1594 /* never reached */
1595 }
1596
decode_direction(char ch)1597 int decode_direction(char ch)
1598 {
1599 switch (ch) {
1600 case 's': return DIRECT_SAME;
1601 case 'n': return DIRECT_NEXT;
1602 case 'p': return DIRECT_PREV;
1603 default: cgierr("Navigation command contains ",
1604 "illegal direction code","");
1605 }
1606 /* never reached */
1607 }
1608
decode_cmd(char * s,struct msginfo * infop)1609 int decode_cmd(char *s,struct msginfo *infop)
1610 /* decodes s into infop. Assures that no security problems slip through by */
1611 /* checking everything */
1612 /* commands xyd:123[:abc]. x what we want, y is the axis, d the direction. */
1613 /* 123 is the current message number. abc is a date/subject/author hash, */
1614 /* depending on axis, or empty if not available. */
1615 /* returns: 0 no command+msgnum. */
1616 /* 1 empty or at least cmd + msgnum. */
1617 /* Guarantee: Only legal values accepted */
1618 {
1619 char ch;
1620
1621 infop->source = 0L;
1622 infop->date = 0L;
1623 infop->author = (char *)0;
1624 infop->subject = (char *)0;
1625 infop->cgiarg = (char *)0;
1626
1627 if (!s || !*s) { /* main index */
1628 infop->item = ITEM_DATE;
1629 infop->axis = ITEM_DATE;
1630 infop->direction = DIRECT_SAME;
1631 latestdate(&msginfo,0);
1632 infop->target = MAXULONG;
1633 return 1;
1634 }
1635 ch = *(s++);
1636 if (ch >= '0' && ch <= '9') { /* numeric - simplified cmd: msgnum ... */
1637 s--;
1638 infop->item = ITEM_MESSAGE;
1639 infop->axis = ITEM_MESSAGE;
1640 infop->direction = DIRECT_SAME;
1641 } else { /* what:axis:direction:msgnum ... */
1642 infop->item = decode_item(ch);
1643 ch = *(s++);
1644 infop->axis = decode_item(ch);
1645 ch = *(s++);
1646 infop->direction = decode_direction(ch);
1647 if (*(s++) != ':') return 0;
1648 }
1649 s+= scan_ulong(s,&(infop->source));
1650 if (*(s++) != ':') return 0;
1651 if (*s >= '0' && *s <= '9') { /* numeric nav hint [date] */
1652 s+= scan_ulong(s,&(infop->date));
1653 if (!*s++) return 1; /* skip any char - should be ':' unless NUL */
1654 }
1655 if (checkhash(s)) { /* Ignore if illegal rather than complaining*/
1656 if (!stralloc_copyb(&charg,s,HASHLEN)) die_nomem();
1657 if (!stralloc_0(&charg)) die_nomem();
1658 infop->cgiarg = charg.s;
1659 }
1660 return 1;
1661 }
1662
msg2hash(struct msginfo * infop)1663 int msg2hash(struct msginfo *infop)
1664 {
1665 unsigned int pos;
1666 unsigned long tmpmsg;
1667
1668 if (!infop->source) die_prog("source is 0 in msg2hash");
1669 (void) makefn(&fn,ITEM_INDEX,infop->source,"");
1670 if ((fd = open_read(fn.s)) == -1) {
1671 if (errno == error_noent)
1672 return 0;
1673 else
1674 strerr_die2sys(111,FATAL,MSG1(ERR_OPEN,fn.s));
1675 }
1676 substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
1677 for (;;) {
1678 if (getln(&ssin,&line,&match,'\n') == -1)
1679 strerr_die2sys(111,FATAL,MSG1(ERR_READ,"index"));
1680 if (!match)
1681 break; /* didn't find message */
1682 if (*line.s == '\t') continue; /* author line */
1683 pos = scan_ulong(line.s,&tmpmsg);
1684 if (tmpmsg == infop->source) {
1685 if (line.s[pos++] != ':' || line.s[pos++] != ' ')
1686 strerr_die1x(100,MSG1(ERR_SYNTAX_MISSING_SEPARATOR,fn.s));
1687 if (line.len < HASHLEN + pos)
1688 strerr_die1x(100,MSG1(ERR_SYNTAX_MISSING_SUBJHASH,fn.s));
1689 if (!stralloc_copyb(&subject,line.s+pos,HASHLEN)) die_nomem();
1690 if (!stralloc_0(&subject)) die_nomem();
1691 infop->subject = subject.s;
1692 if (getln(&ssin,&line,&match,'\n') == -1)
1693 strerr_die2sys(111,FATAL,MSG1(ERR_READ,"index"));
1694 if (!match)
1695 strerr_die1x(100,MSG1(ERR_SYNTAX_MISSING_AUTHOR,fn.s));
1696 pos = byte_chr(line.s,line.len,';');
1697 if (pos == line.len)
1698 strerr_die1x(100,MSG1(ERR_SYNTAX_MISSING_SEMIDATE,fn.s));
1699 if (pos > 1)
1700 infop->date = date2yyyymm(line.s+1); /* ';' marks end ok */
1701 pos++;
1702 if (line.len < HASHLEN + pos)
1703 strerr_die1x(100,MSG1(ERR_SYNTAX_MISSING_AUTHHASH,fn.s));
1704 if (!stralloc_copyb(&author,line.s+pos,HASHLEN)) die_nomem();
1705 if (!stralloc_0(&author)) die_nomem();
1706 infop->author = author.s;
1707 close(fd);
1708 return 1; /* success */
1709 }
1710 }
1711 close(fd);
1712 return 0; /* failed to match */
1713 }
1714
setmsg(struct msginfo * infop)1715 void setmsg(struct msginfo *infop)
1716 /* Reads the file corresponding to infop->axis and assumes fn.s is set */
1717 /* correctly for this. Sets up a msgnav structure and links it in */
1718 /* correction for axis=author/subject. For axis=date it supports also */
1719 /* direction=DIRECT_FIRST which will return the first message of the */
1720 /* first thread in the date file. DIRECT_LAST is not supported. */
1721 /* DIRECT_FIRST is supported ONLY for date. */
1722 {
1723 unsigned int pos;
1724 if (infop->direction == DIRECT_SAME) {
1725 infop->target = infop->source;
1726 return;
1727 }
1728 if ((fd = open_read(fn.s)) == -1) {
1729 if (errno == error_noent)
1730 strerr_die2x(100,FATAL,MSG1(ERR_OPEN_LISTMSGS,fn.s));
1731 else
1732 strerr_die2sys(111,FATAL,MSG1(ERR_OPEN,fn.s));
1733 }
1734 substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
1735 if (infop->axis != ITEM_DATE) {
1736 if (getln(&ssin,&line,&match,'\n') == -1) /* first line */
1737 strerr_die2sys(111,FATAL,MSG1(ERR_READ,fn.s));
1738 if (!match)
1739 strerr_die1x(100,MSG1(ERR_SYNTAX_MISSING_FIRST,fn.s));
1740 }
1741 msgnav[3] = 0L; /* next */
1742 msgnav[4] = 0L; /* after */
1743 infop->target = 0L;
1744 for (;;) {
1745 if (getln(&ssin,&line,&match,'\n') == -1)
1746 strerr_die2sys(111,FATAL,MSG1(ERR_READ,fn.s));
1747 if (!match) break;
1748 msgnav[0] = msgnav[1];
1749 msgnav[1] = msgnav[2];
1750 pos = scan_ulong(line.s,&(msgnav[2]));
1751 if (infop->direction == DIRECT_FIRST && infop->axis == ITEM_DATE) {
1752 if (pos + HASHLEN + 1 < line.len)
1753 if (!stralloc_copyb(&subject,line.s+pos+1,HASHLEN)) die_nomem();
1754 if (!stralloc_0(&subject)) die_nomem();
1755 break;
1756 }
1757 if (msgnav[2] == infop->source) {
1758 if (getln(&ssin,&line,&match,'\n') == -1)
1759 strerr_die2sys(111,FATAL,MSG1(ERR_READ,fn.s));
1760 if (!match) break;
1761 (void) scan_ulong(line.s,&(msgnav[3]));
1762 if (getln(&ssin,&line,&match,'\n') == -1)
1763 strerr_die2sys(111,FATAL,MSG1(ERR_READ,fn.s));
1764 if (!match) break;
1765 (void) scan_ulong(line.s,&(msgnav[4]));
1766 break;
1767 }
1768 }
1769 close(fd);
1770 switch (infop->axis) {
1771 case ITEM_AUTHOR:
1772 infop->authnav = msgnav + 2 + infop->direction;
1773 infop->target = *(infop->authnav);
1774 infop->subject = (char *)0; /* what we know is not for this msg */
1775 infop->date = 0;
1776 break;
1777 case ITEM_SUBJECT:
1778 if (infop->direction == DIRECT_FIRST)
1779 infop->target = msgnav[2];
1780 else {
1781 infop->subjnav = msgnav + 2 + infop->direction;
1782 infop->target = *(infop->subjnav);
1783 }
1784 infop->author = (char *)0; /* what we know is not for this msg */
1785 infop->date = 0;
1786 break;
1787 case ITEM_DATE:
1788 infop->target = msgnav[2];
1789 infop->subject = (char *)0; /* what we know is not for this msg */
1790 infop->author = (char *)0; /* what we know is not for this msg */
1791 break;
1792 default:
1793 die_prog("Bad item in setmsg");
1794 }
1795 return;
1796 }
1797
auth2msg(struct msginfo * infop)1798 void auth2msg(struct msginfo *infop)
1799 {
1800 if (!infop->author) die_prog("no such author in authmsg");
1801 if (!makefn(&fn,ITEM_AUTHOR,0L,infop->author)) die_prog("auth2msg");
1802 setmsg(infop);
1803 }
1804
subj2msg(struct msginfo * infop)1805 void subj2msg(struct msginfo *infop)
1806 {
1807 if (!infop->subject) die_prog("no such subject in subj2msg");
1808 if (!makefn(&fn,ITEM_SUBJECT,0L,infop->subject)) die_prog("subj2msg");
1809 setmsg(infop);
1810 }
1811
date2msg(struct msginfo * infop)1812 void date2msg(struct msginfo *infop)
1813 /* this is all a terrible hack */
1814 {
1815 (void) makefn(&fn,ITEM_DATE,infop->date,"");
1816 infop->direction = DIRECT_FIRST;
1817 infop->axis = ITEM_DATE;
1818 setmsg(infop); /* got first thread */
1819 infop->subject = subject.s;
1820 infop->axis = ITEM_SUBJECT;
1821 subj2msg(infop); /* get 1st message no in that thread */
1822 }
1823
findlastmsg(struct msginfo * infop)1824 void findlastmsg(struct msginfo *infop)
1825 {
1826 if (!getconf_ulong(&(infop->target),"num",0))
1827 cgierr("Sorry, there are no messages in the archive","","");
1828 }
1829
do_cmd(struct msginfo * infop)1830 int do_cmd(struct msginfo *infop)
1831 /* interprets msginfo to create msginfo. Upon return, msginfo can be trusted */
1832 /* to have all info needed, and that all info is correct. There may be more */
1833 /* info than needed. This can be used to build more specific links. NOTE: */
1834 /* there is no guarantee that a message meeting the criteria actually exists*/
1835 {
1836 infop->target = infop->source;
1837
1838 switch (infop->item) {
1839 case ITEM_MESSAGE: /* we want to get a message back */
1840 {
1841 switch (infop->axis) {
1842 case ITEM_MESSAGE:
1843 if (infop->direction == DIRECT_SAME)
1844 break;
1845 else if (infop->direction == DIRECT_NEXT)
1846 (infop->target)++;
1847 else { /* previous */
1848 cache = 2;
1849 if (infop->target >= 2)
1850 (infop->target)--;
1851 else
1852 infop->target = 1;
1853 }
1854 break;
1855 case ITEM_AUTHOR:
1856 infop->author = infop->cgiarg;
1857 if (!infop->author) /* we don't know author hash */
1858 if (!msg2hash(infop)) return 0;
1859 auth2msg(infop);
1860 break;
1861 case ITEM_SUBJECT:
1862 infop->subject = infop->cgiarg;
1863 if (!infop->subject) /* we don't know Subject hash */
1864 if (!msg2hash(infop)) return 0;
1865 subj2msg(infop);
1866 break;
1867 }
1868 break;
1869 }
1870 case ITEM_AUTHOR:
1871 switch (infop->axis) {
1872 case ITEM_MESSAGE:
1873 if (!infop->author)
1874 if (!msg2hash(infop)) return 0;
1875 break;
1876 case ITEM_AUTHOR:
1877 infop->author = infop->cgiarg;
1878 if (!infop->author)
1879 if (!msg2hash(infop)) return 0;
1880 auth2msg(infop);
1881 break;
1882 case ITEM_SUBJECT:
1883 infop->subject = infop->cgiarg;
1884 if (!infop->subject) /* we don't know Subject hash */
1885 if (!msg2hash(infop)) return 0;
1886 subj2msg(infop);
1887 break;
1888 }
1889 break;
1890 case ITEM_SUBJECT:
1891 switch (infop->axis) {
1892 case ITEM_MESSAGE:
1893 if (!msg2hash(infop)) return 0;
1894 break;
1895 case ITEM_AUTHOR:
1896 infop->author = infop->cgiarg;
1897 if (!infop->author)
1898 if (!msg2hash(infop)) return 0;
1899 auth2msg(infop);
1900 break;
1901 case ITEM_SUBJECT:
1902 infop->subject = infop->cgiarg;
1903 if (!infop->subject) /* we don't know Subject hash */
1904 if (!msg2hash(infop)) return 0;
1905 subj2msg(infop);
1906 break;
1907 }
1908 break;
1909 case ITEM_DATE: /* want a date reference */
1910 switch (infop->axis) {
1911 case ITEM_MESSAGE:
1912 case ITEM_AUTHOR:
1913 case ITEM_SUBJECT:
1914 case ITEM_DATE:
1915 if (!infop->date && infop->source)
1916 if (!msg2hash(infop)) return 0;
1917 gtdate(infop,0);
1918 break;
1919 }
1920 break;
1921 case ITEM_INDEX: /* ignore direction etc - only for index */
1922 if (!infop->target)
1923 infop->target = infop->source;
1924 break;
1925 }
1926 return 1;
1927 }
1928
list_lists()1929 void list_lists()
1930 {
1931 unsigned long lno;
1932 cache = 2;
1933 flagrobot = 2;
1934 html_header("Robot index of lists",0,0,0,0);
1935 for (;;) {
1936 if (getln(&ssin,&cfline,&match,'\n') == -1) /* read line */
1937 strerr_die2sys(111,FATAL,MSG1(ERR_READ,fn.s));
1938 if (!match)
1939 break;
1940 if (cfline.s[0] == '#') continue; /* skip comment */
1941 cfline.s[cfline.len - 1] = '\0'; /* so all are sz */
1942 (void) scan_ulong(cfline.s,&lno); /* listno for line */
1943 if (lno) { /* don't expose default list */
1944 oputs("<a href=\"");
1945 oput(strnum,fmt_ulong(strnum,lno));
1946 oputs("/index\">[link]</a>\n");
1947 }
1948 }
1949 html_footer(0);
1950 }
1951
list_list(unsigned long listno)1952 void list_list(unsigned long listno)
1953 /* Make one link [for list_set()] per set of 100 archive messages. */
1954 /* Assumption: Any directory DIR/archive/xxx where 'xxx' is a numeric,*/
1955 /* is part of the list archive and has in it an index file and one */
1956 /* or more messages. */
1957 {
1958 DIR *archivedir;
1959 direntry *d;
1960 unsigned long msgset;
1961
1962 flagrobot = 2;
1963 strnum[fmt_ulong(strnum,listno)] = '\0';
1964 archivedir = opendir("archive/");
1965 if (!archivedir)
1966 strerr_die2sys((errno == error_noent) ? 100 : 111,
1967 FATAL,MSG1(ERR_OPEN,"archive"));
1968 cache = 1;
1969 html_header("Robot index for message sets in list",0,0,0,0);
1970
1971 while ((d = readdir(archivedir))) {
1972 if (d->d_name[scan_ulong(d->d_name,&msgset)])
1973 continue; /* not numeric */
1974 oputs("<a href=\"../"); /* from /ezcgi/0/index to /ezcgi/listno/index*/
1975 oputs(strnum);
1976 oputs("/index/");
1977 oputs(d->d_name);
1978 oputs("\">[link]</a>\n");
1979 }
1980 closedir(archivedir);
1981 html_footer(0);
1982 }
1983
list_set(unsigned long msgset)1984 void list_set(unsigned long msgset)
1985 {
1986 unsigned int msgfirst,msgmax;
1987 unsigned long lastset;
1988
1989 flagrobot = 2;
1990 findlastmsg(&msginfo);
1991 if (!stralloc_copys(&line,"<a href=\"../")) die_nomem();
1992 if (!stralloc_catb(&line,strnum,fmt_ulong(strnum,msgset))) die_nomem();
1993 lastset = msginfo.target / 100;
1994 cache = 2;
1995 msgfirst = 0;
1996 if (!msgset)
1997 msgfirst = 1;
1998 msgmax = 99;
1999 if (msgset > lastset) { /* assure empty list */
2000 msgmax = 0;
2001 msgfirst = 1;
2002 } else if (msgset == lastset) {
2003 cache = 0; /* still changing */
2004 msgmax = msginfo.target % 100;
2005 }
2006 html_header("Robot index for messages in set",0,0,0,0);
2007 while (msgfirst <= msgmax) {
2008 oput(line.s,line.len);
2009 oput(strnum,fmt_uint0(strnum,msgfirst,2));
2010 oputs("\">[link]</a>\n");
2011 msgfirst++;
2012 }
2013 html_footer(0);
2014 }
2015
2016 /**************** MAY BE SUID ROOT HERE ****************************/
drop_priv(int flagchroot)2017 void drop_priv(int flagchroot)
2018 {
2019 if (!uid) strerr_die2x(100,FATAL,MSG(ERR_SUID)); /* not as root */
2020 if (!euid) {
2021 if (flagchroot)
2022 if (chroot(dir) == -1) /* chroot listdir */
2023 strerr_die2sys(111,FATAL,MSG1(ERR_CHROOT,dir));
2024 if (setuid(uid) == -1) /* setuid */
2025 strerr_die2sys(111,FATAL,MSG(ERR_SETUID));
2026 }
2027 euid = (unsigned long) geteuid();
2028 if (!euid) strerr_die2x(100,FATAL,MSG(ERR_SUID)); /* setuid didn't do it*/
2029 }
2030 /*******************************************************************/
2031
main(int argc,char ** argv)2032 int main(int argc,char **argv)
2033 {
2034 char *cp,*cppath;
2035 unsigned long listno,thislistno,tmpuid,msgset;
2036 unsigned long msgnum = 0;
2037 unsigned long port = 0L;
2038 unsigned long tmptarget;
2039 unsigned int pos,l;
2040 int flagindex = 0;
2041 int flagchroot = 1; /* chroot listdir if SUID root */
2042 int ret;
2043 char sep;
2044
2045 /******************** we may be SUID ROOT ******************************/
2046 uid = (unsigned long) getuid(); /* should be http */
2047 euid = (unsigned long) geteuid(); /* chroot only if 0 */
2048
2049 if (!euid) {
2050 if (!stralloc_copys(&line,auto_etc())) die_nomem();
2051 if (!stralloc_cats(&line,EZ_CGIRC)) die_nomem();
2052 if (!stralloc_0(&line)) die_nomem();
2053 if ((fd = open_read(line.s)) == -1) /* open config */
2054 strerr_die2sys(111,FATAL,MSG1(ERR_OPEN,line.s));
2055 } else {
2056 if ((fd = open_read(EZ_CGIRC_LOC)) == -1) /* open local config */
2057 strerr_die2sys(111,FATAL,MSG1(ERR_OPEN,EZ_CGIRC_LOC));
2058 }
2059
2060 substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf)); /* set up buffer */
2061 /* ##### tainted info #####*/
2062
2063 cmd = env_get("QUERY_STRING"); /* get command */
2064 cppath = env_get("PATH_INFO"); /* get path_info */
2065
2066 if (!cmd && !cppath)
2067 cmd = argv[1];
2068
2069 if (!cppath || !*cppath) {
2070 if (cmd && *cmd) {
2071 cmd += scan_ulong(cmd,&thislistno);
2072 if (*cmd == ':') cmd++; /* allow ':' after ln*/
2073 } else
2074 thislistno = 0L;
2075 } else {
2076 if (*cppath == '/') cppath++;
2077 cppath += scan_ulong(cppath,&thislistno); /* this listno */
2078 if (!thislistno || *cppath++ == '/') {
2079 if (str_start(cppath,"index")) {
2080 cppath += 5;
2081 flagindex = 1;
2082 if (!thislistno) { /* list index */
2083 drop_priv(0); /* <---- dropping privs */
2084 list_lists();
2085 close(fd);
2086 _exit(0);
2087 }
2088 }
2089 } /* rest done per list */
2090 }
2091
2092 for (sep = pos = 0;;) {
2093 if (getln(&ssin,&cfline,&match,'\n') == -1) /* read line */
2094 strerr_die2sys(111,FATAL,MSG1(ERR_READ,fn.s));
2095 if (!match)
2096 break;
2097 if (*cfline.s == '#' || cfline.len == 1) continue; /* skip comment/blank */
2098 cfline.s[cfline.len - 1] = '\0'; /* so all are sz */
2099 pos = scan_ulong(cfline.s,&listno); /* listno for line */
2100 if (thislistno != listno) continue;
2101 sep = cfline.s[pos++];
2102 if (cfline.s[pos] == '-') { /* no chroot if -uid*/
2103 flagchroot = 0;
2104 pos++;
2105 }
2106 pos += scan_ulong(cfline.s+pos,&tmpuid); /* listno for line */
2107 if (tmpuid) uid = tmpuid; /* override default */
2108 if (!cfline.s[pos++] == sep)
2109 die_syntax("missing separator after user id");
2110 if (cfline.s[pos] != '/')
2111 die_syntax("dir"); /* absolute path */
2112 l = byte_chr(cfline.s + pos, cfline.len - pos,sep);
2113 if (l == cfline.len - pos) /* listno:path:...*/
2114 die_syntax("missing separator after path");
2115 dir = cfline.s + pos;
2116 pos += l;
2117 cfline.s[pos++] = '\0'; /* .../dir\0 */
2118 break; /* do rest after dropping priv */
2119 }
2120 close(fd); /* don't accept uid 0*/
2121 if (!dir) {
2122 drop_priv(0); /* don't trust cgierr. No dir, no chroot */
2123 cgierr("list ",MSG(ERR_NOEXIST),"");
2124 }
2125 wrap_chdir(dir);
2126 drop_priv(flagchroot);
2127
2128 /******************************* RELAX **********************************/
2129
2130 /********************* continue to process config line ******************/
2131
2132 flagrobot = 0;
2133 if (cfline.s[pos] == '-') {
2134 flagobscure = 1;
2135 pos++;
2136 }
2137 local = cfline.s + pos;
2138 l = byte_chr(cfline.s + pos, cfline.len - pos,sep); /* ... home */
2139 if (l < cfline.len - pos) { /* optional */
2140 pos += l;
2141 cfline.s[pos++] = '\0';
2142 home = cfline.s + pos;
2143 l = byte_chr(cfline.s + pos, cfline.len - pos,sep); /* ... charset */
2144 if (l < cfline.len - pos) { /* optional */
2145 pos += l;
2146 cfline.s[pos++] = '\0';
2147 charset = cfline.s + pos;
2148 l = byte_chr(cfline.s+pos,cfline.len - pos,sep); /* ... stylesheet */
2149 if (l < cfline.len - pos) { /* optional */
2150 pos += l;
2151 cfline.s[pos++] = '\0';
2152 stylesheet = cfline.s + pos;
2153 l = byte_chr(cfline.s+pos,cfline.len-pos,sep); /* ... bannerURL */
2154 if (l < cfline.len - pos) { /* optional */
2155 pos += l;
2156 cfline.s[pos++] = '\0';
2157 banner = cfline.s + pos;
2158 }
2159 }
2160 }
2161 }
2162 if (!charset || !*charset) /* rfc822 default */
2163 charset = EZ_CHARSET;
2164 if (!stralloc_copys(&curcharset,charset)) die_nomem();
2165 csbase = decode_charset(curcharset.s,curcharset.len);
2166 if (csbase == CS_BAD) csbase = CS_NONE;
2167 cs = csbase;
2168 pos = + str_rchr(local,'@');
2169 if (!local[pos])
2170 die_syntax("listaddress lacks '@'"); /* require host */
2171 local[pos++] = '\0';
2172 host = local + pos;
2173
2174 /********************* Accomodate robots and PATH_INFO ****************/
2175
2176 if (flagindex) {
2177 if (*(cppath++) == '/') { /* /2/index/123 */
2178 cppath += scan_ulong(cppath,&msgset);
2179 list_set(msgset);
2180 } else /* /2/index */
2181 list_list(thislistno);
2182 _exit(0);
2183 }
2184
2185 if (cppath && *cppath) { /* /2/msgnum */
2186 flagrobot = 1; /* allow index, but "nofollow" */
2187 scan_ulong(cppath,&msgnum);
2188 } /* dealt with normally */
2189
2190 /********************* Get info from server on BASE etc ****************/
2191
2192 if (!stralloc_copys(&url,"<a href=\"")) die_nomem();
2193 if (!stralloc_copys(&base,"<base href=\"http://")) die_nomem();
2194 cp = env_get("SERVER_PORT");
2195 if (cp) { /* port */
2196 (void) scan_ulong(cp,&port);
2197 if ((unsigned int) port == 443) { /* https: */
2198 if (!stralloc_copys(&base,"<base href=\"https://")) die_nomem();
2199 }
2200 }
2201 if ((cp = env_get("HTTP_HOST")) || (cp = env_get("SERVER_NAME"))) {
2202 if (!stralloc_cats(&base,cp)) die_nomem();
2203 if (port && (unsigned int) port != 80 && (unsigned int) port != 443) {
2204 if (!stralloc_cats(&base,":")) die_nomem();
2205 if (!stralloc_catb(&base,strnum,fmt_ulong(strnum,port))) die_nomem();
2206 }
2207 }
2208 if ((cp = env_get("SCRIPT_NAME")) != 0) {
2209 if (!stralloc_cats(&base,cp)) die_nomem();
2210 pos = str_rchr(cp,'/');
2211 if (cp[pos])
2212 if (!stralloc_cats(&url,cp + pos + 1)) die_nomem();
2213 }
2214 if (!stralloc_cats(&base,"\" />\n")) die_nomem();
2215 if (!stralloc_cats(&url,"?")) die_nomem();
2216 if (thislistno) {
2217 if (!stralloc_catb(&url,strnum,fmt_ulong(strnum,thislistno))) die_nomem();
2218 if (!stralloc_cats(&url,":")) die_nomem();
2219 }
2220
2221 cache = 1; /* don't know if we want to cache */
2222
2223 /****************************** Get command ****************************/
2224
2225 if (msgnum) { /* to support /listno/msgno */
2226 msginfo.target = msgnum;
2227 msginfo.item = ITEM_MESSAGE;
2228 cache = 2;
2229 } else {
2230 (void) decode_cmd(cmd,&msginfo);
2231 if (!do_cmd(&msginfo))
2232 cgierr("I'm sorry, Dave ... I can't do that, Dave ...","","");
2233 }
2234
2235 switch (msginfo.item) {
2236 case ITEM_MESSAGE:
2237 if (!(ret = show_message(&msginfo))) { /* assume next exists ... */
2238 cache = 0; /* border cond. - no cache */
2239 msginfo.target = msginfo.source; /* show same */
2240 msginfo.subjnav = 0;
2241 msginfo.authnav = 0;
2242 ret = show_message(&msginfo);
2243 }
2244 break;
2245 case ITEM_AUTHOR:
2246 if (!(ret = show_object(&msginfo,ITEM_AUTHOR)))
2247 cgierr ("I couldn't find the author for that message","","");
2248 break;
2249 case ITEM_SUBJECT:
2250 if (!(ret = show_object(&msginfo,ITEM_SUBJECT)))
2251 cgierr ("I couldn't find the subject for that message","","");
2252 break;
2253 case ITEM_DATE:
2254 if (!(ret = show_object(&msginfo,ITEM_DATE))) {
2255 finddate(&msginfo);
2256 ret = show_object(&msginfo,ITEM_DATE);
2257 }
2258 break;
2259 case ITEM_INDEX:
2260 ret = 1;
2261 if (show_index(&msginfo)) break;/* msgnumber valid */
2262 tmptarget = msginfo.target;
2263 findlastmsg(&msginfo);
2264 cache = 0; /* latest one - no cache */
2265 if (msginfo.target > tmptarget) {
2266 cache = 2; /* first one won't change */
2267 msginfo.target = 1; /* try */
2268 if (show_index(&msginfo)) break;
2269 msginfo.date = 0; /* first indexes missing */
2270 firstdate(&msginfo); /* instead get first msg of first */
2271 date2msg(&msginfo); /* thread. */
2272 if (show_index(&msginfo)) break;
2273 } else
2274 ret = show_index(&msginfo);
2275 break;
2276 default:
2277 strerr_die2x(100,FATAL,"bad item in main");
2278 }
2279 if (!ret) {
2280 findlastmsg(&msginfo); /* as last resort; last msgindex */
2281 cache = 0;
2282 ret = show_message(&msginfo);
2283 }
2284
2285 _exit(0);
2286 (void)argc;
2287 }
2288