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