1 // Copyright (C) 1999,2000,2005 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 static int run_predeliver = true;
43 static int run_postdeliver = true;
44 
45 // vdeliver is the unified e-mail message delivery agent for virtual
46 // domains managed by vmailmgr.
47 // It is run from the F<.qmail-default> file, and automatically handles
48 // delivery to any user within a virtual domain.
49 
50 cli_option cli_options[] = {
51   { 'D', 0, cli_option::flag, true, &adddtline,
52     "Add a \"Delivered-To:\" line (default)", 0 },
53   // Add the C<Return-Path:> line to the top of the message. (default)
54   { 'F', 0, cli_option::flag, true, &addufline,
55     "Add a \"From \" mailbox line", 0 },
56   { 'R', 0, cli_option::flag, true, &addrpline,
57     "Add a \"Return-Path:\" line (default)", 0 },
58   { 'd', 0, cli_option::flag, false, &adddtline,
59     "Do not add the \"Delivered-To:\" line", 0 },
60   //Do not add the C<Delivered-To:> line to the top of the message.
61   { 'f', 0, cli_option::flag, false, &addufline,
62     "Do not add the \"From \" mailbox line (default)", 0 },
63   // Do not add the C<From> mailbox line to the top of the message.
64   // Note that this line is never added when the message is being
65   // re-injected into the mail stream. (default)
66   { 0, "no-predeliver", cli_option::flag, false, &run_predeliver,
67     "Do not run vdeliver-predeliver scripts", 0 },
68   { 0, "no-postdeliver", cli_option::flag, false, &run_postdeliver,
69     "Do not run vdeliver-postdeliver scripts", 0 },
70   { 0, "quiet", cli_option::flag, true, &o_quiet,
71     "Suppress all status messages", 0 },
72   { 'r', 0, cli_option::flag, false, &addrpline,
73     "Do not add the \"Return-Path:\" line", 0 },
74   // Do not add the C<Return-Path:> line to the top of the message.
75   {0}
76 };
77 
78 // RETURN VALUE
79 //
80 // Returns 0 if delivery was successful,
81 // 100 if a fatal error occurred,
82 // or 111 if a temporary error occurred.
83 
84 // ENVIRONMENT
85 //
86 // F<vdeliver> expects to be run by F<qmail-local> as it requires several
87 // of the environment variables that it sets.
88 // See the I<qmail-command>(8) manual page for full details on these
89 // variables.
90 // In particular, it requires C<DTLINE>, C<EXT>, C<HOST>, C<RPLINE>,
91 // C<SENDER>, C<UFLINE>, and C<USER>.
92 
93 // SEE ALSO
94 //
95 // checkvpw(1), I<qmail-command>(8)
96 
97 #ifndef HAVE_GETHOSTNAME
98 int gethostname(char *name, size_t len);
99 #endif
100 
make_hostname()101 const char* make_hostname()
102 {
103   static char buf[512];
104   gethostname(buf, 511);
105   return buf;
106 }
107 
exit_msg(const char * msg,int code)108 void exit_msg(const char* msg, int code)
109 {
110   if(!o_quiet)
111     fout << "vdeliver: " << msg << endl;
112   exit(code);
113 }
114 
exit_msg(const char * msg1,const mystring & msg2,int code)115 void exit_msg(const char* msg1, const mystring& msg2, int code)
116 {
117   if(!o_quiet)
118     fout << "vdeliver: " << msg1 << msg2 << endl;
119   exit(code);
120 }
121 
die_fail(const char * msg)122 void die_fail(const char* msg) { exit_msg(msg, 100); }
die_temp(const char * msg)123 void die_temp(const char* msg) { exit_msg(msg, 111); }
124 
fail_quota()125 void fail_quota()
126 {
127   die_fail("Delivery failed due to system quota violation");
128 }
129 
read_me()130 mystring read_me()
131 {
132   static mystring me;
133   static mystring mefilename = config->qmail_root() + "control/me";
134   if(!me) {
135     fdibuf in(mefilename.c_str());
136     if(in)
137       in.getline(me);
138   }
139   if(!me)
140     die_temp("control/me is empty!");
141   return me;
142 }
143 
144 char* ufline;
145 char* rpline;
146 char* dtline;
147 
dump(fdobuf & out,bool dosync)148 bool dump(fdobuf& out, bool dosync)
149 {
150   if((ufline && !out.write(ufline, strlen(ufline))) ||
151      (rpline && !out.write(rpline, strlen(rpline))) ||
152      (dtline && !out.write(dtline, strlen(dtline))) ||
153      !fin.rewind() ||
154      !fdbuf_copy(fin, out)) {
155     out.close();
156     return false;
157   }
158   if(dosync && !out.sync())
159     return false;
160   if(!out.close())
161     return false;
162   return true;
163 }
164 
165 static mystring partname;
166 static mystring maildir;
167 
deliver_partial()168 void deliver_partial()
169 {
170   const mystring newdir = maildir + "/new";
171   const mystring tmpdir = maildir + "/tmp";
172 
173   if(!is_dir(tmpdir.c_str()) || !is_dir(newdir.c_str()))
174     die_temp("Destination directory does not appear to be a maildir.");
175 
176   const mystring hostname = make_hostname();
177   pid_t pid = getpid();
178   for(;; sleep(2)) {
179     partname = "/" + mystring(itoa(time(0))) + "." + itoa(pid)
180       + "." + hostname;
181 
182     mystring newfile = newdir + partname;
183     mystring tmpfile = tmpdir + partname;
184 
185     if(is_exist(tmpfile.c_str()))
186       continue;
187     else {
188       fdobuf out(tmpfile.c_str(), fdobuf::create | fdobuf::excl, 0600);
189       if(!out) {
190 #ifdef EDQUOT
191 	if(out.error_number() == EDQUOT)
192 	  fail_quota();
193 #endif
194 	continue;
195       }
196       if(!dump(out, true)) {
197 #ifdef EDQUOT
198 	if(out.error_number() == EDQUOT)
199 	  fail_quota();
200 #endif
201 	die_temp("Error writing the output file.");
202       }
203       return;
204     }
205   }
206 }
207 
deliver_fail(const char * msg)208 void deliver_fail(const char* msg)
209 {
210   mystring tmpfile = maildir + "/tmp/" + partname;
211   unlink(tmpfile.c_str());
212   die_temp(msg);
213 }
214 
deliver_final()215 void deliver_final()
216 {
217   mystring tmpfile = maildir + "/tmp/" + partname;
218   mystring newfile = maildir + "/new/" + partname;
219 
220   if(link(tmpfile.c_str(), newfile.c_str()))
221     deliver_fail("Error linking the temp file to the new file.");
222   if(unlink(tmpfile.c_str()))
223     deliver_fail("Error unlinking the temp file.");
224 }
225 
write_envelope(fdobuf & out,mystring sender,mystring recipient,mystring host)226 void write_envelope(fdobuf& out,
227 		    mystring sender, mystring recipient, mystring host)
228 {
229   out << 'F' << sender << '\0';
230 
231   for(mystring_iter iter = recipient; iter; ++iter) {
232     mystring r = *iter;
233     int at = r.find_first('@');
234     out << 'T' << r;
235 
236     // If the address has no '@', add the virtual domain
237     if(at < 0)
238       out << '@' << host;
239     // If it has an '@', but no domain, add the local domain
240     else if((unsigned)at == r.length()-1)
241       out << read_me();
242     // Else, nothing to add, address already copied
243     out << '\0';
244   }
245   out << '\0';
246 }
247 
inject(mystring sender,mystring recip,mystring host)248 void inject(mystring sender, mystring recip, mystring host)
249 {
250   int pipe1[2];
251   int pipe2[2];
252   if(pipe(pipe1) || pipe(pipe2))
253     deliver_fail("System call to 'pipe' failed.");
254 
255   mystring qq = config->qmail_root() + "bin/qmail-queue";
256   pid_t pid;
257   switch(pid = fork()) {
258   case -1:
259     deliver_fail("System call to 'fork' failed.");
260   case 0:
261     close(pipe1[1]);
262     close(pipe2[1]);
263     if((dup2(pipe1[0], 0) != 0) || (dup2(pipe2[0], 1) != 1))
264       exit(111);
265     execl(qq.c_str(), qq.c_str(), (char *)0);
266     die_temp("Exec of qmail-queue failed.");
267   default:
268     close(pipe1[0]);
269     close(pipe2[0]);
270     signal(SIGPIPE, SIG_IGN);
271     ufline = 0;
272     rpline = 0;
273     fdobuf out(pipe1[1], true);
274     if(!dump(out, false))
275       deliver_fail("Error writing to pipe");
276     fdobuf env(pipe2[1], true);
277     write_envelope(env, sender, recip, host);
278     if(!env.flush() || !env.close())
279       deliver_fail("Error sending envelope to pipe");
280     int status;
281     if(waitpid(pid, &status, WUNTRACED) != pid)
282       deliver_fail("System call to 'waitpid' failed.");
283     if(!WIFEXITED(status))
284       deliver_fail("qmail-queue crashed!");
285     if(WEXITSTATUS(status))
286       deliver_fail("qmail-queue exited with an error!");
287   }
288 }
289 
enqueue(mystring recipient,mystring host,mystring sender)290 void enqueue(mystring recipient, mystring host, mystring sender)
291 {
292   int f = sender.find_first('@');
293   if(f > 0) {
294     presetenv("QMAILUSER=", sender.left(f));
295     presetenv("QMAILHOST=", sender.right(f+1));
296   }
297   inject(sender, recipient, host);
298 }
299 
cli_main(int,char * [])300 int cli_main(int, char*[])
301 {
302   if(!go_home())
303     return 1;
304 
305 #define ENV_VAR(VAR,ENV) const char* tmp__##VAR = getenv(#ENV); if(!tmp__##VAR) die_fail(#ENV " is not set"); mystring VAR = tmp__##VAR;
306   ENV_VAR(user, USER);
307   ENV_VAR(ext, EXT);
308   ENV_VAR(host, HOST);
309   ENV_VAR(sender, SENDER);
310 #undef ENV_VAR
311 #define ENV_VAR(VAR,ENV) VAR = getenv(#ENV); if(!VAR) die_fail(#ENV " is not set");
312   ENV_VAR(ufline, UFLINE);
313   ENV_VAR(rpline, RPLINE);
314   ENV_VAR(dtline, DTLINE);
315 #undef ENV_VAR
316 
317   if(!addufline)
318     ufline = 0;
319   if(!addrpline)
320     rpline = 0;
321   if(!adddtline)
322     dtline = 0;
323 
324   vpwentry* vpw = domain.lookup(ext);
325   if(!vpw)
326     vpw = domain.lookup(config->default_username());
327   if(!vpw)
328     die_fail(mystring("Invalid or unknown virtual user '" + ext + "'").c_str());
329   if(vpw->expiry < (unsigned)time(0))
330     die_fail(mystring("Virtual user '" + ext + "' has expired").c_str());
331 
332   vpw->export_env();
333   bool do_delivery = vpw->has_mailbox && vpw->is_mailbox_enabled &&
334     !!vpw->directory;
335 
336   if (run_predeliver) {
337     int r = execute("vdeliver-predeliver");
338     if(r)
339       if(r == 99)
340 	return 99;
341       else
342 	exit_msg("Execution of vdeliver-predeliver failed", r);
343   }
344 
345   if(do_delivery) {
346     maildir = vpw->directory;
347     deliver_partial();
348   }
349   if(!!vpw->forwards)
350     enqueue(vpw->forwards, host, sender);
351   if(do_delivery)
352     deliver_final();
353 
354   if(!fin.rewind()) {
355     if(!o_quiet)
356       fout << "Could not re-rewind standard input" << endl;
357   }
358   else {
359     if (run_postdeliver) {
360       int r = execute("vdeliver-postdeliver");
361       if(r && r != 99)
362 	if(!o_quiet)
363 	  fout << "Execution of vdeliver-postdeliver failed" << endl;
364       return r;
365     }
366   }
367 
368   return 0;
369 }
370