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