1 /* $Id$ */
2
3 /*
4 *
5 * Copyright (C) 2004-2007 David Mazieres (dm@uun.org)
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2, or (at
10 * your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20 * USA
21 *
22 */
23
24 #include "asmtpd.h"
25 #include "rawnet.h"
26 #include "getopt_long.h"
27 #include "dnsimpl.h"
28
29 bool opt_d;
30 int opt_verbose;
31
32 bool terminated;
33 str path_avenger;
34 str path_bindir;
35 str path_pfos;
36 synfp_collect *synfpc;
37
38 struct listener {
39 const sockaddr_in sin;
40 int lfd;
41
42 list_entry<listener> link;
43 ihash_entry<listener> hlink;
44
45 listener (const sockaddr_in &a);
46 ~listener ();
47 bool init ();
48 void doactive (bool on);
49 void doaccept ();
50
51 static void active (bool on);
52 static bool config (options *opt);
53 };
54
55 static list<listener, &listener::link> listen_list;
56 static ihash<const sockaddr_in, listener,
57 &listener::sin, &listener::hlink> listen_tab;
58
listener(const sockaddr_in & a)59 listener::listener (const sockaddr_in &a)
60 : sin (a), lfd (-1)
61 {
62 listen_list.insert_head (this);
63 listen_tab.insert (this);
64 }
65
~listener()66 listener::~listener ()
67 {
68 if (lfd >= 0) {
69 fdcb (lfd, selread, NULL);
70 close (lfd);
71 }
72 listen_list.remove (this);
73 listen_tab.remove (this);
74 }
75
76 bool
init()77 listener::init ()
78 {
79 if (lfd >= 0)
80 return true;
81 lfd = inetsocket (SOCK_STREAM, ntohs (sin.sin_port),
82 ntohl (sin.sin_addr.s_addr));
83 if (lfd < 0) {
84 warn ("TCP port %s:%d: %m\n", inet_ntoa (sin.sin_addr),
85 htons (sin.sin_port));
86 delete this;
87 return false;
88 }
89 close_on_exec (lfd);
90 make_async (lfd);
91 if (listen (lfd, 5) < 0) {
92 close (lfd);
93 lfd = -1;
94 return false;
95 }
96 return true;
97 }
98
99 void
doactive(bool on)100 listener::doactive (bool on)
101 {
102 if (on)
103 fdcb (lfd, selread, wrap (this, &listener::doaccept));
104 else
105 fdcb (lfd, selread, NULL);
106 }
107
108 void
active(bool on)109 listener::active (bool on)
110 {
111 for (listener *lp = listen_list.first; lp;
112 lp = listen_list.next (lp))
113 lp->doactive (on);
114 }
115
116 bool
config(options * opt)117 listener::config (options *opt)
118 {
119 inet_bindaddr.s_addr = htonl (INADDR_ANY);
120
121 bool any = false;
122 for (sockaddr_in *sinp = opt->bindaddrv.base ();
123 sinp < opt->bindaddrv.lim (); sinp++) {
124 if (sinp->sin_addr.s_addr == htonl (INADDR_ANY))
125 any = true;
126 if (!listen_tab[*sinp])
127 vNew listener (*sinp);
128 }
129 for (listener *lp = listen_list.first, *nlp; lp; lp = nlp) {
130 nlp = listen_list.next (lp);
131 if (!opt->bindaddrh[lp->sin])
132 delete lp;
133 else
134 lp->init ();
135 }
136
137 // XXX
138 if (!any)
139 for (sockaddr_in *sinp = opt->bindaddrv.base ();
140 sinp < opt->bindaddrv.lim (); sinp++)
141 if (sinp->sin_addr.s_addr != htonl (INADDR_LOOPBACK)
142 && listen_tab[*sinp]) {
143 inet_bindaddr = sinp->sin_addr;
144 break;
145 }
146
147 toggle_listen (true);
148 return listen_list.first;
149 }
150
newcon(int f,const sockaddr_in & sin)151 newcon::newcon (int f, const sockaddr_in &sin)
152 : sin (sin), fd (f), ii (NULL), t (TRUST_NONE),
153 cbpending (0), failed (false)
154 {
155 }
156
157 void
init()158 newcon::init ()
159 {
160 cbpending++;
161
162 make_async (fd);
163 close_on_exec (fd);
164
165 if (sin.sin_addr.s_addr == htonl (INADDR_LOOPBACK))
166 t = TRUST_LOCAL;
167 else {
168 for (const ipmask *mp = opt->trustednets.base ();
169 t < TRUST_RCPT && mp < opt->trustednets.lim (); mp++)
170 if ((sin.sin_addr.s_addr & mp->mask) == mp->net)
171 t = TRUST_RCPT;
172
173 if (t < TRUST_RCPT) {
174 ii = ipinfo::lookup (sin.sin_addr, true);
175 if (str err = ii->addcon ()) {
176 use (write (fd, err, err.len ()));
177 close (fd);
178 delete this;
179 return;
180 }
181 }
182 }
183
184 smtpd::num_smtpd++;
185 toggle_listen ();
186
187 if (!name) {
188 cbpending++;
189 identptr (fd, wrap (this, &newcon::ident_cb), opt->ident_timeout);
190 }
191 else if (!h) {
192 cbpending++;
193 dns_hostbyaddr (sin.sin_addr, wrap (this, &newcon::ptr_cb));
194 }
195 else {
196 cbpending++;
197 ptr_cb (h, 0);
198 }
199
200 #if USE_SYNFP
201 if (synfpc && t < TRUST_LOCAL) {
202 cbpending++;
203 synfpc->lookup (sin, wrap (this, &newcon::synfp_cb));
204 }
205 #endif /* USE_SYNFP */
206
207 maybe_start ();
208 }
209
210 void
ident_cb(str nn,ptr<hostent> hh,int err)211 newcon::ident_cb (str nn, ptr<hostent> hh, int err)
212 {
213 assert (nn);
214 name = nn;
215 ptr_cb (hh, err);
216 }
217
218 void
ptr_cb(ptr<hostent> hh,int err)219 newcon::ptr_cb (ptr<hostent> hh, int err)
220 {
221 if (hh)
222 h = hh;
223 if (dns_tmperr (err) && sin.sin_addr.s_addr != htonl (INADDR_LOOPBACK)) {
224 warn << name << ": " << dns_strerror (err) << "\n";
225 if (opt->allow_dnsfail)
226 dns_error = strbuf () << name << ": " << dns_strerror (err);
227 else {
228 str msg (strbuf ("421 %s\r\n", dns_strerror (err)));
229 use (write (fd, msg.cstr (), msg.len ())); // Don't care if truncated
230 failed = true;
231 }
232 }
233
234 if (failed || opt->rbls.empty ())
235 maybe_start ();
236 else {
237 rs = New refcounted<rbl_status>;
238 rbl_check_con (rs, opt->rbls, sin.sin_addr,
239 h ? h->h_name : (char *) NULL,
240 wrap (this, &newcon::rbl_cb));
241 }
242 }
243
244 void
rbl_cb()245 newcon::rbl_cb ()
246 {
247 maybe_start ();
248 }
249
250 void
synfp_cb(str fp)251 newcon::synfp_cb (str fp)
252 {
253 #if SYNFP_DEBUG
254 if (fp)
255 warn ("synfp-cb %s:%d %s\n", inet_ntoa (sin.sin_addr),
256 ntohs (sin.sin_port), fp.cstr ());
257 else
258 warn ("synfp-cb %s:%d NULL\n", inet_ntoa (sin.sin_addr),
259 ntohs (sin.sin_port));
260 #endif
261
262 synfp = fp;
263 maybe_start ();
264 }
265
266 void
maybe_start()267 newcon::maybe_start ()
268 {
269 if (--cbpending)
270 return;
271 smtpd::num_smtpd--;
272 if (failed) {
273 if (ii) {
274 ii->error ();
275 ii->delcon ();
276 }
277 close (fd);
278 delete this;
279 toggle_listen ();
280 return;
281 }
282
283 if (h) {
284 str hname = h->h_name;
285 for (const str *tdp = opt->trusteddomains.base ();
286 t < TRUST_RCPT && tdp < opt->trusteddomains.lim (); tdp++) {
287 if (tdp->len () > hname.len ()
288 || strcasecmp (tdp->cstr (),
289 hname.cstr () + hname.len () - tdp->len ()))
290 continue;
291 if (tdp->len () == hname.len ()) {
292 t = TRUST_RCPT;
293 }
294 else
295 switch (hname[hname.len () - tdp->len () - 1]) {
296 case '.':
297 case '@':
298 t = TRUST_RCPT;
299 }
300 }
301 }
302
303 if (t >= TRUST_RCPT && ii) {
304 ii->delcon ();
305 ii = NULL;
306 }
307
308 vNew smtpd (ii, fd, sin, name, synfp, t, rs, h, dns_error);
309 delete this;
310 }
311
312 void
doaccept()313 listener::doaccept ()
314 {
315 sockaddr_in sin;
316 socklen_t sinlen = sizeof (sin);
317 bzero (&sin, sizeof (sin));
318
319 int fd = accept (lfd, (sockaddr *) &sin, &sinlen);
320 if (fd < 0) {
321 if (errno != EAGAIN)
322 warn ("accept: %m\n");
323 return;
324 }
325
326 if (opt->debug_smtpd)
327 warn ("accepted connection from %s:%d (fd %d)\n",
328 inet_ntoa (sin.sin_addr), ntohs (sin.sin_port), fd);
329 newcon *nc = New newcon (fd, sin);
330 nc->init ();
331 }
332
333 static void
doexit(int val)334 doexit (int val)
335 {
336 warn << progname << " pid " << getpid () << " exiting\n";
337 exit (val);
338 }
339
340 void
toggle_listen(bool force)341 toggle_listen (bool force)
342 {
343 static bool state;
344
345 if (terminated && !smtpd::num_smtpd) {
346 delaycb (0, 2000000, wrap (doexit, 0));
347 return;
348 }
349
350 bool ostate = state;
351 state = !terminated && smtpd::num_smtpd < opt->max_clients;
352 if (force || state != ostate)
353 listener::active (state);
354 }
355
356 static void
termsig(int sig)357 termsig (int sig)
358 {
359 if (terminated) {
360 static int nsig;
361 if (!smtpd::num_indata) {
362 warn ("hard shutdown on second signal\n");
363 doexit (1);
364 }
365 if (++nsig > 2) {
366 warn ("aborting clients in data and exiting immediately\n");
367 doexit (1);
368 }
369 warn ("waiting for clients still in data\n");
370 return;
371 }
372 warn ("shutting down on signal %d\n", sig);
373 terminated = true;
374 clear_filters ();
375 while (listen_list.first)
376 delete listen_list.first;
377
378 for (smtpd *s = smtplist.first, *ns; s; s = ns) {
379 ns = smtplist.next (s);
380 s->maybe_shutdown ();
381 }
382
383 toggle_listen ();
384 }
385
386 static void
reconfig()387 reconfig ()
388 {
389 if (terminated)
390 return;
391
392 warn ("re-reading configuration file\n");
393 options *nopt = New options;
394 if (!parseconfig (nopt, config_file)) {
395 delete nopt;
396 warn ("errors found in config file, keeping old configuration\n");
397 return;
398 }
399
400 if (opt->logpriority != nopt->logpriority
401 || opt->logtag != nopt->logtag) {
402 syslog_priority = nopt->logpriority;
403 warn << "switching log tag/priority to "
404 << nopt->logtag << "/" << syslog_priority << "\n";
405 start_logger (nopt->logtag);
406 }
407
408 if (!listener::config (nopt))
409 fatal ("No requested TCP ports available\n");
410 netpath_reset ();
411
412 delete opt;
413 opt = nopt;
414
415 #if USE_SYNFP
416 delete synfpc;
417 synfpc = NULL;
418 if (opt->synfp) {
419 synfpc = New synfp_collect (opt->synfp_wait, opt->synfp_buf);
420 if (!synfpc->init (opt->bindaddrv)) {
421 delete synfpc;
422 synfpc = NULL;
423 }
424 }
425 #endif /* USE_SYNFP */
426
427 ssl_init ();
428 }
429
430 static void
cleargroups()431 cleargroups ()
432 {
433 /* For efficiency, we want to be able to drop privileges to the
434 * avenger user without calling initgroups. So we'd better get rid
435 * of any supplemental privileged groups root might belong to. */
436 if (!getuid ()) {
437 GETGROUPS_T gid = getgid ();
438 #ifdef HAVE_EGID_IN_GROUPLIST
439 setgroups (1, &gid);
440 #else /* !HAVE_EGID_IN_GROUPLIST */
441 if (setgroups (0, NULL))
442 setgroups (1, &gid);
443 #endif /* !HAVE_EGID_IN_GROUPLIST */
444 }
445 }
446
447 static void
dumpstats()448 dumpstats ()
449 {
450 quota_dump (warnx);
451 }
452 static void
smtpstart()453 smtpstart ()
454 {
455 cleargroups ();
456 ssl_init ();
457
458 if (!listener::config (opt))
459 fatal ("No requested TCP ports available\n");
460
461 if (opt->smtp_filter)
462 run_cmd (opt->smtp_filter, "clear");
463
464 sigcb (SIGINT, wrap (&termsig, SIGINT));
465 sigcb (SIGTERM, wrap (&termsig, SIGTERM));
466 sigcb (SIGHUP, wrap (reconfig));
467 sigcb (SIGUSR1, wrap (dumpstats));
468
469 if (!opt_d) {
470 daemonize (opt->logtag);
471 if (!chdir ("/"))
472 xputenv ("PWD=/");
473 }
474 warn << progname << " (Mail Avenger) version " << VERSION << ", pid "
475 << getpid () << "\n";
476 warn ("%s is %s\n", AVENGER, path_avenger.cstr ());
477 //warn ("pf.os is %s\n", path_pfos.cstr ());
478 warn ("bindir is %s\n", path_bindir.cstr ());
479
480 #if USE_SYNFP
481 if (opt->synfp) {
482 synfpc = New synfp_collect (opt->synfp_wait, opt->synfp_buf);
483 if (!synfpc->init (opt->bindaddrv)) {
484 delete synfpc;
485 synfpc = NULL;
486 }
487 }
488 #endif /* USE_SYNFP */
489 }
490
491 static bool tst_eof;
492 static int tst_n;
493 static void
spftst_2(str line,spf_result res,str expl,str mech)494 spftst_2 (str line, spf_result res, str expl, str mech)
495 {
496 aout << ">>>" << spf_print (res) << ": " << line << "\n";
497 if (mech)
498 aout << " (" << mech << ")\n";
499 if (!--tst_n && tst_eof)
500 exit (0);
501 }
502 static void
spftst(str line,int err)503 spftst (str line, int err)
504 {
505 if (!line) {
506 tst_eof = true;
507 if (!tst_n)
508 exit (0);
509 return;
510 }
511
512 static rxx parse ("^(\\d+(\\.\\d+){3})\\s+(\\S+)(\\s+(\\S+))?$");
513 if (!parse.match (line))
514 warnx << "?syntax error\n";
515 else {
516 tst_n++;
517 in_addr a;
518 a.s_addr = inet_addr (parse[1]);
519 spf_check (a, parse[3], wrap (spftst_2, line), parse[5]);
520 }
521
522 ain->readline (wrap (spftst));
523 }
524
525 static void
rbltst_3(str name,ref<rbl_status> stat)526 rbltst_3 (str name, ref<rbl_status> stat)
527 {
528 aout << ">>> " << name << "\n";
529 aout << "score " << stat->score;
530 if (stat->trusted)
531 aout << ", whitelisted";
532 aout << "\n";
533 for (rbl_status::result *rp = stat->results.base ();
534 rp < stat->results.lim (); rp++)
535 aout << rp->tostr (false) << "\n";
536 aout << "----------------\n";
537
538 if (!--tst_n && tst_eof)
539 exit (0);
540 }
541 static void
rbltst_2(ref<rbl_status> rs,in_addr addr,ptr<hostent> h,int err)542 rbltst_2 (ref<rbl_status> rs, in_addr addr, ptr<hostent> h, int err)
543 {
544 if (err && dns_tmperr (err))
545 warn << inet_ntoa (addr) << ": " << dns_strerror (err) << "\n";
546 if (h)
547 rbl_check_con (rs, opt->rbls, addr, h->h_name,
548 wrap (rbltst_3, inet_ntoa (addr), rs));
549 else
550 rbl_check_con (rs, opt->rbls, addr, NULL,
551 wrap (rbltst_3, inet_ntoa (addr), rs));
552 }
553 static void
rbltst(str line,int err)554 rbltst (str line, int err)
555 {
556 if (!line) {
557 tst_eof = true;
558 if (!tst_n)
559 exit (0);
560 return;
561 }
562
563 ref<rbl_status> rs (New refcounted<rbl_status>);
564 in_addr a;
565 if (str r = extract_domain (line)) {
566 tst_n++;
567 rbl_check_env (rs, opt->rbls, r, wrap (rbltst_3, line, rs));
568 }
569 else if (inet_aton (line, &a) == 1) {
570 tst_n++;
571 dns_hostbyaddr (a, wrap (rbltst_2, rs, a));
572 }
573 else
574 aout << "?syntax error\n";
575
576 ain->readline (wrap (rbltst));
577 }
578
579 static void avenge_usage () __attribute__ ((noreturn));
580 static void
avenge_usage()581 avenge_usage ()
582 {
583 warnx << "usage: " << progname << " --avenge recipient [sender [ip-addr]]\n";
584 exit (1);
585 }
586 static void
avenge_c(aios_t in,strbuf sb,ref<vec<str>> cmdv,str line,int err)587 avenge_c (aios_t in, strbuf sb, ref<vec<str> > cmdv, str line, int err)
588 {
589 static rxx resprx ("^(\\d\\d\\d)(-| )(.*)$");
590 if (!line)
591 fatal ("test SMTP server: %s\n", strerror (err));
592 if (!resprx.match (line))
593 fatal ("bad line from test SMTP server:\n%s\n", line.cstr ());
594 sb << line << "\n";
595 if (resprx[2] == " ") {
596 str r (sb);
597 //warnx << r;
598 if (r[0] != '2') {
599 warnx << "\nrejected by SMTP server:\n" << r;
600 exit (1);
601 }
602 sb.tosuio ()->clear ();
603 if (cmdv->empty ()) {
604 warnx << "\naccepted by SMTP server:\n" << r;
605 exit (0);
606 }
607 //warnx << "\nSMTP test <<< " << cmdv->front () << "\n";
608 in << cmdv->pop_front () << "\r\n";
609 }
610 in->readline (wrap (avenge_c, in, sb, cmdv));
611 }
612 static void
avenge_s(int s,sockaddr_in sin,ptr<hostent> h,int err)613 avenge_s (int s, sockaddr_in sin, ptr<hostent> h, int err)
614 {
615 str msg;
616 if (!h && dns_tmperr (err))
617 msg = strbuf ("%s: %s", inet_ntoa (sin.sin_addr), dns_strerror (err));
618 vNew smtpd (NULL, s, sin, "SMTP-test", NULL, TRUST_NONE, NULL, h, msg);
619 }
620 static void
avenge(int argc,char ** argv)621 avenge (int argc, char **argv)
622 {
623 if (argc < 1 || argc > 3)
624 avenge_usage ();
625
626 int ls = inetsocket (SOCK_STREAM, 0,
627 htonl (opt->bindaddrv[0].sin_addr.s_addr));
628 if (ls < 0)
629 fatal ("socket: %m\n");
630 if (listen (ls, 1) < 0)
631 fatal ("listen: %m\n");
632
633 sockaddr_in sin;
634 socklen_t sinlen = sizeof (sin);
635 bzero (&sin, sizeof (sin));
636 getsockname (ls, (sockaddr *) &sin, &sinlen);
637 if (sin.sin_addr.s_addr == htonl (INADDR_ANY)) {
638 sin.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
639 vec<in_addr> myips;
640 for (myipaddrs (&myips); !myips.empty (); myips.pop_front ())
641 if (myips[0].s_addr != ntohl (INADDR_LOOPBACK)) {
642 sin.sin_addr = myips[0];
643 break;
644 }
645 }
646
647 int c = inetsocket (SOCK_STREAM);
648 if (c < 0)
649 fatal ("socket: %m\n");
650 make_async (c);
651 if (connect (c, (sockaddr *) &sin, sizeof (sin)) < 0
652 && errno != EINPROGRESS)
653 fatal ("TCP connect: %m\n");
654
655 sinlen = sizeof (sin);
656 int s = accept (ls, (sockaddr *) &sin, &sinlen);
657 if (s < 0)
658 fatal ("accept: %m\n");
659 close (ls);
660 make_async (s);
661
662 opt->debug_avenger = true;
663 opt->vrfy_delay = 0;
664
665 if (argc >= 3 && inet_aton (argv[2], &sin.sin_addr) != 1)
666 avenge_usage ();
667 dns_hostbyaddr (sin.sin_addr, wrap (avenge_s, s, sin));
668
669 ref<vec<str> > cmdv = New refcounted<vec<str> >;
670 cmdv->push_back (strbuf () << "ehlo " << opt->hostname);
671
672 if (argc >= 2)
673 cmdv->push_back (strbuf () << "mail from:<" << argv[1] <<">");
674 else
675 cmdv->push_back (strbuf () << "mail from:<postmaster@"
676 << opt->hostname << ">");
677
678 cmdv->push_back (strbuf () << "rcpt to:<" << argv[0] <<">");
679
680 aios_t in (aios::alloc (c));
681 in->readline (wrap (avenge_c, in, strbuf (), cmdv));
682 }
683
684 static void
path_init()685 path_init ()
686 {
687 static rxx colonplus (":+");
688 str path = getenv ("PATH");
689 strbuf sb;
690 sb << "PATH=" << path_bindir;
691 vec<str> comp;
692 split (&comp, colonplus, path);
693 while (!comp.empty ())
694 if (comp.front () == path_bindir)
695 comp.pop_front ();
696 else
697 sb << ":" << comp.pop_front ();
698 path = sb;
699 xputenv (path);
700 //warn << path << "\n";
701 }
702
703 inline bool
execok(const char * path)704 execok (const char *path)
705 {
706 struct stat sb;
707 return !stat (path, &sb) && S_ISREG (sb.st_mode) && (sb.st_mode & 0111);
708 }
709 inline str
striplast(const char * in)710 striplast (const char *in)
711 {
712 const char *p = in + strlen (in);
713 while (p > in && p[-1] == '/')
714 p--;
715 while (p > in && p[-1] != '/')
716 p--;
717 while (p > in && p[-1] == '/')
718 p--;
719 if (p > in)
720 return str (in, p - in);
721 return NULL;
722 }
723 inline str
stripfirst(const char * in)724 stripfirst (const char *in)
725 {
726 while (in && *in && *in != '/')
727 in++;
728 while (in && *in && *in == '/')
729 in++;
730
731 if (*in)
732 return in;
733 return NULL;
734 }
735 static str
mycwd()736 mycwd ()
737 {
738 struct stat sb1, sb2;
739 if (stat (".", &sb1))
740 return NULL;
741 if (char *pwd = getenv ("PWD"))
742 if (!stat (pwd, &sb2) && sb1.st_dev == sb2.st_dev
743 && sb1.st_ino == sb2.st_ino)
744 return pwd;
745 char buf[MAXPATHLEN + 1];
746 return getcwd (buf, sizeof (buf));
747 }
748 static str
normalize_path(str path)749 normalize_path (str path)
750 {
751 str dir;
752 if (path[0] != '/') {
753 while (path && path[0] == '.' && path[1] == '/')
754 path = stripfirst (path);
755 if (path && (dir = mycwd ())) {
756 if (!strncmp (path, "../", 3))
757 if (str ndir = striplast (dir)) {
758 path = stripfirst (path);
759 dir = ndir;
760 }
761 path = dir << "/" << path;
762 }
763 else if (!path)
764 path = mycwd ();
765 }
766 return path;
767 }
768 static void
find_avenger()769 find_avenger ()
770 {
771 str dir = progdir;
772 if (!dir) {
773 dir = find_program (progname);
774 if (dir)
775 dir = striplast (dir);
776 }
777 while (dir && dir.len () && dir[dir.len () - 1] == '/')
778 dir = substr (dir, 0, dir.len () - 1);
779
780 str path, bindir;
781 if (dir) {
782 if ((path = striplast (dir))) {
783 bindir = path << "/bin";
784 path_pfos = path << "/share/pf.os";
785 path = path << "/" << "libexec/" AVENGER;
786 if (!execok (path))
787 path = NULL;
788 }
789 if (!path && (path = striplast (dir))) {
790 bindir = path << "/util";
791 path = path << "/" AVENGER;
792 path_pfos = path << "/pf.os";
793 if (!execok (path))
794 path = NULL;
795 }
796 else if (!path && dir == ".") {
797 bindir = "../util";
798 path = "../" AVENGER;
799 path_pfos = "../pf.os";
800 if (!execok (path))
801 path = NULL;
802 }
803 else if (!path) {
804 bindir = "util";
805 path = AVENGER;
806 path_pfos = "pf.os";
807 if (!execok (path))
808 path = NULL;
809 }
810 }
811 if (!path) {
812 bindir = BINDIR;
813 path_pfos = DATADIR "/pf.os";
814 if (!execok (path = LIBEXEC "/" AVENGER))
815 path = NULL;
816 }
817
818 if (path) {
819 path = normalize_path (path);
820 bindir = normalize_path (bindir);
821 path_pfos = normalize_path (path_pfos);
822 }
823
824 if (!path)
825 fatal ("cannot find %s program\n", AVENGER);
826 path_avenger = path;
827 path_bindir = bindir;
828 if (access (path_pfos, 0) < 0)
829 path_pfos = DATADIR "/pf.os";
830 if (access (path_pfos, 0) && !access ("/etc/pf.os", 0))
831 path_pfos = "/etc/pf.os";
832 path_init ();
833 }
834
835 static str
compile_options()836 compile_options ()
837 {
838 bool set = false;
839 strbuf sb;
840 #define setopt(opt) \
841 do { \
842 sb << (set ? " " : " (") << #opt; \
843 set = true; \
844 } while (0)
845 #if !USE_SYNFP
846 setopt (no-synfp);
847 #endif /* !USE_SYNFP */
848 #ifdef SASL
849 setopt (SASL);
850 #endif /* SASL */
851 #ifndef STARTTLS
852 setopt (no-starttls);
853 #endif /* !STARTTLS */
854 #undef setopt
855 if (set)
856 sb << ")";
857 return sb;
858 }
859
860 static void usage () __attribute__ ((noreturn));
861 static void
usage()862 usage ()
863 {
864 warnx << "usage: " << progname << " [-d] [-f <config-file]\n"
865 #if USE_SYNFP
866 << " [--spf | --rbl | --avenge | --synfp | --netpath] ...\n";
867 #else /* !USE_SYNFP */
868 << " [--spf | --rbl | --avenge | --netpath] ...\n";
869 #endif /* !USE_SYNFP */
870 exit (1);
871 }
872
873 int
main(int argc,char ** argv)874 main (int argc, char **argv)
875 {
876 setprogname (argv[0]);
877
878 int mode = 0;
879 option o[] = {
880 { "version", no_argument, &mode, 1 },
881 { "help", no_argument, &mode, 2 },
882 { "spf", no_argument, &mode, 3 },
883 { "rbl", no_argument, &mode, 4 },
884 #if USE_SYNFP
885 { "synfp", no_argument, &mode, 5 },
886 #endif /* USE_SYNFP */
887 { "netpath", no_argument, &mode, 6 },
888 { "avenge", no_argument, &mode, 7 },
889 { "resconf", no_argument, &mode, 8 },
890 { "verbose", no_argument, &opt_verbose, 1 },
891 { NULL, 0, NULL, 0 }
892 };
893
894 int c;
895 while ((c = getopt_long (argc, argv, "+dDf:", o, NULL)) != -1)
896 switch (c) {
897 case 0:
898 break;
899 case 'd':
900 opt_d = true;
901 break;
902 case 'D':
903 opt_d = false;
904 break;
905 case 'f':
906 config_file = optarg;
907 break;
908 default:
909 usage ();
910 break;
911 }
912
913 switch (mode) {
914 case 1:
915 warnx << progname << " (Mail Avenger) " << VERSION
916 << compile_options () << "\n"
917 << "Copyright (C) 2004-2007 David Mazieres\n"
918 << "This program comes with NO WARRANTY,"
919 << " to the extent permitted by law.\n"
920 << "You may redistribute it under the terms of"
921 << " the GNU General Public License;\n"
922 << "see the file named COPYING for details.\n";
923 return 0;
924 case 2:
925 usage ();
926 #if USE_SYNFP
927 case 5:
928 find_avenger ();
929 synfp_test (argc - optind, argv + optind);
930 amain ();
931 #endif /* USE_SYNFP */
932 case 6:
933 netpath_test (argc - optind, argv + optind);
934 amain ();
935 }
936
937 if (!parseconfig (opt, config_file))
938 fatal ("error parsing asmtpd.conf file\n");
939 syslog_priority = opt->logpriority;
940
941 if (mode == 7) {
942 find_avenger ();
943 avenge (argc - optind, argv + optind);
944 amain ();
945 }
946
947 if (optind != argc)
948 usage ();
949
950 switch (mode) {
951 case 0:
952 find_avenger ();
953 smtpstart ();
954 break;
955 case 3:
956 aout << "SPF test mode; enter: <IP address> <from address>"
957 " [<helo host>]\n";
958 ain->readline (wrap (spftst));
959 amain ();
960 break;
961 case 4:
962 aout << "RBL test mode; enter {<IP address> | <from address>}\n";
963 ain->readline (wrap (rbltst));
964 amain ();
965 break;
966 case 8:
967 {
968 parsed_resolv_conf prc;
969 parse_resolv_conf (&prc, "/etc/resolv.conf");
970 show_resolv_conf (prc);
971 return 0;
972 }
973 default:
974 usage ();
975 return 1;
976 }
977
978 amain ();
979 }
980
981