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