1 /* $Id$ */
2 
3 /*
4  *
5  * Copyright (C) 2003 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 
25 #include "asmtpd.h"
26 #include "rawnet.h"
27 
28 ihash<const uid_t, avcount, &avcount::uid, &avcount::link> avctab;
29 
avcount(uid_t u)30 avcount::avcount (uid_t u)
31   : uid (u), num (0), release_lock (false)
32 {
33   avctab.insert (this);
34 }
35 
~avcount()36 avcount::~avcount ()
37 {
38   avctab.remove (this);
39 }
40 
41 void
release()42 avcount::release ()
43 {
44   assert (num > 0);
45   num--;
46   if (release_lock)
47     return;
48   release_lock = true;
49 
50   while (num < int (opt->avenger_max_per_user) && !waiters.empty ())
51     (*waiters.pop_front ()) ();
52 
53   if (num)
54     release_lock = false;
55   else
56     delete this;
57 }
58 
59 bool
acquire()60 avcount::acquire ()
61 {
62   if (num >= int (opt->avenger_max_per_user))
63     return false;
64   num++;
65   return true;
66 }
67 
68 avcount *
get(uid_t u)69 avcount::get (uid_t u)
70 {
71   if (avcount *avc = avctab[u])
72     return avc;
73   return New avcount (u);
74 }
75 
avif(const smtpd * s,str n,avcount * avc,pid_t p,int fd,cb_t c)76 avif::avif (const smtpd *s, str n, avcount *avc, pid_t p, int fd, cb_t c)
77   : cb (c), smtp (s), pid (p), aio (aios::alloc (fd)), name (n), avc (avc)
78 {
79   if (opt->debug_avenger)
80     aio->setdebug (strbuf ("%s (a%d)", name.cstr (), fd));
81 }
82 
83 void
init()84 avif::init ()
85 {
86   chldcb (pid, wrap (this, &avif::reap));
87   aio->readline (wrap (this, &avif::input));
88 }
89 
~avif()90 avif::~avif ()
91 {
92   aio->readcancel ();
93   aio->abort ();
94   if (pid > 0) {
95     chldcb (pid, NULL);
96     if (killpg (pid, SIGKILL) < 0)
97       kill (pid, SIGKILL);
98   }
99   if (avc)
100     avc->release ();
101   while (result *rp = reslist.first)
102     delres (rp);
103 }
104 
105 void
input(str line,int err)106 avif::input (str line, int err)
107 {
108   if (!line || strlen (line) != line.len ()) {
109     (*cb) (NEXT, NULL);		// By default accept mail
110     delete this;
111     return;
112   }
113 
114   static rxx spfrx ("^spf([01])?\\s+(\\w+)\\s+(.*)$");
115   static rxx dnsarx ("^dns-a\\s+(\\w+)\\s+(\\S+)$");
116   static rxx dnsptrrx ("^dns-ptr\\s+(\\w+)\\s+(\\S+)$");
117   static rxx dnsmxrx ("^dns-mx\\s+(\\w+)\\s+(\\S+)$");
118   static rxx dnstxtrx ("^dns-txt\\s+(\\w+)\\s+(\\S+)$");
119   static rxx netpathrx ("^netpath\\s+(\\w+)\\s+(\\S+)(\\s+(-?\\d+))?$");
120   static rxx retrx ("^return\\s+(([245]\\d\\d)([ -]).*)$");
121   static rxx retcontrx ("^(([245]\\d\\d)([ -]).*)$");
122   static rxx redirrx ("^redirect\\s+(\\S+)$");
123   static rxx bodytestrx ("^bodytest\\s+(\\S.*)$");
124 
125   if (retcode) {
126     if (!retcontrx.match (line) || retcode != retcontrx[2]) {
127       badinput (line);
128       return;
129     }
130     retbuf << retcontrx[1] << "\r\n";
131     if (retcontrx[3] != "-") {
132       (*cb) (DONE, retbuf);
133       delete this;
134       return;
135     }
136   }
137   else if (!line.len ())
138     ;
139   else if (line[0] == '.')
140     newres ()->res = line << "\n";
141   else if (spfrx.match (line)) {
142     str from = smtp->get_from ();
143     if (!from || !from.len ())
144       from = smtp->get_helo ();
145     spf_t *spf = New spf_t (smtp->get_addr (), from);
146     spf->spfrec = spfrx[3];
147     spf->helo = smtp->get_helo ();
148     spf->ptr_cache = smtp->ptr_cache;
149     result *rp = newres ();
150     spf->cb = wrap (this, &avif::spf_cb, spfrx[2], rp,
151 		    spfrx[1] && spfrx[1][0] == '1');
152     rp->abortcb = wrap (spf_cancel, spf);
153     spf->init ();
154   }
155   else if (dnsarx.match (line)) {
156     result *rp = newres ();
157     if (dnsreq *rqp
158 	= dns_hostbyname (dnsarx[2],
159 			  wrap (this, &avif::dns_a_cb, dnsarx[1], rp),
160 			  false, false))
161       rp->abortcb = wrap (dnsreq_cancel, rqp);
162   }
163   else if (dnsptrrx.match (line)) {
164     str var = dnsptrrx[1];
165     str name = dnsptrrx[2];
166     result *rp = newres ();
167     in_addr a;
168     int r = inet_aton (name, &a);
169     if (r == 0)
170       rp->res = var << "=\n";
171     else if (r < 0)
172       rp->res = "";
173     else if (dnsreq *rqp
174 	     = dns_hostbyaddr (a, wrap (this, &avif::dns_ptr_cb, var, rp)))
175       rp->abortcb = wrap (dnsreq_cancel, rqp);
176   }
177   else if (dnsmxrx.match (line)) {
178     result *rp = newres ();
179     if (dnsreq *rqp
180 	= dns_mxbyname (dnsmxrx[2],
181 			wrap (this, &avif::dns_mx_cb, dnsmxrx[1], rp),
182 			false))
183       rp->abortcb = wrap (dnsreq_cancel, rqp);
184   }
185   else if (dnstxtrx.match (line)) {
186     result *rp = newres ();
187     if (dnsreq *rqp
188 	= dns_txtbyname (dnstxtrx[2],
189 			wrap (this, &avif::dns_txt_cb, dnstxtrx[1], rp),
190 			false))
191       rp->abortcb = wrap (dnsreq_cancel, rqp);
192   }
193   else if (netpathrx.match (line)) {
194     result *rp = newres ();
195     int hops = 0;
196     if (str h = netpathrx[4])
197       convertint (h, &hops);
198     if (dnsreq *rqp = dns_hostbyname (netpathrx[2],
199 				     wrap (this, &avif::netpath_cb1,
200 					   netpathrx[1], hops, rp),
201 				     true, true))
202       rp->abortcb = wrap (dnsreq_cancel, rqp);
203   }
204   else if (retrx.match (line)) {
205     str code (retrx[2]);
206     if (retcode && retcode != code) {
207       badinput (line);
208       return;
209     }
210     retcode = code;
211     retbuf << retrx[1] << "\r\n";
212     if (retrx[3] != "-") {
213       (*cb) (DONE, retbuf);
214       delete this;
215       return;
216     }
217   }
218   else if (redirrx.match (line)) {
219     (*cb) (REDIR, redirrx[1]);
220     delete this;
221     return;
222   }
223   else if (bodytestrx.match (line)) {
224     (*cb) (BODY, bodytestrx[1]);
225     delete this;
226     return;
227   }
228   else {
229     badinput (line);
230     return;
231   }
232 
233   aio->readline (wrap (this, &avif::input));
234   maybe_reply ();
235 }
236 
237 str
safestring(str msg)238 safestring (str msg)
239 {
240   /* Who knows what a bad user might accomplish by sending weird
241    * control characters... */
242   strbuf sb;
243   for (u_int i = 0; i < msg.len (); i++) {
244     u_char c = msg[i];
245     if (c == 0x7f)
246       sb.tosuio ()->copy ("^?", 2);
247     else if (c >= ' ')
248       sb.tosuio ()->copy (&c, 1);
249     else {
250       sb.tosuio ()->copy ("^", 1);
251       c = c + '@';
252       sb.tosuio ()->copy (&c, 1);
253     }
254   }
255   return sb;
256 }
257 void
badinput(str line)258 avif::badinput (str line)
259 {
260   warn ("bad input from %s's %s: ", name.cstr (), AVENGER)
261     << safestring (line) << "\n";
262   (*cb) (NEXT, NULL);
263   delete this;
264   return;
265 }
266 
267 void
spf_cb(str var,result * rp,bool one,spf_t * spf)268 avif::spf_cb (str var, result *rp, bool one, spf_t *spf)
269 {
270   if (one)
271     rp->res = var << "=" << spf1_print (spf->result) << "\n";
272   else
273     rp->res = var << "=" << spf_print (spf->result) << "\n";
274   maybe_reply ();
275 }
276 
277 void
dns_a_cb(str var,result * rp,ptr<hostent> h,int err)278 avif::dns_a_cb (str var, result *rp, ptr<hostent> h, int err)
279 {
280   strbuf sb;
281   if (h) {
282     char **ap = h->h_addr_list;
283     sb << var << "=" << str (inet_ntoa (*(in_addr *) *ap));
284     while (*++ap)
285       sb << " " << str (inet_ntoa (*(in_addr *) *ap));
286     sb << "\n";
287   }
288   else if (!dns_tmperr (err))
289     sb << var << "=\n";
290 
291   rp->res = sb;
292   maybe_reply ();
293 }
294 
295 void
dns_ptr_cb(str var,result * rp,ptr<hostent> h,int err)296 avif::dns_ptr_cb (str var, result *rp, ptr<hostent> h, int err)
297 {
298   strbuf sb;
299   if (h) {
300     sb << var << "=" << h->h_name;
301     for (char **np = h->h_aliases; *np; np++)
302       sb << " " << *np;
303     sb << "\n";
304   }
305   else if (!dns_tmperr (err))
306     sb << var << "=\n";
307 
308   rp->res = sb;
309   maybe_reply ();
310 }
311 
312 void
dns_mx_cb(str var,result * rp,ptr<mxlist> mxl,int err)313 avif::dns_mx_cb (str var, result *rp, ptr<mxlist> mxl, int err)
314 {
315   strbuf sb;
316   if (mxl) {
317     sb << var << "=";
318     sb << int (mxl->m_mxes[0].pref) << ":" << mxl->m_mxes[0].name;
319     for (u_int i = 1; i < mxl->m_nmx; i++)
320       sb << " " << int (mxl->m_mxes[i].pref) << ":" << mxl->m_mxes[i].name;
321     sb << "\n";
322   }
323   else if (!dns_tmperr (err))
324     sb << var << "=\n";
325 
326   rp->res = sb;
327   maybe_reply ();
328 }
329 
330 void
dns_txt_cb(str var,result * rp,ptr<txtlist> t,int err)331 avif::dns_txt_cb (str var, result *rp, ptr<txtlist> t, int err)
332 {
333   strbuf sb;
334   if (t) {
335     /* XXX - This is bad if there are multiple TXT records.  moreover,
336      * if a TXT record contains a newline, this will cause maybe_reply
337      * to fail, and thus the variable will not get set. */
338     sb << var << "=" << t->t_txts[0] << "\n";
339   }
340   else if (!dns_tmperr (err))
341     sb << var << "=\n";
342   rp->res = sb;
343   maybe_reply ();
344 }
345 
346 void
netpath_cb1(str var,int hops,result * rp,ptr<hostent> h,int err)347 avif::netpath_cb1 (str var, int hops, result *rp, ptr<hostent> h, int err)
348 {
349   rp->abortcb = NULL;		// not needed
350   if (h) {
351     sockaddr_in sin;
352     sin.sin_family = AF_INET;
353     sin.sin_port = htons (0);
354     sin.sin_addr = *(in_addr *) h->h_addr;
355     if (traceroute *trp = netpath (&sin, hops,
356 				   wrap (this, &avif::netpath_cb2, var, rp)))
357       rp->abortcb = wrap (netpath_cancel, trp);
358   }
359   else {
360     rp->res = "";
361     maybe_reply ();
362   }
363 }
364 void
netpath_cb2(str var,result * rp,int nhops,in_addr * av,int an)365 avif::netpath_cb2 (str var, result *rp, int nhops, in_addr *av, int an)
366 {
367   strbuf sb;
368   if (an > 0) {
369     sb << var << "=" << nhops;
370     for (int i = 0; i < an; i++)
371       sb << " " << inet_ntoa (av[i]);
372     sb << "\n";
373   }
374   rp->res = sb;
375   maybe_reply ();
376 }
377 
378 void
maybe_reply()379 avif::maybe_reply ()
380 {
381   result *rp;
382   while ((rp = reslist.first) && rp->res) {
383     /* Sheer paranoia--what if some weird caracters come back in DNS
384      * requests (or something) and somehow don't get filtered by the
385      * resolver. */
386     if (strchr (rp->res, '\n') == rp->res.cstr () + rp->res.len () - 1
387 	&& !memchr (rp->res, '\0', rp->res.len ()))
388       aio << rp->res;
389     else if (rp->res.len ())
390       warn << "user " << name << " newline should be at end of variable\n";
391     delres (rp);
392   }
393 }
394 
395 /* Casting to void isn't enough to get rid of these warnings */
396 inline void
ignore_int(int)397 ignore_int (int)
398 {
399 }
400 
401 void
chldinit(struct passwd * pw,int fd,bool sys,str ext)402 avif::chldinit (struct passwd *pw, int fd, bool sys, str ext)
403 {
404   if (opt->avenger_timeout)
405     alarm (opt->avenger_timeout);
406 
407   bool root = getuid () <= 0;
408   str avdir;
409   if (sys)
410     avdir = pw->pw_dir;
411   else
412     avdir = strbuf () << pw->pw_dir << "/.avenger";
413 
414 #ifdef HAVE_SETEUID
415   if (!sys) {
416     /* quick optimization because setgroups is expensive */
417     GETGROUPS_T gid = pw->pw_gid;
418     ignore_int (setgid (gid));
419     if (root)
420       ignore_int (seteuid (pw->pw_uid));
421     struct stat sb;
422     if (!sys && lstat (avdir, &sb)) {
423       if (smtpd::tmperr (errno)) {
424 	aout << "return 451 " << avdir << ": " << strerror (errno) << "\n";
425 	aout->flush ();
426       }
427       _exit (0);
428     }
429     if (root)
430       ignore_int (seteuid (getuid ()));
431     if (!S_ISDIR (sb.st_mode) || (sb.st_uid && sb.st_uid != pw->pw_uid)) {
432       warn << avdir << " should be directory owned by " << pw->pw_name << "\n";
433       _exit (0);
434     }
435   }
436 #endif /* HAVE_SETEUID */
437 
438   if (sys)
439     setgroups (opt->av_groups.size (), opt->av_groups.base ());
440   become_user (pw, !sys);
441 
442   if (chdir (avdir) < 0) {
443     maybe_warn (strbuf ("%s: %m\n", avdir.cstr ()));
444     if (smtpd::tmperr (errno)) {
445       aout << "return 451 " << avdir << ": " << strerror (errno) << "\n";
446       aout->flush ();
447       _exit (0);
448     }
449     if (!sys)
450       _exit (0);
451   }
452 }
453 
454 void
alloc(struct passwd * pw,const smtpd * s,str recip,char mode,avcount * avc,str ext,str avuser,cb_t cb,str extraenv)455 avif::alloc (struct passwd *pw, const smtpd *s, str recip, char mode,
456 	     avcount *avc, str ext, str avuser, cb_t cb, str extraenv)
457 {
458   if (mode == 's') {
459     str path = strbuf () << opt->etcdir << "/" << ext;
460     if (access (path, 0) < 0 && errno == ENOENT) {
461       (*cb) (NEXT, NULL);
462       return;
463     }
464   }
465 
466   int fds[2];
467   if (socketpair (AF_UNIX, SOCK_STREAM, 0, fds) < 0) {
468     (*cb) (DONE, strbuf ("451 %m\r\n"));
469     return;
470   }
471 
472   close_on_exec (fds[0]);
473   if (fds[1] > 1)
474     close_on_exec (fds[1]);
475 
476   const char *av[] = { path_avenger, NULL, NULL, NULL };
477   str modestr;
478   if (mode) {
479     av[1] = modestr = strbuf ("-%c", mode);
480     av[2] = ext;
481   }
482   else
483     av[1] = ext;
484 
485   vec<str> senv;
486   s->envinit (&senv, pw);
487 
488   if (!strncmp (senv[0], "PWD=", 4))
489     senv.pop_front ();
490   if (mode == 's')
491     senv.push_back (strbuf ("PWD=%s", pw->pw_dir));
492   else
493     senv.push_back (strbuf ("PWD=%s/.avenger", pw->pw_dir));
494   senv.push_back (strbuf ("RECIPIENT=") << recip);
495   senv.push_back (strbuf ("RECIPIENT_HOST=")
496 		  << mytolower (extract_domain (recip)));
497   senv.push_back (strbuf ("RECIPIENT_LOCAL=")
498 		  << mytolower (extract_local (recip)));
499   if (ext && mode != 's')
500     senv.push_back (strbuf ("EXT=") << ext);
501   if (extraenv)
502     senv.push_back (extraenv);
503   if (avuser)
504     senv.push_back (strbuf () << "AVUSER=" << avuser);
505 
506   vec<const char *> env;
507   env.reserve (senv.size () + 1);
508   for (const str *sp = senv.base (); sp < senv.lim (); sp++)
509     env.push_back (sp->cstr ());
510   env.push_back (NULL);
511 
512   pid_t pid = aspawn (path_avenger, av, fds[1], fds[1], errfd,
513 		      wrap (&chldinit, pw, fds[1], mode == 's', ext),
514 		      const_cast<char **> (env.base ()));
515   if (pid <= 0) {
516     (*cb) (DONE, strbuf ("451 %m\r\n"));
517     return;
518   }
519 
520   str name;
521   if (mode == 's' && ext)
522     name = strbuf ("%c", opt->separator ? opt->separator : ' ') << ext;
523   else if (ext)
524     name = strbuf ("%s%c", pw->pw_name, opt->separator) << ext;
525   else
526     name = pw->pw_name;
527 
528   close (fds[1]);
529   (New avif (s, name, avc, pid, fds[0], cb))->init ();
530 }
531 
532 void
reap(int status)533 avif::reap (int status)
534 {
535   pid = -1;
536   if (status)
537     warn << AVENGER " for " << name << " exited with "
538 	 << exitstr (status) << "\n";
539 }
540