/* $Id: enqmsg.C,v 1.7 2006/02/13 02:53:53 dm Exp $ */ /* * * Copyright (C) 2004 David Mazieres (dm@uun.org) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA * */ #include "asmtpd.h" #include str enqmsg_file::spooldir; vec enqmsg_file::mini_env; str enqmsg_file::get_spooldir () { static uid_t uid; static time_t lastcheck; struct stat sb; if (!spooldir) uid = getuid (); else if (timenow <= lastcheck + 10) return spooldir; else if (!lstat (spooldir, &sb) && S_ISDIR (sb.st_mode) && sb.st_uid == uid) { lastcheck = timenow; return spooldir; } char *p = getenv ("TMPDIR"); if (!p) p = "/var/tmp"; mstr m (strlen (p) + progname.len () + 12); #if HAVE_MKDTEMP strcpy (m, p); strcat (m, "/"); strcat (m, progname); strcat (m, "XXXXXXXXXX"); errno = 0; if (!mkdtemp (m.cstr ())) fatal ("mkdtemp %s failed\n", m.cstr ()); #else /* !HAVE_MKDTEMP */ do { strcpy (m, p); strcat (m, "/"); strcat (m, progname); strcat (m, "XXXXXXXXXX"); errno = 0; if (!mktemp (m.cstr ())) fatal ("mktemp %s failed\n", m.cstr ()); errno = 0; } while (mkdir (m.cstr (), 0700) && errno == EEXIST); if (errno) fatal ("could not create spool directory in %s: %m\n", p); #endif /* !HAVE_MKDTEMP */ m.setlen (strlen (m.cstr ())); spooldir = m; lastcheck = timenow; return spooldir; } inline void preserve (vec *e, const char *var) { if (const char *p = getenv (var)) { str s (strbuf ("%s=%s", var, p)); e->push_back (xstrdup (s.cstr ())); } } void enqmsg_file::init_mini_env () { while (!mini_env.empty ()) xfree ((void *) mini_env.pop_front ()); preserve (&mini_env, "PATH"); preserve (&mini_env, "PWD"); preserve (&mini_env, "TZ"); preserve (&mini_env, "TMPDIR"); preserve (&mini_env, "DMALLOC_OPTIONS"); preserve (&mini_env, "STKTRACE"); mini_env.push_back (xstrdup ("USER=unknown-user")); mini_env.push_back (NULL); } enqmsg_file::enqmsg_file () : fd (-1), efd (-1), error (false), eof (false), rps (NULL) { } enqmsg_file::~enqmsg_file () { if (fd >= 0) close (fd); if (path) unlink (path); if (rps) runprog_cancel (rps); } bool enqmsg_file::init (str f, const vec &to, str rcvd_line) { assert (fd < 0 && !error && !eof && !path); str tmplate (get_spooldir () << "/msg.XXXXXXXXXX"); mstr tpath (tmplate.len ()); memcpy (tpath.cstr (), tmplate, tmplate.len () + 1); umask (077); fd = mkstemp (tpath); if (fd < 0) return false; path = tpath; close_on_exec (fd); errno = 0; if (write (fd, rcvd_line, rcvd_line.len ()) != implicit_cast (rcvd_line.len ())) { if (errno) warn ("%s: %m\n", path.cstr ()); close (fd); error = true; fd = -1; return false; } #if 0 unlink (path); path = NULL; #endif from = f; top = &to; return true; } void enqmsg_file::writev (suio *uiop, cbs cb) { assert (!eof); errno = 0; uiop->output (fd); if (!uiop->resid ()) { (*cb) (NULL); return; } eof = true; if (errno) (*cb) (strbuf ("451 %m\r\n")); else (*cb) (strbuf ("451 local disk write error\r\n")); } int enqmsg_file::getfd () { eof = true; if (fd >= 0 && lseek (fd, 0, SEEK_SET) != -1) return fd; return -1; } void enqmsg_file::commit (cbs cb) { assert (fd >= 0 && !error && efd < 0 && !rps); eof = true; if (lseek (fd, 0, SEEK_SET) == -1) { (*cb) (strbuf ("451 %m\r\n")); return; } if (!opt->sendmailfromline) { char buf[8192]; int n = read (fd, buf, sizeof (buf)); if (n >= 5 && !strncmp (buf, "From ", 5)) { for (;;) { char *p = static_cast (memchr (buf, '\n', n)); if (p) { if (lseek (fd, p - buf + 1 - n, SEEK_CUR) == -1) { (*cb) (strbuf ("451 %m\r\n")); return; } break; } if ((n = read (fd, buf, sizeof (buf))) < 0) { (*cb) (strbuf ("451 %m\r\n")); return; } } } else if (lseek (fd, 0, SEEK_SET) == -1) { (*cb) (strbuf ("451 %m\r\n")); return; } } vec av; for (const str *sp = opt->sendmail.base (); sp < opt->sendmail.lim (); sp++) av.push_back (*sp); av.push_back ("-f"); if (from.len ()) av.push_back (from); else av.push_back (opt->emptysender); av.push_back ("--"); for (const str *sp = top->base (); sp < top->lim (); sp++) av.push_back (*sp); av.push_back (NULL); if (mini_env.empty ()) init_mini_env (); rps = runprog (av[0], av.base (), fd, true, wrap (this, &enqmsg_file::smcb, av[0], cb), (opt->sendmailpriv ? NULL : wrap (become_user, opt->av_user, false)), mini_env.base ()); } void enqmsg_file::smcb (str sm, cbs cb, ref po) { rps = NULL; if (!po->status) { (*cb) (NULL); return; } const char *smbn = strrchr (sm, '/'); if (smbn) smbn++; else smbn = sm; for (u_int i = 0; i < 5 && i < po->output.size (); i++) warn << smbn << ": " << po->output[i] << "\n"; warn << sm << ": exited with " << exitstr (po->status) << "\n"; if (str resp = po->response (451)) (*cb) (resp); else (*cb) (strbuf () << "451 " << smbn << " failed\r\n"); } EXITFN (cleanup); static void cleanup () { if (!enqmsg_file::spooldir) return; int olddir = open (".", O_RDONLY); if (chdir (enqmsg_file::spooldir)) { warn ("%s: %m\n", enqmsg_file::spooldir.cstr ()); return; } DIR *dir = opendir ("."); if (!dir) { warn ("%s: %m\n", enqmsg_file::spooldir.cstr ()); fchdir (olddir); close (olddir); return; } while (struct dirent *de = readdir (dir)) if (!strncmp (de->d_name, "msg", 3)) unlink (de->d_name); closedir (dir); if (fchdir (olddir) < 0) chdir ("/"); close (olddir); if (rmdir (enqmsg_file::spooldir)) warn ("%s: %m\n", enqmsg_file::spooldir.cstr ()); enqmsg_file::spooldir = NULL; }