1 /*
2 ** Copyright 1998 - 2007 Double Precision, Inc.
3 ** See COPYING for distribution information.
4 */
5 
6 #include	"config.h"
7 #include	"cmlm.h"
8 #include	"cmlmsublist.h"
9 #include	"dbobj.h"
10 #include	"rfc822/rfc822.h"
11 #include	"numlib/numlib.h"
12 
13 #include <sstream>
14 
15 #include	<ctype.h>
16 #include	<time.h>
17 #include	<string.h>
18 #include	<iostream>
19 #include	<fstream>
20 #include	<list>
21 #include	<sys/types.h>
22 #include	<sys/stat.h>
23 #include	<sysexits.h>
24 #if HAVE_SYS_WAIT_H
25 #include <sys/wait.h>
26 #endif
27 #ifndef WEXITSTATUS
28 #define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
29 #endif
30 #ifndef WIFEXITED
31 #define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
32 #endif
33 
34 static const char rcsid[]="$Id: cmlm3.C,v 1.18 2007/12/17 12:09:18 mrsam Exp $";
35 /* I also want to encode /s as well, sometimes */
36 
37 #define	VERPSPECIAL	"@:%!+-[]/"
38 
39 static const char xdigit[]="0123456789ABCDEFabcdef";
40 
toverp(std::string buf)41 std::string toverp(std::string buf)
42 {
43 	std::string::iterator b=buf.begin(), e=buf.end();
44 	size_t i;
45 
46 	while (b != e)
47 	{
48 		if (*--e == '@')
49 		{
50 			*e='=';
51 			break;
52 		}
53 	}
54 
55 	for (i=0; i<buf.size(); ++i)
56 	{
57 		if (strchr(VERPSPECIAL, buf[i]) == 0)	continue;
58 		buf=buf.substr(0, i)+ '+' + xdigit[ (buf[i] >> 4) & 15] +
59 			xdigit[buf[i] & 15] + buf.substr(i+1);
60 		i += 2;
61 	}
62 	return (buf);
63 }
64 
fromverp(std::string buf)65 std::string fromverp(std::string buf)
66 {
67 	std::vector<char> obuf;
68 	std::string::iterator b=buf.begin(), e=buf.end();
69 	const char *r;
70 
71 	obuf.reserve(buf.size());
72 
73 	while (b != e)
74 	{
75 		if (*b != '+' || e-b < 3)
76 		{
77 			obuf.push_back(*b++);
78 			continue;
79 		}
80 
81 		++b;
82 
83 		int h=0, l=0;
84 
85 		r=strchr(xdigit, *b++);
86 		if (r)	h= r-xdigit;
87 		r=strchr(xdigit, *b++);
88 		if (r)	l= r-xdigit;
89 
90 		if (h >= 16) h -= 6;
91 		if (l >= 16) l -= 6;
92 
93 		obuf.push_back((char)(h*16+l));
94 	}
95 
96 	buf=std::string(obuf.begin(), obuf.end());
97 
98 	b=buf.begin();
99 	e=buf.end();
100 
101 	while (b != e)
102 	{
103 		if (*--e == '=')
104 		{
105 			*e='@';
106 			break;
107 		}
108 	}
109 
110 	return buf;
111 }
112 
mkfilename()113 std::string mkfilename()
114 {
115 	char buf[256];
116 	char *p;
117 
118 	buf[sizeof(buf)-1]=0;
119 
120 	if (gethostname(buf, sizeof(buf)-1))
121 		strcpy(buf, "courier-mlm");
122 
123 	// Drop any 8-bit characters from the hostname, to avoid creating
124 	// illegal mail headers.
125 
126 	for (p=buf; *p; p++)
127 		*p &= 127;
128 
129 	time_t	t;
130 	time(&t);
131 
132 	std::ostringstream o;
133 
134 	o << t << "." << getpid() << "." << buf;
135 
136 	return o.str();
137 }
138 
mktmpfilename()139 std::string mktmpfilename()
140 {
141 	std::string f;
142 	unsigned	n=0;
143 	struct	stat	stat_buf;
144 
145 	std::string p;
146 
147 	do
148 	{
149 		if (n)	sleep(n);
150 		n=3;
151 		f=mkfilename();
152 
153 		p= TMP "/" + f;
154 
155 	} while ( stat(p.c_str(), &stat_buf) == 0);
156 	return (f);
157 }
158 
159 
160 /*
161 ** Check whether an address can post to this list (whether it's listed as
162 ** either a subscriber, or an alias.
163 */
164 static int is_subscriber_1(std::string, std::string);
165 
is_subscriber(std::string from)166 int is_subscriber(std::string from)
167 {
168 	int	rc;
169 
170 	if (from.size() == 0) return (EX_NOUSER);
171 
172 	rc=is_subscriber_1(".", from);
173 	if (rc && rc != EX_NOUSER)
174 		return (rc);
175 	if (rc)
176 	{
177 		std::string	digestdir=cmdget_s("DIGEST");
178 
179 		if (digestdir.size() > 0)
180 			rc=is_subscriber_1(digestdir, from);
181 	}
182 	return (rc);
183 }
184 
185 static int getinfodir(std::string, std::string, int (*)(std::string));
186 
isfound(std::string dummy)187 int isfound(std::string dummy)
188 {
189 	return 0;
190 }
191 
is_subscriber_1(std::string dir,std::string from)192 static int is_subscriber_1(std::string dir, std::string from)
193 {
194 int	rc;
195 
196 	rc=getinfodir(dir, from, isfound);
197 
198 	if (rc == 0)	return (0);
199 
200 	/* Not on subscription rolls, maybe an alias */
201 
202 	std::string shared_lock_name=dir;
203 
204 	shared_lock_name += "/";
205 	shared_lock_name += SUBLOCKFILE;
206 
207 	SharedLock alias_lock(shared_lock_name.c_str());
208 	DbObj	alias;
209 
210 	std::string	alias_name=dir;
211 
212 	alias_name += "/" ALIASES;
213 
214 	if (alias.Open(alias_name, "R") == 0)
215 	{
216 		std::string key;
217 
218 		key="1";
219 		key += from;
220 
221 		addrlower(key);
222 
223 		if (alias.Fetch(key, "").size() != 0)
224 		{
225 			rc=0;
226 		}
227 	}
228 	return (rc);
229 }
230 
getinfo(std::string a,int (* func)(std::string))231 int getinfo(std::string a, int (*func)(std::string))
232 {
233 	return (getinfodir(".", a, func));
234 }
235 
getinfodir(std::string dir,std::string address,int (* func)(std::string))236 int getinfodir(std::string dir, std::string address, int (*func)(std::string))
237 {
238 	struct	stat	stat_buf;
239 
240 	std::string::iterator b=address.begin(), e=address.end(),
241 		p=std::find(b, e, '@');
242 
243 	if (p == e || std::find(++p, e, '@') != e ||
244 	    std::find(p, e, '.') == e ||
245 	    std::find(p, e, '/') != e)
246 	{
247 		std::cerr << "Invalid address." << std::endl;
248 		return (1);
249 	}
250 
251 	addrlower(address);
252 
253 	std::string shared_lock_name=dir;
254 
255 	shared_lock_name += "/";
256 	shared_lock_name += SUBLOCKFILE;
257 
258 	SharedLock subscription_lock(shared_lock_name.c_str());
259 
260 	std::string	buf;
261 
262 	buf=dir;
263 
264 	buf += "/" DOMAINS "/";
265 	buf += std::string(p, e);
266 
267 	DbObj	domain_file;
268 
269 	if (stat(buf.c_str(), &stat_buf) == 0 &&
270 	    domain_file.Open(buf, "W") == 0)
271 	{
272 		std::string val;
273 
274 		if ((val=domain_file.Fetch(address, "")).size() > 0)
275 		{
276 			int	rc= (*func)(val);
277 
278 			domain_file.Close();
279 			return (rc);
280 		}
281 
282 		domain_file.Close();
283 		return (EX_NOUSER);
284 	}
285 
286 	buf=dir;
287 	buf += "/" MISC;
288 	if (domain_file.Open(buf, "W"))
289 	{
290 		return (EX_NOUSER);
291 	}
292 
293 	std::string r;
294 
295 	if ((r=domain_file.Fetch(std::string(p, e), "")).size() == 0)
296 	{
297 		domain_file.Close();
298 		return (EX_NOUSER);
299 	}
300 
301 	std::list<std::string> misclist;
302 
303 	SubscriberList::splitmisc(r, misclist);
304 
305 	while (!misclist.empty())
306 	{
307 		if (misclist.front() == address)
308 		{
309 			misclist.pop_front();
310 
311 			std::string v;
312 
313 			if (!misclist.empty())
314 				v=misclist.front();
315 
316 			return (*func)(v);
317 		}
318 
319 		misclist.pop_front();
320 
321 		if (!misclist.empty())
322 			misclist.pop_front();
323 	}
324 
325 	domain_file.Close();
326 	return (EX_NOUSER);
327 }
328 
myname()329 std::string myname()
330 {
331 	std::string	myname_buf;
332 
333 	myname_buf = cmdget_s("NAME");
334 	if (myname_buf.size() == 0)
335 		myname_buf = "Courier Mailing List Manager";
336 
337 	return myname_buf;
338 }
339 
340 
341 // Run sendmail in the background
342 
sendmail(const char ** argv,pid_t & p)343 int sendmail(const char **argv, pid_t &p)
344 {
345 int	pipefd[2];
346 
347 	while (pipe(pipefd) == -1)
348 	{
349 		perror("pipe");
350 		sleep(5);
351 	}
352 
353 	while ((p=fork()) == -1)
354 	{
355 		perror("fork");
356 		sleep(5);
357 	}
358 
359 	if (p == 0)
360 	{
361 	char	*fakeenv=0;
362 
363 		dup2(pipefd[0], 0);
364 		close(pipefd[0]);
365 		close(pipefd[1]);
366 
367 		execve(SENDMAIL, (char **)argv, &fakeenv);
368 		perror("exec");
369 		exit(1);
370 	}
371 	close(pipefd[0]);
372 	return (pipefd[1]);
373 }
374 
wait4sendmail(pid_t p)375 int wait4sendmail(pid_t p)
376 {
377 int	waitstat;
378 pid_t	p2;
379 
380 	while ((p2=wait(&waitstat)) != p)
381 		;
382 	if (WIFEXITED(waitstat))
383 		return (WEXITSTATUS(waitstat));
384 	return (EX_TEMPFAIL);
385 }
386 
387 //
388 // Run sendmail with the -bcc flag, requesting only FAIL notices.
389 // If 'nodsn' is set, request no delivery status notices at all.
390 //
391 
sendmail_bcc(pid_t & p,std::string from,int nodsn)392 int sendmail_bcc(pid_t &p, std::string from, int nodsn)
393 {
394 const char	*argvec[7];
395 
396 	argvec[0]="sendmail";
397 	argvec[1]="-bcc";
398 	argvec[2]="-f";
399 	argvec[3]=from.c_str();
400 	argvec[4]="-N";
401 	argvec[5]=nodsn?"never":"fail";
402 	argvec[6]=0;
403 	return (sendmail(argvec, p));
404 }
405 
406 ////////////////////////////////////////////////////////////////////////////
407 //
408 // Read message, up to 16K bytes of it.
409 //
410 
readmsg()411 std::string readmsg()
412 {
413 	char	msgbuf[16384];
414 	size_t	i, j;
415 
416 	for (i=0; i<sizeof(msgbuf); )
417 	{
418 		std::cin.read(msgbuf+i, sizeof(msgbuf)-i);
419 
420 		int	n=std::cin.gcount();
421 
422 		if (n <= 0)	break;
423 		i += n;
424 	}
425 
426 	for (j=0; j<i; j++)
427 		if (msgbuf[j] == 0)	msgbuf[j]=' ';
428 
429 	return msgbuf;
430 }
431 
432 //
433 // Extract a specific header.
434 //
435 
header_s(std::string msgbuf,const char * header)436 std::string header_s(std::string msgbuf, const char *header)
437 {
438 	std::istringstream i(msgbuf);
439 	std::string line;
440 
441 	while (std::getline(i, line).good())
442 	{
443 		if (line.size() == 0)
444 			break; // End of headers
445 
446 		std::string::iterator b=line.begin(), e=line.end();
447 
448 		std::string::iterator p=std::find(b, e, ':');
449 
450 		std::string name(b, p);
451 
452 		std::transform(name.begin(), name.end(), name.begin(),
453 			       std::ptr_fun(::tolower));
454 
455 		if (name != header)
456 			continue;
457 
458 		if (p != e)
459 			++p;
460 
461 		std::string
462 			buf(std::find_if(p, e,
463 					 std::not1(std::ptr_fun(::isspace))),
464 			    e);
465 
466 		while (std::getline(i, line).good())
467 		{
468 			b=line.begin();
469 			e=line.end();
470 
471 			p=std::find_if(b, e,
472 				       std::not1(std::ptr_fun(::isspace)));
473 
474 			if (b == p)
475 				break;
476 
477 			buf=buf + " " + std::string(p, e);
478 		}
479 		return buf;
480 	}
481 	return "";
482 }
483 
484 //
485 //  Addresses to subscribe/unsubscribe are derived in two ways:
486 //
487 //  * From the headers.
488 //
489 //  * Explicitly specified in the subscription address:
490 //                 To:  list-subscribe-luser=host@listdomain
491 //
492 //  If we find an explicit address, use that.  Otherwise, grep the headers.
493 //
494 
returnaddr(std::string msg,const char * explicit_address)495 std::string returnaddr(std::string msg, const char *explicit_address)
496 {
497 	std::string addr;
498 	int n;
499 
500 	if (explicit_address && *explicit_address)
501 	{
502 		addr=fromverp(explicit_address);
503 	}
504 	else
505 	{
506 		// Preference:  Reply-To:, then From:, then envelope Sender.
507 
508 		addr=header_s(msg, "reply-to");
509 
510 		if (addr == "")	addr=header_s(msg, "from");
511 		if (addr == "")
512 		{
513 			const char *p=getenv("SENDER");
514 
515 			if (!p)	return (addr);
516 			addr=p;
517 		}
518 		else
519 		{
520 //
521 //  Grabbed the header, must now parse an address out of it.
522 //
523 			struct rfc822t *t=rfc822t_alloc_new(addr.c_str(),
524 							    NULL, NULL);
525 
526 			if (!t)
527 			{
528 				perror("malloc");
529 				exit(1);
530 			}
531 
532 		struct rfc822a *a=rfc822a_alloc(t);
533 
534 			if (!a)
535 			{
536 				perror("malloc");
537 				exit(1);
538 			}
539 
540 			if (a->naddrs <= 0)
541 			{
542 				addr="";
543 				rfc822a_free(a);
544 				rfc822t_free(t);
545 				return (addr);
546 			}
547 
548 		char	*s=rfc822_getaddr(a, 0);
549 
550 			rfc822a_free(a);
551 			rfc822t_free(t);
552 
553 			if (!s)
554 			{
555 				perror("malloc");
556 				exit(1);
557 			}
558 
559 			addr=s;
560 			free(s);
561 		}
562 	}
563 
564 	// Sanity check: all addresses must have an @
565 
566 	n=0;
567 
568 	std::string::iterator b=addr.begin(), e=addr.end();
569 
570 	while (b != e)
571 		if (*b++ == '@')
572 			++n;
573 
574 	if (n != 1)
575 		addr="";
576 
577 	// One more sanity check: domain.
578 
579 	std::string::iterator p=std::find(addr.begin(), e, '@');
580 
581 	if (std::find(p, e, '/') != e || std::find(p, e, '.') == e)
582 		addr="";
583 
584 	return (addr);
585 }
586