--- /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;
+}