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