1 /*
2 ** Copyright 2007-2009 Double Precision, Inc.  See COPYING for
3 ** distribution information.
4 **
5 ** $Id: webmlmd.C,v 1.19 2009/11/08 18:14:47 mrsam Exp $
6 */
7 
8 #include	"config.h"
9 #include	"cgi/cgi.h"
10 #include	"rfc822/rfc822.h"
11 #include	"rfc822/rfc2047.h"
12 #include	"numlib/numlib.h"
13 #include	"unicode/unicode.h"
14 #include	"datadir.h"
15 
16 #include	<stdio.h>
17 #include	<errno.h>
18 #include	<stdlib.h>
19 #if	HAVE_UNISTD_H
20 #include	<unistd.h>
21 #endif
22 #include	<string.h>
23 #include	<signal.h>
24 
25 #include	<sys/types.h>
26 #include	<sys/stat.h>
27 #include        <sys/socket.h>
28 #include        <sys/un.h>
29 #if	HAVE_LOCALE_H
30 #if	HAVE_SETLOCALE
31 #include	<locale.h>
32 #endif
33 #endif
34 
35 #include	<list>
36 #include	<vector>
37 #include	<string>
38 #include	<algorithm>
39 #include	<iostream>
40 #include	<fstream>
41 #include	<sstream>
42 #include	<map>
43 
44 #include	"cmlm.h"
45 #include	"cmlmcmdmisc.h"
46 #include	"webmlmd.H"
47 #include	"webmlmdcmlm.H"
48 #include	"webmlmddirs.H"
49 #include	"webmlmdhandlers.H"
50 
51 #define TEMPLATEDIR DATADIR "/couriermlm"
52 
53 extern "C" {
54 
fake_exit(int rc)55 	void fake_exit(int rc)
56 	{
57 		exit(rc);
58 	}
59 
rfc2045_enomem(const char * errmsg)60 	void rfc2045_enomem(const char *errmsg)
61 	{
62 		cginocache();
63 		std::cout << "Content-Type: text/plain" << std::endl
64 			  << std::endl
65 			  << errmsg
66 			  << std::endl << std::flush;
67 		fake_exit(1);
68 	}
69 
error(const char * errmsg)70 	void error(const char *errmsg)
71 	{
72 		cginocache();
73 		printf("Content-Type: text/html; charset=utf-8\n\n"
74                        "<html><head><title>%s</title></head><body><h1>%s</h1></body></html>\n",
75                        errmsg, errmsg);
76 		fake_exit(1);
77 	}
78 }
79 
80 webmlmd::dirs mlm_dirs;
81 
toutf8str(std::wstring w)82 std::string toutf8str(std::wstring w)
83 {
84 	std::vector<unicode_char> u;
85 
86 	u.reserve(w.size()+1);
87 
88 	u.insert(u.end(), w.begin(), w.end());
89 
90 	u.push_back(0);
91 
92 	char *p=unicode_toutf8(&u[0]);
93 
94 	std::string s;
95 
96 	if (p)
97 	{
98 		s=p;
99 		free(p);
100 	}
101 	return s;
102 }
103 
104 // First component of PATH_INFO is the mailing list name
105 
list_name()106 std::string webmlmd::list_name()
107 {
108 	const char *pi=getenv("PATH_INFO");
109 
110 	if (!pi)
111 		return "";
112 
113 	if (*pi == '/')
114 		++pi;
115 
116 	std::string list_name(pi);
117 
118 	std::string::iterator b=list_name.begin(),
119 		p=std::find(b, list_name.end(), '/');
120 
121 	return std::string(b, p);
122 }
123 
124 // The remaining component in PATH_INFO is the list command
125 
list_cmd()126 static std::string list_cmd()
127 {
128 	const char *pi=cgiextrapath();
129 
130 	if (*pi == '/')
131 		++pi;
132 
133 	std::string list_name(pi);
134 
135 	std::string::iterator b=list_name.begin(), e=list_name.end(),
136 		p=std::find(b, e, '/');
137 
138 	if (p != e)
139 		++p;
140 
141 	return std::string(p, e);
142 }
143 
144 static void main2(void *);
145 
init_dirs(void * dummy)146 static void init_dirs(void *dummy)
147 {
148 	const char *dirs=getenv("LISTS");
149 
150 	if (!mlm_dirs.initialize(dirs ? dirs:""))
151 	{
152 		exit(1);
153 	}
154 }
155 
main(int argc,char ** argv)156 int main(int argc, char **argv)
157 {
158 	const char *cmd=NULL;
159 	const char *port_s=getenv("PORT");
160 	const char *prefork_s=getenv("PREFORK");
161 
162 	if (argc > 1)
163 		cmd=argv[1];
164 
165 #if HAVE_SETLOCALE
166 	setlocale(LC_ALL, "C");
167 #endif
168 
169 	/*
170 	** For convenience's sake, we need to start webmlmd by a helper script.
171 	** Rather than confusing folks with webmlmd.rc, automatically invoke
172 	** the helper script, when needed.
173 	*/
174 
175 	if (cmd && strcmp(cmd, "check"))
176 	{
177 		std::vector<char> shscript_v;
178 
179 		{
180 			std::string shscript=argv[0];
181 
182 			shscript += ".rc";
183 
184 			shscript_v.reserve(shscript.size()+1);
185 
186 			shscript_v.insert(shscript_v.end(),
187 					  shscript.begin(),
188 					  shscript.end());
189 			shscript_v.push_back(0);
190 		}
191 
192 		std::vector<char *> new_argv;
193 
194 		new_argv.push_back(&shscript_v[0]);
195 
196 		new_argv.insert(new_argv.end(), argv, argv+argc);
197 		new_argv.push_back(0);
198 
199 		execvp(new_argv[0], &new_argv[0]);
200 		perror(new_argv[0]);
201 		exit(1);
202 	}
203 
204 	if (!port_s || !*port_s)
205 	{
206 		std::cerr << "PORT not set in the configuration file"
207 			  << std::endl;
208 		exit(1);
209 	}
210 
211 	if (argc > 1 && strcmp(argv[1], "check") == 0)
212 	{
213 		if (access(port_s, 0) == 0)
214 		{
215 			if (access(port_s, W_OK) < 0)
216 			{
217 				perror(port_s);
218 				exit(1);
219 			}
220 		}
221 		else
222 		{
223 			int fd=socket(PF_UNIX, SOCK_STREAM, 0);
224 
225 			if (fd >= 0)
226 			{
227 				struct sockaddr_un skun;
228 
229 				memset(&skun, 0, sizeof(skun));
230 
231 				if (strlen(port_s) >= sizeof(skun.sun_path))
232 				{
233 					fprintf(stderr, "%s: pathname too long\n",
234 						port_s);
235 					exit(1);
236 				}
237 
238 				skun.sun_family=AF_UNIX;
239 
240 				strcpy(skun.sun_path, port_s);
241 
242 				if (bind(fd, (const struct sockaddr *)&skun,
243 					 sizeof(skun)) ||
244 				    listen(fd, 5) < 0)
245 				{
246 					close(fd);
247 					fd= -1;
248 				}
249 			}
250 
251 			if (fd < 0)
252 			{
253 				perror(port_s);
254 				exit(1);
255 			}
256 			close(fd);
257 		}
258 	}
259 
260 	if (cmd)
261 	{
262 		init_dirs(NULL);
263 		exit(0); /* The "check" command */
264 	}
265 
266 	cgi_daemon(atoi(prefork_s), port_s, init_dirs, main2, NULL);
267 
268 	return (0);
269 }
270 
getoption(std::string listdir,std::string option)271 std::string getoption(std::string listdir,
272 		      std::string option)
273 {
274 	std::string n=listdir + "/options";
275 
276 	std::ifstream i(n.c_str());
277 
278 	std::string s;
279 
280 	if (!i.is_open())
281 		return s;
282 
283 	while (std::getline(i, s).good())
284 	{
285 		std::string::iterator b=s.begin(), e=s.end(),
286 			p=std::find(b, e, '=');
287 
288 		if (std::string(b, p) == option)
289 		{
290 			if (p != e)
291 				++p;
292 
293 			return std::string(p, e);
294 		}
295 	}
296 	return "";
297 }
298 
getoption(std::string option)299 std::string getoption(std::string option)
300 {
301 	return getoption(".", option);
302 }
303 
getwoption(std::string dir,std::string option)304 std::wstring getwoption(std::string dir, std::string option)
305 {
306 	std::string s=getoption(dir, option);
307 
308 	unicode_char *u=unicode_fromutf8(s.c_str());
309 
310 	if (!u)
311 		return std::wstring();
312 
313 	size_t ul=0;
314 
315 	for (ul=0; u[ul]; ++ul)
316 		;
317 
318 	std::wstring w(u, u+ul);
319 	free(u);
320 
321 	return w;
322 }
323 
getwoption(std::string option)324 std::wstring getwoption(std::string option)
325 {
326 	return getwoption(".", option);
327 }
328 
getlistname(std::string dir,std::string dirbasename)329 std::string getlistname(std::string dir, std::string dirbasename)
330 {
331 	std::wstring listname=getwoption(dir, "LISTNAME");
332 
333 	if (listname.size() == 0)
334 	{
335 		listname.insert(listname.end(),
336 				dirbasename.begin(),
337 				dirbasename.end());
338 	}
339 
340 	return toutf8str(webmlmd::html_escape(listname));
341 }
342 
getlistname(std::string dirbasename)343 std::string getlistname(std::string dirbasename)
344 {
345 	return getlistname(".", dirbasename);
346 }
347 
348 HANDLER("LISTS", emit_lists)
349 {
350 	std::vector<std::string>::iterator b=mlm_dirs.begin(),
351 		e=mlm_dirs.end();
352 
353 	std::cout << "<ul>";
354 
355 	while (b != e)
356 	{
357 		std::string n=webmlmd::basename(*b);
358 
359 		std::cout << "<li><a href=\"" << cgirelscriptptr() << "/"
360 			  << webmlmd::html_escape(n) << "\">"
361 			  << getlistname(*b, n)
362 			  << "</a></li>";
363 		++b;
364 	}
365 	std::cout << "</ul>";
366 }
367 
368 HANDLER("LISTNAME", emit_list_name)
369 {
370 	std::cout << getlistname(list_name());
371 }
372 
373 HANDLER("LISTDESCR", emit_list_descr)
374 {
375 	std::cout << getoption("LISTDESCR");
376 }
377 
378 HANDLER("LISTURL", emit_list_url)
379 {
380 	std::cout << cgirelscriptptr() << "/" << list_name();
381 }
382 
383 HANDLER("REPEATURL", emit_repeat_url)
384 {
385 	std::cout << cgirelscriptptr() << cgiextrapath();
386 }
387 
388 static void emit_select(const char *name,
389 			const char *values,
390 			const std::list<std::string> &options,
391 			std::string default_option,
392 			size_t list_size=0,
393 			bool allow_multiple=false)
394 {
395 	std::string options_str;
396 
397 	std::list<std::string>::const_iterator b=options.begin();
398 
399 	while (b != options.end())
400 	{
401 		options_str += *b++;
402 		options_str += "\n";
403 	}
404 
405 	char *buf=cgi_select(name, values, options_str.c_str(),
406 			     getoption("POST").c_str(), 0, 0);
407 
408 	if (buf)
409 	{
410 		std::cout << buf;
411 		free(buf);
412 	}
413 }
414 
415 
emit_checkbox(const char * name,const char * value,const char * opts)416 static void emit_checkbox(const char *name, const char *value,
417 			  const char *opts)
418 {
419 	char *buf=cgi_checkbox(name, value, opts);
420 
421 	if (buf)
422 	{
423 		std::cout << buf;
424 		free(buf);
425 	}
426 }
427 
emit_textarea(const char * name,std::string value,const char * wrap,int rows,int cols,const char * opts)428 static void emit_textarea(const char *name, std::string value,
429 			  const char *wrap,
430 			  int rows, int cols, const char *opts)
431 {
432 	std::vector<unicode_char> u;
433 
434 	u.reserve(value.size()+1);
435 
436 	u.insert(u.end(), value.begin(), value.end());
437 	u.push_back(0);
438 
439 	char *buf=cgi_textarea(name, rows, cols, &u[0], wrap, opts);
440 
441 	u.clear();
442 
443 	if (buf)
444 	{
445 		std::cout << buf;
446 		free(buf);
447 	}
448 }
449 
emit_input(const char * name,std::wstring value,int size,int maxlength,const char * opts)450 static void emit_input(const char *name, std::wstring value,
451 		       int size, int maxlength,
452 		       const char *opts)
453 {
454 	std::vector<unicode_char> unicode_buf;
455 
456 	unicode_buf.reserve(value.size()+1);
457 
458 	unicode_buf.insert(unicode_buf.end(), value.begin(), value.end());
459 	unicode_buf.push_back(0);
460 
461 	char *buf=cgi_input(name, &unicode_buf[0], size, maxlength, "");
462 	unicode_buf.clear();
463 
464 	if (buf)
465 	{
466 		std::cout << buf;
467 		free(buf);
468 	}
469 }
470 
471 HANDLER("ADMINUPDATE", do_admin_update)
472 {
473 	if (!*cgi("update"))
474 		return;
475 
476 	std::list<std::string> new_cmds;
477 
478 	static const char * const booleans[]={
479 		"!simpleconfirm",
480 		"casesensitive",
481 		"nobozos",
482 		"nodsn",
483 	};
484 
485 	size_t i;
486 
487 	for (i=0; i<sizeof(booleans)/sizeof(booleans[0]); i++)
488 	{
489 		const char *p=booleans[i];
490 
491 		const char *yes, *no;
492 
493 		if (*p =='!')
494 		{
495 			++p;
496 
497 			yes="0";
498 			no="1";
499 		}
500 		else
501 		{
502 			yes="1";
503 			no="0";
504 		}
505 
506 		std::string setting=p;
507 
508 		std::transform(setting.begin(), setting.end(),
509 			       setting.begin(), std::ptr_fun(toupper));
510 
511 		std::string varname("opt");
512 
513 		varname += p;
514 
515 		new_cmds.push_back(setting + "=" +
516 				   (*cgi(varname.c_str()) ? yes:no));
517 	}
518 
519 	new_cmds.push_back(*cgi("optsubscribe") ?
520 			   "SUBSCRIBE=mod":"subscribe=all");
521 
522 	static const char * const plains[]={
523 		"post",
524 		"postarchive",
525 		"reportaddr",
526 		"digest",
527 		"listname",
528 		"keyword"
529 	};
530 
531 	for (i=0; i<sizeof(plains)/sizeof(plains[0]); i++)
532 	{
533 
534 		std::string setting=plains[i];
535 
536 		std::transform(setting.begin(), setting.end(),
537 			       setting.begin(), std::ptr_fun(toupper));
538 
539 		std::string varname("opt");
540 
541 		varname += plains[i];
542 
543 		new_cmds.push_back(setting + "=" + cgi(varname.c_str()));
544 	}
545 
546 	static const struct {
547 		const char *name;
548 		unsigned minvalue;
549 		unsigned maxvalue;
550 	} numerical[]={
551 		{"startprobe", 3, 30},
552 		{"maxbounces", 10, 50},
553 		{"purgebounce", 5, 30},
554 		{"maxmodnotices", 5, 99},
555 		{"remoderate", 12, 48},
556 		{"maxfetchsize", 100, 9999},
557 		{"purgearchive", 2, 9999},
558 		{"purgecmd", 6, 720},
559 	};
560 
561 	for (i=0; i<sizeof(numerical)/sizeof(numerical[0]); i++)
562 	{
563 
564 		std::string setting=numerical[i].name;
565 
566 		std::transform(setting.begin(), setting.end(),
567 			       setting.begin(), std::ptr_fun(toupper));
568 
569 		std::string varname("opt");
570 
571 		varname += numerical[i].name;
572 
573 		std::ostringstream o;
574 
575 		o << setting << "=";
576 
577 		int nn=atoi(cgi(varname.c_str()));
578 
579 		if (nn > 0)
580 		{
581 			if ((unsigned)nn < numerical[i].minvalue)
582 				nn=(int)numerical[i].minvalue;
583 			if ((unsigned)nn > numerical[i].maxvalue)
584 				nn=(int)numerical[i].maxvalue;
585 
586 			o << nn;
587 		}
588 
589 		new_cmds.push_back(o.str());
590 	}
591 
592 	char *p=rfc2047_encode_str(cgi("optname"), "utf-8",
593 				   rfc2047_qp_allow_word);
594 
595 	new_cmds.push_back(std::string("NAME=") + (p ? p:""));
596 
597 	if (p)
598 		free(p);
599 
600 	std::vector<std::string> args_array;
601 
602 	args_array.reserve(new_cmds.size());
603 	args_array.insert(args_array.end(), new_cmds.begin(),
604 			  new_cmds.end());
605 
606 	cmdset(args_array, false);
607 
608 	{
609 		std::string key=cgi("optheaderadd");
610 
611 		key += "\n";
612 
613 		std::istringstream i(key);
614 
615 		std::ofstream ofs(HEADERADD ".new");
616 
617 		std::string s;
618 
619 		while (std::getline(i, s).good())
620 			if (s.size() > 0)
621 			{
622 				ofs << s << "\n";
623 			}
624 
625 		ofs.close();
626 		rename(HEADERADD ".new", HEADERADD);
627 	}
628 
629 	{
630 		std::string key=cgi("optheaderdel");
631 		key += "\n";
632 
633 		std::istringstream i(key);
634 
635 		std::ofstream ofs(HEADERDEL ".new");
636 
637 		std::string s;
638 
639 		while (std::getline(i, s).good())
640 			if (s.size() > 0)
641 			{
642 				ofs << s << "\n";
643 			}
644 
645 		ofs.close();
646 		rename(HEADERDEL ".new", HEADERDEL);
647 	}
648 
649 }
650 
651 HANDLER("OPTPOST", emit_optpost_setting)
652 {
653 	if (args.size() != 3)
654 		return;
655 
656 	emit_select("optpost",
657 		    "all\n"
658 		    "subscribers\n"
659 		    "mod\n", args, getoption("POST"));
660 }
661 
662 HANDLER("OPTSUBSCRIBE", emit_optsubscribe)
663 {
664 	emit_checkbox("optsubscribe", "X",
665 		      getoption("SUBSCRIBE") == "mod" ? "*":"");
666 }
667 
668 HANDLER("OPTSIMPLECONFIRM", emit_simpleconfirm)
669 {
670 	emit_checkbox("optsimpleconfirm", "X",
671 		      getoption("SIMPLECONFIRM") != "1" ? "*":"");
672 }
673 
674 HANDLER("OPTPURGECMD", emit_purgecmd)
675 {
676 	emit_input("optpurgecmd", getwoption("PURGECMD"), 8, 8, "");
677 }
678 
679 HANDLER("OPTKEYWORD", emit_keyword)
680 {
681 	emit_input("optkeyword", getwoption("KEYWORD"), 32, 255, "");
682 }
683 
684 HANDLER("OPTNAME", emit_name)
685 {
686 	std::string n=getoption("NAME");
687 
688 	char *p=rfc822_display_hdrvalue_tobuf("subject",
689 					      n.c_str(),
690 					      unicode_UTF8.chset,
691 					      NULL,
692 					      NULL);
693 	if (p)
694 	{
695 		unicode_char *u=unicode_fromutf8(p);
696 
697 		if (u)
698 		{
699 			size_t ul=0;
700 
701 			for (ul=0; u[ul]; ++ul)
702 				;
703 
704 			emit_input("optname", std::wstring(u, u+ul), 32, 255,
705 				   "");
706 			free(u);
707 		}
708 		free(p);
709 	}
710 }
711 
712 HANDLER("OPTLISTNAME", emit_listname)
713 {
714 	emit_input("optlistname", getwoption("LISTNAME"), 32, 255, "");
715 }
716 
717 HANDLER("OPTPOSTARCHIVE", emit_postarchive)
718 {
719 	if (args.size() != 2)
720 		return;
721 
722 	emit_select("optpostarchive",
723 		    "all\n"
724 		    "subscribers\n", args, getoption("POSTARCHIVE"));
725 }
726 
727 HANDLER("OPTCASESENSITIVE", emit_casesensitive)
728 {
729 	emit_checkbox("optcasesensitive", "X",
730 		      getoption("CASESENSITIVE") == "1" ? "*":"");
731 }
732 
733 HANDLER("OPTREPORTADDR", emit_reportaddr)
734 {
735 	emit_input("optreportaddr", getwoption("REPORTADDR"), 32, 255, "");
736 }
737 
738 HANDLER("OPTNOBOZOS", emit_nobozos)
739 {
740 	emit_checkbox("optnobozos", "X",
741 		      getoption("NOBOZOS") != "0" ? "*":"");
742 }
743 
744 HANDLER("OPTDIGEST", emit_digest)
745 {
746 	emit_input("optdigest", getwoption("DIGEST"), 32, 255, "");
747 }
748 
749 HANDLER("OPTMAXFETCHSIZE", emit_maxfetchsize)
750 {
751 	emit_input("optmaxfetchsize", getwoption("MAXFETCHSIZE"), 8, 8, "");
752 }
753 
754 HANDLER("OPTNODSN", emit_nodsn)
755 {
756 	emit_checkbox("optnodsn", "X",
757 		      getoption("NODSN") == "1" ? "*":"");
758 }
759 
760 HANDLER("OPTSTARTPROBE", emit_startprobe)
761 {
762 	emit_input("optstartprobe", getwoption("STARTPROBE"), 8, 8, "");
763 }
764 
765 HANDLER("OPTMAXBOUNCES", emit_maxbounces)
766 {
767 	emit_input("optmaxbounces", getwoption("MAXBOUNCES"), 8, 8, "");
768 }
769 
770 HANDLER("OPTPURGEBOUNCE", emit_purgebounce)
771 {
772 	emit_input("optpurgebounce", getwoption("PURGEBOUNCE"), 8, 8, "");
773 }
774 
775 HANDLER("OPTMAXMODNOTICES", emit_maxmodnotices)
776 {
777 	emit_input("optmaxmodnotices", getwoption("MAXMODNOTICES"), 8, 8, "");
778 }
779 
780 HANDLER("OPTREMODERATE", emit_remoderate)
781 {
782 	emit_input("optremoderate", getwoption("REMODERATE"), 8, 8, "");
783 }
784 
785 HANDLER("OPTPURGEARCHIVE", emit_purgearchive)
786 {
787 	emit_input("optpurgearchive", getwoption("PURGEARCHIVE"), 8, 8, "");
788 }
789 
790 HANDLER("OPTHEADERADD", emit_headeradd)
791 {
792 	std::ostringstream o;
793 
794 	std::ifstream ifs(HEADERADD);
795 
796 	if (ifs.is_open())
797 	{
798 		std::string line;
799 
800 		while (std::getline(ifs, line).good())
801 			if (line.size())
802 				o << line << std::endl;
803 	}
804 
805 	emit_textarea("optheaderadd", o.str(), "off", 4, 40, "");
806 }
807 
808 HANDLER("OPTHEADERDEL", emit_headerdel)
809 {
810 	std::ostringstream o;
811 
812 	std::ifstream ifs(HEADERDEL);
813 
814 	if (ifs.is_open())
815 	{
816 		std::string line;
817 
818 		while (std::getline(ifs, line).good())
819 			if (line.size())
820 				o << line << std::endl;
821 	}
822 
823 	emit_textarea("optheaderdel", o.str(), "off", 4, 40, "");
824 }
825 
826 
827 
showform(const char * filename,const std::map<std::string,std::string> & parms)828 static void showform(const char *filename,
829 		     const std::map<std::string, std::string> &parms)
830 {
831 	webmlmd::handler_list handlers;
832 
833 	std::ifstream i(filename);
834 
835 	if (!i.is_open())
836 	{
837 		std::cout << filename << ": cannot open"
838 			  << std::endl;
839 		return;
840 	}
841 
842 	std::string line;
843 
844 	while (std::getline(i, line).good())
845 	{
846 		std::string::iterator b=line.begin(), e=line.end();
847 
848 		while (b != e)
849 		{
850 			static const char mac_open[]="[@";
851 			static const char mac_close[]="@]";
852 
853 			std::string::iterator p=
854 				std::search(b, e, mac_open, mac_open+2);
855 
856 			std::cout << std::string(b, p);
857 
858 			b=p;
859 
860 			if (b == e)
861 				break;
862 
863 			b += 2;
864 
865 			p=std::search(b, e, mac_close, mac_close+2);
866 
867 			std::list<std::string> args;
868 
869 			std::replace(b, p, '_', ' ');
870 			while (b != p)
871 			{
872 				std::string::iterator word_start=b;
873 
874 				b=std::find(b, p, ':');
875 
876 				args.push_back(std::string(word_start, b));
877 
878 				if (b != p)
879 					++b;
880 			}
881 
882 			if (b != e)
883 				b += 2;
884 
885 			std::string handler_name;
886 
887 			if (!args.empty())
888 			{
889 				handler_name=args.front();
890 				args.pop_front();
891 			}
892 
893 			webmlmd::handler_list::iterator
894 				h=handlers.find(handler_name);
895 
896 			if (h != handlers.end())
897 				(*h->second)(args);
898 
899 			std::map<std::string, std::string>::const_iterator
900 				vb=parms.find(handler_name);
901 
902 			if (vb != parms.end())
903 			{
904 				std::string txt=vb->second;
905 
906 				if (vb->first.substr(0, 1) != ".")
907 					txt= webmlmd::html_escape(txt);
908 				std::cout << txt;
909 			}
910 		}
911 		std::cout << std::endl;
912 	}
913 }
914 
showhtmlform(const char * formname,std::map<std::string,std::string> & parms)915 void webmlmd::showhtmlform(const char *formname,
916 			   std::map<std::string, std::string> &parms)
917 {
918 	cginocache();
919 	std::cout << "Content-Type: text/html; charset=utf-8"
920 		  << std::endl
921 		  << std::endl;
922 
923 	showform(formname, parms);
924 }
925 
showhtmlform(const char * formname)926 void webmlmd::showhtmlform(const char *formname)
927 {
928 	std::map<std::string, std::string> dummy;
929 
930 	webmlmd::showhtmlform(formname, dummy);
931 }
932 
showerrorform(const char * formname,std::string errmsg)933 static void showerrorform(const char *formname,
934 			  std::string errmsg)
935 {
936 	std::map<std::string, std::string> parms;
937 
938 	parms["ERRMSG"]=errmsg;
939 
940 	webmlmd::showhtmlform(formname, parms);
941 }
942 
sendsubunsub(std::string ext)943 static void sendsubunsub(std::string ext)
944 {
945 	std::string address(cgi("address"));
946 
947 	std::string::iterator b=address.begin(),
948 		e=address.end(),
949 		p=std::find(b, e, '@');
950 
951 	if (p == e || std::find(p+1, e, '@') != e ||
952 	    std::find(p, e, '/') != e)
953 	{
954 		showerrorform("webmlmerror.tmpl.html",
955 			      "Invalid E-mail address: " + address);
956 		return;
957 	}
958 
959 	webmlmd::cmlm ctlmsg;
960 
961 	if (ctlmsg.start(ext, address, "ctlmsg"))
962 	{
963 		FILE *stdinptr=ctlmsg.stdinptr();
964 
965 		ctlmsg.mk_received_header();
966 		fprintf(stdinptr, "From: %s\n", address.c_str());
967 		fprintf(stdinptr, "Subject: %s\n", ext.c_str());
968 		fprintf(stdinptr, "\nSubscription request received.\n");
969 
970 		if (ctlmsg.wait())
971 		{
972 			webmlmd::showhtmlform("webmlmrequestreceived.tmpl.html");
973 			return;
974 		}
975 	}
976 
977 	showerrorform("webmlmerror.tmpl.html",
978 		      "An error occured processing this request.\n"
979 		      "Please try again later.\n");
980 }
981 
982 /*
983 ** /admin request, password is good
984 */
985 
adminrequest(std::string admin_path)986 static void adminrequest(std::string admin_path)
987 {
988 	if (admin_path == "gosublist")
989 	{
990 		std::map<std::string, std::string> parms;
991 
992 		parms["REFRESHURL"]=std::string(cgirelscriptptr())
993 			+ "/" + webmlmd::list_name() + "/admin/sublist";
994 		webmlmd::showhtmlform("webmlmpleasewait.tmpl.html", parms);
995 		return;
996 	}
997 
998 	if (admin_path == "sublist")
999 	{
1000 		webmlmd::showhtmlform("webmlmsublist.tmpl.html");
1001 		return;
1002 	}
1003 
1004 	if (admin_path == "subinfo")
1005 	{
1006 		webmlmd::showhtmlform("webmlmsubinfo.tmpl.html");
1007 		return;
1008 	}
1009 
1010 	if (admin_path == "mod")
1011 	{
1012 		std::string filename;
1013 
1014 		std::map<std::string, std::string> parms;
1015 
1016 		if (*cgi("domoderate"))
1017 		{
1018 			filename=cgi("msgname");
1019 
1020 			if (filename.find('/') != filename.npos)
1021 				filename="";
1022 		}
1023 
1024 		if (filename != "" && strcmp(cgi("modaction"), "accept") == 0)
1025 		{
1026 			parms[".ERRMSG"]=webmlmd::do_mod_accept(filename);
1027 
1028 			if (parms[".ERRMSG"] != "")
1029 				filename="";
1030 		}
1031 		else if (filename != "" && strcmp(cgi("modaction"), "reject")
1032 			 == 0)
1033 		{
1034 			parms[".ERRMSG"]=
1035 				webmlmd::do_mod_reject(filename,
1036 						       *cgi("returntosender")
1037 						       != 0,
1038 						       cgi("rejectmsg"));
1039 
1040 			if (parms[".ERRMSG"] != "")
1041 				filename="";
1042 		}
1043 
1044 		if (filename != "")
1045 		{
1046 			size_t dummy;
1047 
1048 			filename=webmlmd::get_next_mod_filename(filename,
1049 								dummy);
1050 
1051 			if (filename == "")
1052 			{
1053 				std::string url=std::string(cgirelscriptptr())
1054 					+ "/" + webmlmd::list_name()
1055 					+ "/admin";
1056 
1057 				cgiredirect(url.c_str());
1058 
1059 				fflush(stdout);
1060 				std::cout << "\nRedirect to " << url << "\n";
1061 				return;
1062 			}
1063 
1064 			cgi_put("msgname", filename.c_str());
1065 
1066 		}
1067 
1068 		webmlmd::showhtmlform("webmlmlistadminmod.tmpl.html", parms);
1069 		return;
1070 	}
1071 
1072 	webmlmd::showhtmlform("webmlmlistadmin.tmpl.html");
1073 }
1074 
1075 /*
1076 ** Some list request
1077 */
1078 
listrequest2(std::string list_name,std::string path_info)1079 static void listrequest2(std::string list_name, std::string path_info)
1080 {
1081 	if (path_info == "sendsub")
1082 	{
1083 		sendsubunsub("subscribe");
1084 		return;
1085 	}
1086 
1087 	if (path_info == "sendunsub")
1088 	{
1089 		sendsubunsub("unsubscribe");
1090 		return;
1091 	}
1092 
1093 	if (path_info == "doconfirm")
1094 	{
1095 		std::string method(cgi("method"));
1096 		std::string token(cgi("token"));
1097 
1098 		std::string errmsg="Confirmation failed.  No further information is available.";
1099 
1100 		if (std::find_if(method.begin(), method.end(),
1101 				 std::not1(std::ptr_fun(::isalpha))) ==
1102 		    method.end() &&
1103 		    std::find_if(token.begin(), token.end(),
1104 				 std::not1(std::ptr_fun(::isalpha))) ==
1105 		    token.end())
1106 		{
1107 			webmlmd::cmlm confirm;
1108 
1109 			if (confirm.start(method + "-" + token, "",
1110 					  "ctlmsg"))
1111 			{
1112 				FILE *stdinptr=confirm.stdinptr();
1113 
1114 				confirm.mk_received_header();
1115 				fprintf(stdinptr,
1116 					"Subject: yes -- confirmed by WebMLM\n"
1117 					"\n"
1118 					"Confirmed\n");
1119 
1120 				if (confirm.wait())
1121 				{
1122 					webmlmd::showhtmlform("webmlmprocessed.html");
1123 					return;
1124 				}
1125 
1126 				char buf[1024];
1127 				bool isfirst=true;
1128 
1129 				FILE *stdoutptr=confirm.stdoutptr();
1130 
1131 				while (fgets(buf, sizeof(buf), stdoutptr))
1132 				{
1133 					if (isfirst)
1134 					{
1135 						isfirst=false;
1136 						errmsg="";
1137 					}
1138 					errmsg += buf;
1139 				}
1140 			}
1141 		}
1142 
1143 		std::map<std::string, std::string> args;
1144 
1145 		args["ERROR"]=webmlmd::html_escape(errmsg);
1146 		webmlmd::showhtmlform("webmlmnotprocessed.html", args);
1147 		return;
1148 	}
1149 
1150 
1151 	std::string::iterator b=path_info.begin(), e=path_info.end();
1152 
1153 	std::string::iterator p=std::find(b, e, '/');
1154 
1155 	std::string w(b, p);
1156 
1157 	if (p != e) ++p;
1158 
1159 	if (w == "subconfirm" || w == "unsubconfirm")
1160 	{
1161 		std::map<std::string, std::string> args;
1162 
1163 		args["METHOD"]=w;
1164 		args["TOKEN"]=std::string(p, e);
1165 		webmlmd::showhtmlform("webmlmconfirm.html", args);
1166 		return;
1167 	}
1168 
1169 	if (w == "style.css")
1170 	{
1171 		std::map<std::string, std::string> args;
1172 
1173 		std::cout << "Content-Type: text/css"
1174 			  << std::endl << std::endl;
1175 		showform("style.css.tmpl", args);
1176 		return;
1177 	}
1178 
1179 	if (w == "admin")
1180 	{
1181 		char *password_cookie=cgi_get_cookie("password");
1182 		const char *password;
1183 
1184 		if (password_cookie)
1185 		{
1186 			if (getoption("LISTPW") == password_cookie)
1187 			{
1188 				free(password_cookie);
1189 				adminrequest(std::string(p, e));
1190 				return;
1191 			}
1192 
1193 			free(password_cookie);
1194 		}
1195 
1196 		password=cgi("password");
1197 
1198 		if (*password && getoption("LISTPW") == password)
1199 		{
1200 			struct cgi_set_cookie_info cookie_info;
1201 
1202 			cgi_set_cookie_info_init(&cookie_info);
1203 
1204 			cgi_set_cookie_session(&cookie_info, "password",
1205 					       password);
1206 
1207 			std::string cookie_path;
1208 
1209 			cookie_path=cgirelscriptptr();
1210 
1211 			cookie_path += "/";
1212 			cookie_path += list_name;
1213 
1214 			cgi_set_cookie_url(&cookie_info, cookie_path.c_str());
1215 
1216 			cgi_set_cookies(&cookie_info, 1);
1217 			cgi_set_cookie_info_free(&cookie_info);
1218 			adminrequest(std::string(p, e));
1219 			return;
1220 		}
1221 		webmlmd::showhtmlform("webmlmlistadminpw.tmpl.html");
1222 		return;
1223 	}
1224 
1225 	// Clear the password cookie when returning to the list screen
1226 
1227 	{
1228 		struct cgi_set_cookie_info cookie_info;
1229 
1230 		cgi_set_cookie_info_init(&cookie_info);
1231 
1232 		cgi_set_cookie_expired(&cookie_info, "password");
1233 
1234 		std::string cookie_path;
1235 
1236 		cookie_path=cgirelscriptptr();
1237 
1238 		cookie_path += "/";
1239 		cookie_path += list_name;
1240 
1241 		cgi_set_cookie_url(&cookie_info, cookie_path.c_str());
1242 
1243 		cgi_set_cookies(&cookie_info, 1);
1244 		cgi_set_cookie_info_free(&cookie_info);
1245 	}
1246 
1247 	webmlmd::showhtmlform("webmlmlistindex.tmpl.html");
1248 }
1249 
listrequest(std::string list_name,std::string path_info)1250 static void listrequest(std::string list_name,
1251 			std::string path_info)
1252 {
1253 	std::vector<std::string>::iterator lb=mlm_dirs.begin(),
1254 		le=mlm_dirs.end();
1255 
1256 	while (lb != le)
1257 	{
1258 		std::string d=*lb++;
1259 
1260 		if (webmlmd::basename(d) == list_name)
1261 		{
1262 			if (chdir(d.c_str()) < 0)
1263 			{
1264 				cginocache();
1265 				std::cout << "Content-Type: text/plain"
1266 					  << std::endl
1267 					  << std::endl;
1268 				perror(d.c_str());
1269 				return;
1270 			}
1271 			listrequest2(list_name, path_info);
1272 			return;
1273 		}
1274 	}
1275 
1276 	cginocache();
1277 	std::cout << "Content-Type: text/plain"
1278 		  << std::endl
1279 		  << std::endl
1280 		  << "List " << list_name << " not found."
1281 		  << std::endl;
1282 	return;
1283 }
1284 
main2(void * dummy)1285 static void main2(void *dummy)
1286 {
1287 	cgi_setup();
1288 
1289 	std::string n=webmlmd::list_name();
1290 
1291 	if (n.size() == 0)
1292 	{
1293 		webmlmd::showhtmlform(TEMPLATEDIR "/webmlmidx.html");
1294 	}
1295 	else
1296 	{
1297 		listrequest(n, list_cmd());
1298 	}
1299 
1300 	std::cout.flush();
1301 }
1302