|
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 } |