commands/vdeliver.cc
changeset 0 6f7a81934006
child 2 b3afb9f1e801
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/commands/vdeliver.cc	Wed Jan 16 22:39:43 2008 +0100
@@ -0,0 +1,349 @@
+// Copyright (C) 1999,2000 Bruce Guenter <bruceg@em.ca>
+//
+// 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 of the License, 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 <config.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include "ac/time.h"
+#include "ac/wait.h"
+#include <signal.h>
+#include "fdbuf/fdbuf.h"
+#include "cli/cli.h"
+#include "vcommand.h"
+#include "misc/itoa.h"
+#include "misc/stat_fns.h"
+#include "misc/exec.h"
+
+const char* cli_program = "vdeliver";
+const char* cli_help_prefix = "VMailMgr delivery agent\n";
+const char* cli_help_suffix = "";
+const char* cli_args_usage = "";
+const int cli_args_min = 0;
+const int cli_args_max = 0;
+static int addufline = false;
+static int addrpline = true;
+static int adddtline = true;
+static int o_quiet = false;
+
+// vdeliver is the unified e-mail message delivery agent for virtual
+// domains managed by vmailmgr.
+// It is run from the F<.qmail-default> file, and automatically handles
+// delivery to any user within a virtual domain.
+
+cli_option cli_options[] = {
+  { 'D', 0, cli_option::flag, true, &adddtline,
+    "Add a \"Delivered-To:\" line (default)", 0 },
+  // Add the C<Return-Path:> line to the top of the message. (default)
+  { 'F', 0, cli_option::flag, true, &addufline,
+    "Add a \"From \" mailbox line", 0 },
+  { 'R', 0, cli_option::flag, true, &addrpline,
+    "Add a \"Return-Path:\" line (default)", 0 },
+  { 'd', 0, cli_option::flag, false, &adddtline,
+    "Do not add the \"Delivered-To:\" line", 0 },
+  //Do not add the C<Delivered-To:> line to the top of the message.
+  { 'f', 0, cli_option::flag, false, &addufline,
+    "Do not add the \"From \" mailbox line (default)", 0 },
+  // Do not add the C<From> mailbox line to the top of the message.
+  // Note that this line is never added when the message is being
+  // re-injected into the mail stream. (default)
+  { 0, "quiet", cli_option::flag, true, &o_quiet,
+    "Suppress all status messages", 0 },
+  { 'r', 0, cli_option::flag, false, &addrpline,
+    "Do not add the \"Return-Path:\" line", 0 },
+  // Do not add the C<Return-Path:> line to the top of the message.
+  {0}
+};
+
+// RETURN VALUE
+//
+// Returns 0 if delivery was successful,
+// 100 if a fatal error occurred,
+// or 111 if a temporary error occurred.
+
+// ENVIRONMENT
+//
+// F<vdeliver> expects to be run by F<qmail-local> as it requires several
+// of the environment variables that it sets.
+// See the I<qmail-command>(8) manual page for full details on these
+// variables.
+// In particular, it requires C<DTLINE>, C<EXT>, C<HOST>, C<RPLINE>,
+// C<SENDER>, C<UFLINE>, and C<USER>.
+
+// SEE ALSO
+//
+// checkvpw(1), I<qmail-command>(8)
+
+#ifndef HAVE_GETHOSTNAME
+int gethostname(char *name, size_t len);
+#endif
+
+const char* make_hostname()
+{
+  static char buf[512];
+  gethostname(buf, 511);
+  return buf;
+}
+
+void exit_msg(const char* msg, int code)
+{
+  if(!o_quiet)
+    fout << "vdeliver: " << msg << endl;
+  exit(code);
+}
+
+void exit_msg(const char* msg1, const mystring& msg2, int code)
+{
+  if(!o_quiet)
+    fout << "vdeliver: " << msg1 << msg2 << endl;
+  exit(code);
+}
+
+void die_fail(const char* msg) { exit_msg(msg, 100); }
+void die_temp(const char* msg) { exit_msg(msg, 111); }
+
+void fail_quota()
+{
+  die_fail("Delivery failed due to system quota violation");
+}
+
+mystring read_me()
+{
+  static mystring me;
+  static mystring mefilename = config->qmail_root() + "control/me";
+  if(!me) {
+    fdibuf in(mefilename.c_str());
+    if(in)
+      in.getline(me);
+  }
+  if(!me)
+    die_temp("control/me is empty!");
+  return me;
+}
+
+char* ufline;
+char* rpline;
+char* dtline;
+
+bool dump(fdobuf& out, bool dosync)
+{
+  if((ufline && !out.write(ufline, strlen(ufline))) ||
+     (rpline && !out.write(rpline, strlen(rpline))) ||
+     (dtline && !out.write(dtline, strlen(dtline))) ||
+     !fin.rewind() ||
+     !fdbuf_copy(fin, out)) {
+    out.close();
+    return false;
+  }
+  if(dosync && !out.sync())
+    return false;
+  if(!out.close())
+    return false;
+  return true;
+}
+
+static mystring partname;
+static mystring maildir;
+
+void deliver_partial()
+{
+  const mystring newdir = maildir + "/new";
+  const mystring tmpdir = maildir + "/tmp";
+
+  if(!is_dir(tmpdir.c_str()) || !is_dir(newdir.c_str()))
+    die_temp("Destination directory does not appear to be a maildir.");
+
+  const mystring hostname = make_hostname();
+  pid_t pid = getpid();
+  for(;; sleep(2)) {
+    partname = "/" + mystring(itoa(time(0))) + "." + itoa(pid)
+      + "." + hostname;
+    
+    mystring newfile = newdir + partname;
+    mystring tmpfile = tmpdir + partname;
+
+    if(is_exist(tmpfile.c_str()))
+      continue;
+    else {
+      fdobuf out(tmpfile.c_str(), fdobuf::create | fdobuf::excl, 0600);
+      if(!out) {
+#ifdef EDQUOT
+	if(out.error_number() == EDQUOT)
+	  fail_quota();
+#endif
+	continue;
+      }
+      if(!dump(out, true)) {
+#ifdef EDQUOT
+	if(out.error_number() == EDQUOT)
+	  fail_quota();
+#endif
+	die_temp("Error writing the output file.");
+      }
+      return;
+    }
+  }
+}
+
+void deliver_fail(const char* msg)
+{
+  mystring tmpfile = maildir + "/tmp/" + partname;
+  unlink(tmpfile.c_str());
+  die_temp(msg);
+}
+
+void deliver_final()
+{
+  mystring tmpfile = maildir + "/tmp/" + partname;
+  mystring newfile = maildir + "/new/" + partname;
+  
+  if(link(tmpfile.c_str(), newfile.c_str()))
+    deliver_fail("Error linking the temp file to the new file.");
+  if(unlink(tmpfile.c_str()))
+    deliver_fail("Error unlinking the temp file.");
+}
+
+void write_envelope(fdobuf& out,
+		    mystring sender, mystring recipient, mystring host)
+{
+  out << 'F' << sender << '\0';
+
+  for(mystring_iter iter = recipient; iter; ++iter) {
+    mystring r = *iter;
+    int at = r.find_first('@');
+    out << 'T' << r;
+
+    // If the address has no '@', add the virtual domain
+    if(at < 0)
+      out << '@' << host;
+    // If it has an '@', but no domain, add the local domain
+    else if((unsigned)at == r.length()-1)
+      out << read_me();
+    // Else, nothing to add, address already copied
+    out << '\0';
+  }
+  out << '\0';
+}
+
+void inject(mystring sender, mystring recip, mystring host)
+{
+  int pipe1[2];
+  int pipe2[2];
+  if(pipe(pipe1) || pipe(pipe2))
+    deliver_fail("System call to 'pipe' failed.");
+
+  mystring qq = config->qmail_root() + "bin/qmail-queue";
+  pid_t pid;
+  switch(pid = fork()) {
+  case -1:
+    deliver_fail("System call to 'fork' failed.");
+  case 0:
+    close(pipe1[1]);
+    close(pipe2[1]);
+    if((dup2(pipe1[0], 0) != 0) || (dup2(pipe2[0], 1) != 1))
+      exit(111);
+    execl(qq.c_str(), qq.c_str(), 0);
+    die_temp("Exec of qmail-queue failed.");
+  default:
+    close(pipe1[0]);
+    close(pipe2[0]);
+    signal(SIGPIPE, SIG_IGN);
+    ufline = 0;
+    rpline = 0;
+    fdobuf out(pipe1[1], true);
+    if(!dump(out, false))
+      deliver_fail("Error writing to pipe");
+    fdobuf env(pipe2[1], true);
+    write_envelope(env, sender, recip, host);
+    if(!env.flush() || !env.close())
+      deliver_fail("Error sending envelope to pipe");
+    int status;
+    if(waitpid(pid, &status, WUNTRACED) != pid)
+      deliver_fail("System call to 'waitpid' failed.");
+    if(!WIFEXITED(status))
+      deliver_fail("qmail-queue crashed!");
+    if(WEXITSTATUS(status))
+      deliver_fail("qmail-queue exited with an error!");
+  }
+}
+
+void enqueue(mystring recipient, mystring host, mystring sender)
+{
+  int f = sender.find_first('@');
+  if(f > 0) {
+    presetenv("QMAILUSER=", sender.left(f));
+    presetenv("QMAILHOST=", sender.right(f+1));
+  }
+  inject(sender, recipient, host);
+}
+
+int cli_main(int, char*[])
+{
+  if(!go_home())
+    return 1;
+  
+#define ENV_VAR(VAR,ENV) const char* tmp__##VAR = getenv(#ENV); if(!tmp__##VAR) die_fail(#ENV " is not set"); mystring VAR = tmp__##VAR;
+  ENV_VAR(user, USER);
+  ENV_VAR(ext, EXT);
+  ENV_VAR(host, HOST);
+  ENV_VAR(sender, SENDER);
+#undef ENV_VAR
+#define ENV_VAR(VAR,ENV) VAR = getenv(#ENV); if(!VAR) die_fail(#ENV " is not set");
+  ENV_VAR(ufline, UFLINE);
+  ENV_VAR(rpline, RPLINE);
+  ENV_VAR(dtline, DTLINE);
+#undef ENV_VAR
+
+  if(!addufline)
+    ufline = 0;
+  if(!addrpline)
+    rpline = 0;
+  if(!adddtline)
+    dtline = 0;
+
+  vpwentry* vpw = domain.lookup(ext, false);
+  if(!vpw)
+    die_fail(mystring("Invalid or unknown virtual user '" + ext + "'").c_str());
+  if(vpw->expiry < (unsigned)time(0))
+    die_fail(mystring("Virtual user '" + ext + "' has expired").c_str());
+  
+  vpw->export_env();
+  bool enabled = vpw->is_mailbox_enabled && !!vpw->mailbox;
+
+  int r = execute("vdeliver-predeliver");
+  if(r)
+    exit_msg("Execution of vdeliver-predeliver failed", r);
+
+  if(enabled) {
+    maildir = vpw->mailbox;
+    deliver_partial();
+  }
+  if(!!vpw->forwards)
+    enqueue(vpw->forwards, host, sender);
+  if(enabled)
+    deliver_final();
+
+  if(!fin.rewind()) {
+    if(!o_quiet)
+      fout << "Could not re-rewind standard input" << endl;
+  }
+  else if(execute("vdeliver-postdeliver"))
+    if(!o_quiet)
+      fout << "Execution of vdeliver-postdeliver failed" << endl;
+
+  return 0;
+}