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