1 /*
2 ** Copyright 2000-2007 Double Precision, Inc.
3 ** See COPYING for distribution information.
4 */
5 
6 #include	"config.h"
7 #include	"cmlm.h"
8 #include	"cmlmbounce.h"
9 #include	"cmlmarchive.h"
10 #include	"cmlmsubunsub.h"
11 #include	"random128/random128.h"
12 #include	"numlib/numlib.h"
13 #include	"dbobj.h"
14 
15 #include	<stdio.h>
16 #include	<string.h>
17 #include	<ctype.h>
18 #include	<fcntl.h>
19 #include	<sysexits.h>
20 #include	<iostream>
21 #include	<fstream>
22 #include	<vector>
23 #include	<sstream>
24 
25 #include <sys/types.h>
26 #if HAVE_SYS_WAIT_H
27 #include <sys/wait.h>
28 #endif
29 #ifndef WEXITSTATUS
30 #define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
31 #endif
32 #ifndef WIFEXITED
33 #define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
34 #endif
35 
36 static const char rcsid[]="$Id: cmlmbounce.C,v 1.15 2007/06/28 02:27:24 mrsam Exp $";
37 
38 static int handlebounce(std::string, std::string, unsigned long);
39 static std::string mkbouncetoken(std::string addr, std::string pfix);
40 
41 //
42 //  Initial bounce.  Determine address(es) that are bouncing.
43 //
44 
dobounce(const char * p)45 int dobounce(const char *p)
46 {
47 	unsigned long n=0;
48 
49 	while (isdigit((int)(unsigned char) *p))
50 		n=n*10 + (*p++ - '0');
51 
52 	if (*p && *p != '-')
53 	{
54 		std::cerr << "Invalid address." << std::endl;
55 		return (EX_SOFTWARE);
56 	}
57 
58 	std::string t=mktmpfilename();
59 	std::string tname= TMP "/" + t;
60 
61 	int tmpfile_fd=open(tname.c_str(), O_RDWR|O_CREAT|O_TRUNC, 0666);
62 
63 	if (tmpfile_fd < 0)
64 	{
65 		perror(tname.c_str());
66 		return (-1);
67 	}
68 
69 	afxopipestream tmpfile_pipe(tmpfile_fd);
70 
71 	int cin_fd=dup(0);
72 
73 	if (cin_fd < 0)
74 	{
75 		perror("dup");
76 		exit(1);
77 	}
78 
79 	int rc;
80 
81 	{
82 		afxipipestream cin_copy(cin_fd);
83 
84 		rc=copyio_noseek(cin_copy, tmpfile_pipe);
85 
86 		if (cin_copy.bad())
87 			rc= -1;
88 	}
89 
90 	if (rc == 0)
91 	{
92 		tmpfile_pipe.close();
93 		if (tmpfile_pipe.bad())
94 			rc= -1;
95 	}
96 
97 	std::ifstream tmpfile(tname.c_str());
98 
99 	if (rc || !tmpfile.is_open())
100 	{
101 		perror(tname.c_str());
102 		tmpfile.close();
103 		unlink(tname.c_str());
104 		return (rc);
105 	}
106 
107 	if (*p)
108 	{
109 		std::string addr=fromverp(p+1);
110 
111 		int exitstatus=EX_SOFTWARE;
112 
113 		tmpfile.close();
114 		if (addr.size() > 0 && addr.find('@') != addr.npos)
115 			exitstatus=handlebounce(tname, addr, n);
116 		else
117 			std::cerr << "Invalid address" << std::endl;
118 
119 		unlink(tname.c_str());
120 		return (exitstatus);
121 	}
122 
123 	// No need to reinvent the wheel.  Run reformime.
124 
125 int	pipefd[2];
126 
127 	if (pipe(pipefd) < 0)
128 	{
129 		perror("pipe");
130 		return (EX_TEMPFAIL);
131 	}
132 
133 pid_t	pid=fork();
134 
135 	if (pid < 0)
136 	{
137 		perror("fork");
138 		close(pipefd[0]);
139 		close(pipefd[1]);
140 		return (EX_TEMPFAIL);
141 	}
142 
143 	if (pid == 0)
144 	{
145 		close(0);
146 		if (dup(tmpfile_fd) < 0)
147 		{
148 			perror("dup");
149 			exit(EX_TEMPFAIL);
150 		}
151 		tmpfile.close();
152 		dup2(pipefd[1], 1);
153 		close(pipefd[0]);
154 		close(pipefd[1]);
155 		execl(REFORMIME, "reformime", "-D", (char *)0);
156 		perror("execl");
157 		exit(EX_OSERR);
158 	}
159 	close(pipefd[1]);
160 	tmpfile.close();
161 
162 unsigned	addrcnt=0;
163 
164 	{
165 		afxipipestream reformime(pipefd[0]);
166 
167 		std::string buf;
168 
169 		while (std::getline(reformime, buf).good())
170 		{
171 			size_t i=buf.find(' ');
172 
173 			if (i == buf.npos)	continue;
174 			if (strncasecmp(buf.c_str(), "f", 1))	continue;
175 
176 			// failed
177 
178 			buf=buf.substr(i);
179 			TrimLeft(buf);
180 			TrimRight(buf);
181 			if (buf.size() == 0)	continue;
182 			rc=handlebounce(tname, buf, n);
183 			if (rc)
184 			{
185 				unlink (tname.c_str());
186 				return (rc);
187 			}
188 			++addrcnt;
189 		}
190 	}
191 
192 int	exitstatus;
193 
194 	while ( wait(&exitstatus) != pid)
195 		;
196 
197 	if (WIFEXITED(exitstatus))
198 		exitstatus=WEXITSTATUS(exitstatus);
199 	else
200 		exitstatus=EX_SOFTWARE;
201 
202 	unlink(tname.c_str());
203 	return (exitstatus);
204 }
205 
206 //
207 //  This is called to handle a bounce to a single address.
208 //
209 
handlebounce(std::string tmpfilename,std::string addr,unsigned long n)210 static int handlebounce(std::string tmpfilename, std::string addr,
211 			unsigned long n)
212 {
213 ExclusiveLock bounce_lock(BOUNCESLOCK);
214 DbObj	dat;
215 struct	stat	stat_buf;
216 
217 char bufn[NUMBUFSIZE];
218 
219 	libmail_str_size_t(n, bufn);
220 
221 	addrlower(addr);
222 	if (addr.find('@') == addr.npos)
223 	{
224 		std::cerr << "Invalid address." << std::endl;
225 		return (EX_SOFTWARE);
226 	}
227 
228 	// Validate bounce address as much as possible:
229 	// 1.  Must be a current subscriber address
230 	// 2.  Bounce must specify a message number that exists
231 
232 	if (getinfo(addr, isfound))
233 		return (0);	// Not a subscriber (any more?)
234 
235 	if (stat( Archive::filename(n).c_str(), &stat_buf))
236 	{
237 		std::cerr << "Invalid bounce address." << std::endl;
238 		return (EX_SOFTWARE);
239 	}
240 
241 	// Locate the directory where bounces for this address are kept
242 
243 	if (dat.Open(BOUNCESDAT, "C"))
244 	{
245 		perror(BOUNCESDAT);
246 		return (EX_OSERR);
247 	}
248 
249 	std::string	bouncedir, bouncedirp;
250 
251 	std::string q;
252 
253 	if ((q=dat.Fetch(addr, "")) != "")
254 	{
255 		bouncedir=q;
256 
257 		bouncedirp=BOUNCES "/" + q;
258 		if (stat(bouncedirp.c_str(), &stat_buf))	// Stale entry
259 			q="";
260 	}
261 
262 	if (q == "") // First bounce for this address -- create this directory
263 	{
264 		bouncedir=mktmpfilename();
265 		bouncedirp=BOUNCES "/" + bouncedir;
266 
267 		if ( mkdir( bouncedirp.c_str(), 0755))
268 		{
269 			perror(bouncedirp.c_str());
270 			return (EX_OSERR);
271 		}
272 
273 		std::ofstream	addrfile((bouncedirp + "/.address").c_str());
274 
275 		addrfile << addr << std::endl << std::flush;
276 		if (addrfile.fail())
277 		{
278 			perror(bouncedirp.c_str());
279 			return (EX_OSERR);
280 		}
281 		if (dat.Store(addr, bouncedir, "R"))
282 			return (EX_SOFTWARE);
283 	}
284 
285 	if (link(tmpfilename.c_str(), (bouncedirp + "/" + bufn).c_str()) < 0)
286 		; /* ignore */
287 	return (0);
288 }
289 
290 //
291 //  Return the address associated with a bounce return address.
292 
getbounceaddr(const char * n)293 static std::string getbounceaddr(const char *n)
294 {
295 	std::string s;
296 	std::string buf;
297 	std::string ns=n;
298 
299 	if (strchr(n, '/'))	return ("");	// Script kiddies
300 
301 	size_t i=ns.find('-');
302 
303 	if (i == ns.npos)
304 		return "";
305 
306 	buf=ns.substr(0, i);
307 
308 	ns=ns.substr(i); // Bounce token
309 
310 	std::transform(ns.begin(), ns.end(), ns.begin(),
311 		       std::ptr_fun(::toupper));
312 
313 	ns=buf + ns;
314 
315 	s= TMP "/";
316 	s += ns;
317 
318 	std::ifstream	ifs(s.c_str());
319 
320 	if (!std::getline(ifs, buf).good())
321 		buf="";
322 
323 	ifs.close();
324 
325 	if (buf.size() > 0)
326 		unlink(s.c_str());
327 	return (buf);
328 }
329 
330 //
331 //  Warning message bounces, send a probe.
332 //
333 
dobounce1(const char * n)334 int dobounce1(const char *n)
335 {
336 	std::string addr=getbounceaddr(n);
337 
338 	if (addr == "")
339 	{
340 		std::cerr << "Invalid address." << std::endl;
341 		return (EX_SOFTWARE);
342 	}
343 
344 	std::string token(mkbouncetoken(addr, "bounce2-"));
345 
346 	if (token == "")
347 		return (EX_SOFTWARE);
348 
349 	std::string owner=get_verp_return(token);
350 	pid_t	p;
351 	afxopipestream ack(sendmail_bcc(p, owner));
352 
353 	ack << "From: " << myname() << " <" << owner << ">" << std::endl
354 	    << "To: " << cmdget_s("ADDRESS") << std::endl
355 	    << "Bcc: " << addr << std::endl;
356 
357 	copy_report("warn2msg.tmpl", ack);
358 
359 	ack.close();
360 	return (wait4sendmail(p));
361 }
362 
dobounce2(const char * n)363 int dobounce2(const char *n)
364 {
365 	std::string addr=getbounceaddr(n);
366 	std::string message;
367 	std::string buf;
368 
369 	if (addr == "")
370 	{
371 		std::cerr << "Invalid address." << std::endl;
372 		return (EX_SOFTWARE);
373 	}
374 
375 	while (std::getline(std::cin, buf).good())
376 	{
377 		if (message.size() + buf.size() < 50000)
378 		{
379 			message += buf;
380 			message += '\n';
381 		}
382 	}
383 	return (docmdunsub_bounce(addr, message));
384 }
385 
mkbouncetoken(std::string addr,std::string pfix)386 static std::string mkbouncetoken(std::string addr, std::string pfix)
387 {
388 	struct	stat	stat_buf;
389 	ExclusiveLock tmp_lock(TMPLOCK);
390 
391 	std::string f, token;
392 
393 	do
394 	{
395 		token=pfix;
396 		token += random128_alpha();
397 		f = TMP "/";
398 		f += token;
399 
400 	} while (stat(f.c_str(), &stat_buf) == 0);
401 
402 	{
403 		std::ofstream	ofs(f.c_str());
404 
405 		ofs << addr << std::endl << std::flush;
406 		if (ofs.fail())
407 		{
408 			perror(f.c_str());
409 			token="";
410 		}
411 		ofs.close();
412 		if (ofs.fail())
413 		{
414 			perror(f.c_str());
415 			token="";
416 		}
417 	}
418 	return (token);
419 }
420 
421 //
422 //  Generate a warning bounce message.
423 //
424 
bouncewarning(std::string addr,std::string lastbounce,std::string dir)425 int bouncewarning(std::string addr, std::string lastbounce, std::string dir)
426 {
427 	int b_fd=open(lastbounce.c_str(), O_RDONLY);
428 
429 	if (b_fd < 0)
430 	{
431 		perror(lastbounce.c_str());
432 		return (EX_SOFTWARE);
433 	}
434 
435 	afxipipestream bouncetxt(b_fd);
436 
437 	std::string boundary=mkboundary_msg_s(bouncetxt);
438 
439 	bouncetxt.seekg(0);
440 
441 	std::string token(mkbouncetoken(addr, "bounce1-"));
442 
443 	if (token == "")
444 		return (EX_SOFTWARE);
445 
446 	std::string owner=get_verp_return(token);
447 
448 	pid_t	p;
449 	afxopipestream ack(sendmail_bcc(p, owner));
450 
451 	ack << "From: " << myname() << " <" << owner << ">" << std::endl
452 		<< "To: " << cmdget_s("ADDRESS") << std::endl
453 		<< "Bcc: " << addr << std::endl
454 		<< "Mime-Version: 1.0" << std::endl
455 		<< "Content-Type: multipart/mixed; boundary=\""
456 			<< boundary << "\"" << std::endl
457 		<< "Content-Transfer-Encoding: 8bit" << std::endl;
458 
459 	copy_report("warn1headers.tmpl", ack);
460 
461 	ack << std::endl << "This is a MIME formatted message." << std::endl
462 		<< std::endl << "--" << boundary << std::endl;
463 
464 	copy_report("warn1text.tmpl", ack);
465 
466 	std::vector<long> bouncelist;
467 
468 	DIR	*dirp;
469 	struct	dirent *de;
470 
471 	dirp=opendir(dir.c_str());
472 
473 	while (dirp && (de=readdir(dirp)) != 0)
474 	{
475 		if (de->d_name[0] == '.')	continue;
476 
477 		std::istringstream i(de->d_name);
478 
479 		long n=0;
480 
481 		i >> n;
482 
483 		if (!i.fail())
484 			bouncelist.push_back(n);
485 	}
486 	if (dirp)	closedir(dirp);
487 
488 
489 	std::vector<long>::iterator b=bouncelist.begin(),
490 		e=bouncelist.end();
491 
492 	std::sort(b, e);
493 
494 	while (b != e)
495 	{
496 		ack << *b++ << std::endl;
497 	}
498 
499 	copy_report("warn1text2.tmpl", ack);
500 
501 	ack << std::endl << "--" << boundary << std::endl
502 		<< "Content-Type: message/rfc822" << std::endl << std::endl;
503 
504 	if (copyio_noseek(bouncetxt, ack))
505 		perror(lastbounce.c_str());
506 	ack << std::endl << "--" << boundary << "--" << std::endl << std::flush;
507 	ack.close();
508 
509 	return (wait4sendmail(p));
510 }
511