commands/vdeliver.cc
changeset 0 6f7a81934006
child 2 b3afb9f1e801
equal deleted inserted replaced
-1:000000000000 0:6f7a81934006
       
     1 // Copyright (C) 1999,2000 Bruce Guenter <bruceg@em.ca>
       
     2 //
       
     3 // This program is free software; you can redistribute it and/or modify
       
     4 // it under the terms of the GNU General Public License as published by
       
     5 // the Free Software Foundation; either version 2 of the License, or
       
     6 // (at your option) any later version.
       
     7 //
       
     8 // This program is distributed in the hope that it will be useful,
       
     9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       
    11 // GNU General Public License for more details.
       
    12 //
       
    13 // You should have received a copy of the GNU General Public License
       
    14 // along with this program; if not, write to the Free Software
       
    15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
       
    16 
       
    17 #include <config.h>
       
    18 #include <errno.h>
       
    19 #include <stdlib.h>
       
    20 #include <unistd.h>
       
    21 #include <sys/stat.h>
       
    22 #include "ac/time.h"
       
    23 #include "ac/wait.h"
       
    24 #include <signal.h>
       
    25 #include "fdbuf/fdbuf.h"
       
    26 #include "cli/cli.h"
       
    27 #include "vcommand.h"
       
    28 #include "misc/itoa.h"
       
    29 #include "misc/stat_fns.h"
       
    30 #include "misc/exec.h"
       
    31 
       
    32 const char* cli_program = "vdeliver";
       
    33 const char* cli_help_prefix = "VMailMgr delivery agent\n";
       
    34 const char* cli_help_suffix = "";
       
    35 const char* cli_args_usage = "";
       
    36 const int cli_args_min = 0;
       
    37 const int cli_args_max = 0;
       
    38 static int addufline = false;
       
    39 static int addrpline = true;
       
    40 static int adddtline = true;
       
    41 static int o_quiet = false;
       
    42 
       
    43 // vdeliver is the unified e-mail message delivery agent for virtual
       
    44 // domains managed by vmailmgr.
       
    45 // It is run from the F<.qmail-default> file, and automatically handles
       
    46 // delivery to any user within a virtual domain.
       
    47 
       
    48 cli_option cli_options[] = {
       
    49   { 'D', 0, cli_option::flag, true, &adddtline,
       
    50     "Add a \"Delivered-To:\" line (default)", 0 },
       
    51   // Add the C<Return-Path:> line to the top of the message. (default)
       
    52   { 'F', 0, cli_option::flag, true, &addufline,
       
    53     "Add a \"From \" mailbox line", 0 },
       
    54   { 'R', 0, cli_option::flag, true, &addrpline,
       
    55     "Add a \"Return-Path:\" line (default)", 0 },
       
    56   { 'd', 0, cli_option::flag, false, &adddtline,
       
    57     "Do not add the \"Delivered-To:\" line", 0 },
       
    58   //Do not add the C<Delivered-To:> line to the top of the message.
       
    59   { 'f', 0, cli_option::flag, false, &addufline,
       
    60     "Do not add the \"From \" mailbox line (default)", 0 },
       
    61   // Do not add the C<From> mailbox line to the top of the message.
       
    62   // Note that this line is never added when the message is being
       
    63   // re-injected into the mail stream. (default)
       
    64   { 0, "quiet", cli_option::flag, true, &o_quiet,
       
    65     "Suppress all status messages", 0 },
       
    66   { 'r', 0, cli_option::flag, false, &addrpline,
       
    67     "Do not add the \"Return-Path:\" line", 0 },
       
    68   // Do not add the C<Return-Path:> line to the top of the message.
       
    69   {0}
       
    70 };
       
    71 
       
    72 // RETURN VALUE
       
    73 //
       
    74 // Returns 0 if delivery was successful,
       
    75 // 100 if a fatal error occurred,
       
    76 // or 111 if a temporary error occurred.
       
    77 
       
    78 // ENVIRONMENT
       
    79 //
       
    80 // F<vdeliver> expects to be run by F<qmail-local> as it requires several
       
    81 // of the environment variables that it sets.
       
    82 // See the I<qmail-command>(8) manual page for full details on these
       
    83 // variables.
       
    84 // In particular, it requires C<DTLINE>, C<EXT>, C<HOST>, C<RPLINE>,
       
    85 // C<SENDER>, C<UFLINE>, and C<USER>.
       
    86 
       
    87 // SEE ALSO
       
    88 //
       
    89 // checkvpw(1), I<qmail-command>(8)
       
    90 
       
    91 #ifndef HAVE_GETHOSTNAME
       
    92 int gethostname(char *name, size_t len);
       
    93 #endif
       
    94 
       
    95 const char* make_hostname()
       
    96 {
       
    97   static char buf[512];
       
    98   gethostname(buf, 511);
       
    99   return buf;
       
   100 }
       
   101 
       
   102 void exit_msg(const char* msg, int code)
       
   103 {
       
   104   if(!o_quiet)
       
   105     fout << "vdeliver: " << msg << endl;
       
   106   exit(code);
       
   107 }
       
   108 
       
   109 void exit_msg(const char* msg1, const mystring& msg2, int code)
       
   110 {
       
   111   if(!o_quiet)
       
   112     fout << "vdeliver: " << msg1 << msg2 << endl;
       
   113   exit(code);
       
   114 }
       
   115 
       
   116 void die_fail(const char* msg) { exit_msg(msg, 100); }
       
   117 void die_temp(const char* msg) { exit_msg(msg, 111); }
       
   118 
       
   119 void fail_quota()
       
   120 {
       
   121   die_fail("Delivery failed due to system quota violation");
       
   122 }
       
   123 
       
   124 mystring read_me()
       
   125 {
       
   126   static mystring me;
       
   127   static mystring mefilename = config->qmail_root() + "control/me";
       
   128   if(!me) {
       
   129     fdibuf in(mefilename.c_str());
       
   130     if(in)
       
   131       in.getline(me);
       
   132   }
       
   133   if(!me)
       
   134     die_temp("control/me is empty!");
       
   135   return me;
       
   136 }
       
   137 
       
   138 char* ufline;
       
   139 char* rpline;
       
   140 char* dtline;
       
   141 
       
   142 bool dump(fdobuf& out, bool dosync)
       
   143 {
       
   144   if((ufline && !out.write(ufline, strlen(ufline))) ||
       
   145      (rpline && !out.write(rpline, strlen(rpline))) ||
       
   146      (dtline && !out.write(dtline, strlen(dtline))) ||
       
   147      !fin.rewind() ||
       
   148      !fdbuf_copy(fin, out)) {
       
   149     out.close();
       
   150     return false;
       
   151   }
       
   152   if(dosync && !out.sync())
       
   153     return false;
       
   154   if(!out.close())
       
   155     return false;
       
   156   return true;
       
   157 }
       
   158 
       
   159 static mystring partname;
       
   160 static mystring maildir;
       
   161 
       
   162 void deliver_partial()
       
   163 {
       
   164   const mystring newdir = maildir + "/new";
       
   165   const mystring tmpdir = maildir + "/tmp";
       
   166 
       
   167   if(!is_dir(tmpdir.c_str()) || !is_dir(newdir.c_str()))
       
   168     die_temp("Destination directory does not appear to be a maildir.");
       
   169 
       
   170   const mystring hostname = make_hostname();
       
   171   pid_t pid = getpid();
       
   172   for(;; sleep(2)) {
       
   173     partname = "/" + mystring(itoa(time(0))) + "." + itoa(pid)
       
   174       + "." + hostname;
       
   175     
       
   176     mystring newfile = newdir + partname;
       
   177     mystring tmpfile = tmpdir + partname;
       
   178 
       
   179     if(is_exist(tmpfile.c_str()))
       
   180       continue;
       
   181     else {
       
   182       fdobuf out(tmpfile.c_str(), fdobuf::create | fdobuf::excl, 0600);
       
   183       if(!out) {
       
   184 #ifdef EDQUOT
       
   185 	if(out.error_number() == EDQUOT)
       
   186 	  fail_quota();
       
   187 #endif
       
   188 	continue;
       
   189       }
       
   190       if(!dump(out, true)) {
       
   191 #ifdef EDQUOT
       
   192 	if(out.error_number() == EDQUOT)
       
   193 	  fail_quota();
       
   194 #endif
       
   195 	die_temp("Error writing the output file.");
       
   196       }
       
   197       return;
       
   198     }
       
   199   }
       
   200 }
       
   201 
       
   202 void deliver_fail(const char* msg)
       
   203 {
       
   204   mystring tmpfile = maildir + "/tmp/" + partname;
       
   205   unlink(tmpfile.c_str());
       
   206   die_temp(msg);
       
   207 }
       
   208 
       
   209 void deliver_final()
       
   210 {
       
   211   mystring tmpfile = maildir + "/tmp/" + partname;
       
   212   mystring newfile = maildir + "/new/" + partname;
       
   213   
       
   214   if(link(tmpfile.c_str(), newfile.c_str()))
       
   215     deliver_fail("Error linking the temp file to the new file.");
       
   216   if(unlink(tmpfile.c_str()))
       
   217     deliver_fail("Error unlinking the temp file.");
       
   218 }
       
   219 
       
   220 void write_envelope(fdobuf& out,
       
   221 		    mystring sender, mystring recipient, mystring host)
       
   222 {
       
   223   out << 'F' << sender << '\0';
       
   224 
       
   225   for(mystring_iter iter = recipient; iter; ++iter) {
       
   226     mystring r = *iter;
       
   227     int at = r.find_first('@');
       
   228     out << 'T' << r;
       
   229 
       
   230     // If the address has no '@', add the virtual domain
       
   231     if(at < 0)
       
   232       out << '@' << host;
       
   233     // If it has an '@', but no domain, add the local domain
       
   234     else if((unsigned)at == r.length()-1)
       
   235       out << read_me();
       
   236     // Else, nothing to add, address already copied
       
   237     out << '\0';
       
   238   }
       
   239   out << '\0';
       
   240 }
       
   241 
       
   242 void inject(mystring sender, mystring recip, mystring host)
       
   243 {
       
   244   int pipe1[2];
       
   245   int pipe2[2];
       
   246   if(pipe(pipe1) || pipe(pipe2))
       
   247     deliver_fail("System call to 'pipe' failed.");
       
   248 
       
   249   mystring qq = config->qmail_root() + "bin/qmail-queue";
       
   250   pid_t pid;
       
   251   switch(pid = fork()) {
       
   252   case -1:
       
   253     deliver_fail("System call to 'fork' failed.");
       
   254   case 0:
       
   255     close(pipe1[1]);
       
   256     close(pipe2[1]);
       
   257     if((dup2(pipe1[0], 0) != 0) || (dup2(pipe2[0], 1) != 1))
       
   258       exit(111);
       
   259     execl(qq.c_str(), qq.c_str(), 0);
       
   260     die_temp("Exec of qmail-queue failed.");
       
   261   default:
       
   262     close(pipe1[0]);
       
   263     close(pipe2[0]);
       
   264     signal(SIGPIPE, SIG_IGN);
       
   265     ufline = 0;
       
   266     rpline = 0;
       
   267     fdobuf out(pipe1[1], true);
       
   268     if(!dump(out, false))
       
   269       deliver_fail("Error writing to pipe");
       
   270     fdobuf env(pipe2[1], true);
       
   271     write_envelope(env, sender, recip, host);
       
   272     if(!env.flush() || !env.close())
       
   273       deliver_fail("Error sending envelope to pipe");
       
   274     int status;
       
   275     if(waitpid(pid, &status, WUNTRACED) != pid)
       
   276       deliver_fail("System call to 'waitpid' failed.");
       
   277     if(!WIFEXITED(status))
       
   278       deliver_fail("qmail-queue crashed!");
       
   279     if(WEXITSTATUS(status))
       
   280       deliver_fail("qmail-queue exited with an error!");
       
   281   }
       
   282 }
       
   283 
       
   284 void enqueue(mystring recipient, mystring host, mystring sender)
       
   285 {
       
   286   int f = sender.find_first('@');
       
   287   if(f > 0) {
       
   288     presetenv("QMAILUSER=", sender.left(f));
       
   289     presetenv("QMAILHOST=", sender.right(f+1));
       
   290   }
       
   291   inject(sender, recipient, host);
       
   292 }
       
   293 
       
   294 int cli_main(int, char*[])
       
   295 {
       
   296   if(!go_home())
       
   297     return 1;
       
   298   
       
   299 #define ENV_VAR(VAR,ENV) const char* tmp__##VAR = getenv(#ENV); if(!tmp__##VAR) die_fail(#ENV " is not set"); mystring VAR = tmp__##VAR;
       
   300   ENV_VAR(user, USER);
       
   301   ENV_VAR(ext, EXT);
       
   302   ENV_VAR(host, HOST);
       
   303   ENV_VAR(sender, SENDER);
       
   304 #undef ENV_VAR
       
   305 #define ENV_VAR(VAR,ENV) VAR = getenv(#ENV); if(!VAR) die_fail(#ENV " is not set");
       
   306   ENV_VAR(ufline, UFLINE);
       
   307   ENV_VAR(rpline, RPLINE);
       
   308   ENV_VAR(dtline, DTLINE);
       
   309 #undef ENV_VAR
       
   310 
       
   311   if(!addufline)
       
   312     ufline = 0;
       
   313   if(!addrpline)
       
   314     rpline = 0;
       
   315   if(!adddtline)
       
   316     dtline = 0;
       
   317 
       
   318   vpwentry* vpw = domain.lookup(ext, false);
       
   319   if(!vpw)
       
   320     die_fail(mystring("Invalid or unknown virtual user '" + ext + "'").c_str());
       
   321   if(vpw->expiry < (unsigned)time(0))
       
   322     die_fail(mystring("Virtual user '" + ext + "' has expired").c_str());
       
   323   
       
   324   vpw->export_env();
       
   325   bool enabled = vpw->is_mailbox_enabled && !!vpw->mailbox;
       
   326 
       
   327   int r = execute("vdeliver-predeliver");
       
   328   if(r)
       
   329     exit_msg("Execution of vdeliver-predeliver failed", r);
       
   330 
       
   331   if(enabled) {
       
   332     maildir = vpw->mailbox;
       
   333     deliver_partial();
       
   334   }
       
   335   if(!!vpw->forwards)
       
   336     enqueue(vpw->forwards, host, sender);
       
   337   if(enabled)
       
   338     deliver_final();
       
   339 
       
   340   if(!fin.rewind()) {
       
   341     if(!o_quiet)
       
   342       fout << "Could not re-rewind standard input" << endl;
       
   343   }
       
   344   else if(execute("vdeliver-postdeliver"))
       
   345     if(!o_quiet)
       
   346       fout << "Execution of vdeliver-postdeliver failed" << endl;
       
   347 
       
   348   return 0;
       
   349 }