1 /* $Id$ */
2
3 /*
4 *
5 * Copyright (C) 2004 David Mazieres (dm@uun.org)
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2, or (at
10 * your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20 * USA
21 *
22 */
23
24 #include "asmtpd.h"
25 #include <dirent.h>
26
27 str enqmsg_file::spooldir;
28 vec<const char *> enqmsg_file::mini_env;
29
30 str
get_spooldir()31 enqmsg_file::get_spooldir ()
32 {
33 static uid_t uid;
34 static time_t lastcheck;
35 struct stat sb;
36
37 if (!spooldir)
38 uid = getuid ();
39 else if (timenow <= lastcheck + 10)
40 return spooldir;
41 else if (!lstat (spooldir, &sb) && S_ISDIR (sb.st_mode)
42 && sb.st_uid == uid) {
43 lastcheck = timenow;
44 return spooldir;
45 }
46 const char *p = getenv ("TMPDIR");
47 if (!p)
48 p = "/var/tmp";
49 mstr m (strlen (p) + progname.len () + 12);
50 #if HAVE_MKDTEMP
51 strcpy (m, p);
52 strcat (m, "/");
53 strcat (m, progname);
54 strcat (m, "XXXXXXXXXX");
55 errno = 0;
56 if (!mkdtemp (m.cstr ()))
57 fatal ("mkdtemp %s failed\n", m.cstr ());
58 #else /* !HAVE_MKDTEMP */
59 do {
60 strcpy (m, p);
61 strcat (m, "/");
62 strcat (m, progname);
63 strcat (m, "XXXXXXXXXX");
64 errno = 0;
65 if (!mktemp (m.cstr ()))
66 fatal ("mktemp %s failed\n", m.cstr ());
67 errno = 0;
68 } while (mkdir (m.cstr (), 0700) && errno == EEXIST);
69 if (errno)
70 fatal ("could not create spool directory in %s: %m\n", p);
71 #endif /* !HAVE_MKDTEMP */
72 m.setlen (strlen (m.cstr ()));
73 spooldir = m;
74 lastcheck = timenow;
75 return spooldir;
76 }
77
78 inline void
preserve(vec<const char * > * e,const char * var)79 preserve (vec<const char *> *e, const char *var)
80 {
81 if (const char *p = getenv (var)) {
82 str s (strbuf ("%s=%s", var, p));
83 e->push_back (xstrdup (s.cstr ()));
84 }
85 }
86 void
init_mini_env()87 enqmsg_file::init_mini_env ()
88 {
89 while (!mini_env.empty ())
90 xfree ((void *) mini_env.pop_front ());
91
92 preserve (&mini_env, "PATH");
93 preserve (&mini_env, "PWD");
94 preserve (&mini_env, "TZ");
95 preserve (&mini_env, "TMPDIR");
96 preserve (&mini_env, "DMALLOC_OPTIONS");
97 preserve (&mini_env, "STKTRACE");
98 mini_env.push_back (xstrdup ("USER=unknown-user"));
99 mini_env.push_back (NULL);
100 }
101
enqmsg_file()102 enqmsg_file::enqmsg_file ()
103 : fd (-1), efd (-1), error (false), eof (false), rps (NULL)
104 {
105 }
106
~enqmsg_file()107 enqmsg_file::~enqmsg_file ()
108 {
109 if (fd >= 0)
110 close (fd);
111 if (path)
112 unlink (path);
113 if (rps)
114 runprog_cancel (rps);
115 }
116
117 bool
init(str f,const vec<str> & to,str rcvd_line)118 enqmsg_file::init (str f, const vec<str> &to, str rcvd_line)
119 {
120 assert (fd < 0 && !error && !eof && !path);
121
122 str tmplate (get_spooldir () << "/msg.XXXXXXXXXX");
123 mstr tpath (tmplate.len ());
124 memcpy (tpath.cstr (), tmplate, tmplate.len () + 1);
125 umask (077);
126 fd = mkstemp (tpath);
127 if (fd < 0)
128 return false;
129 path = tpath;
130 close_on_exec (fd);
131
132 errno = 0;
133 if (write (fd, rcvd_line, rcvd_line.len ())
134 != implicit_cast<ssize_t> (rcvd_line.len ())) {
135 if (errno)
136 warn ("%s: %m\n", path.cstr ());
137 close (fd);
138 error = true;
139 fd = -1;
140 return false;
141 }
142
143 #if 0
144 unlink (path);
145 path = NULL;
146 #endif
147
148 from = f;
149 top = &to;
150
151 return true;
152 }
153
154 void
writev(suio * uiop,cbs cb)155 enqmsg_file::writev (suio *uiop, cbs cb)
156 {
157 assert (!eof);
158 errno = 0;
159 uiop->output (fd);
160 if (!uiop->resid ()) {
161 (*cb) (NULL);
162 return;
163 }
164 eof = true;
165 if (errno)
166 (*cb) (strbuf ("451 %m\r\n"));
167 else
168 (*cb) (strbuf ("451 local disk write error\r\n"));
169 }
170
171 int
getfd()172 enqmsg_file::getfd ()
173 {
174 eof = true;
175 if (fd >= 0 && lseek (fd, 0, SEEK_SET) != -1)
176 return fd;
177 return -1;
178 }
179
180 void
commit(cbs cb)181 enqmsg_file::commit (cbs cb)
182 {
183 assert (fd >= 0 && !error && efd < 0 && !rps);
184 eof = true;
185
186 if (lseek (fd, 0, SEEK_SET) == -1) {
187 (*cb) (strbuf ("451 %m\r\n"));
188 return;
189 }
190 if (!opt->sendmailfromline) {
191 char buf[8192];
192 int n = read (fd, buf, sizeof (buf));
193 if (n >= 5 && !strncmp (buf, "From ", 5)) {
194 for (;;) {
195 char *p = static_cast<char *> (memchr (buf, '\n', n));
196 if (p) {
197 if (lseek (fd, p - buf + 1 - n, SEEK_CUR) == -1) {
198 (*cb) (strbuf ("451 %m\r\n"));
199 return;
200 }
201 break;
202 }
203 if ((n = read (fd, buf, sizeof (buf))) < 0) {
204 (*cb) (strbuf ("451 %m\r\n"));
205 return;
206 }
207 }
208 }
209 else if (lseek (fd, 0, SEEK_SET) == -1) {
210 (*cb) (strbuf ("451 %m\r\n"));
211 return;
212 }
213 }
214
215 vec<const char *> av;
216 for (const str *sp = opt->sendmail.base (); sp < opt->sendmail.lim (); sp++)
217 av.push_back (*sp);
218 av.push_back ("-f");
219 if (from.len ())
220 av.push_back (from);
221 else
222 av.push_back (opt->emptysender);
223 av.push_back ("--");
224 for (const str *sp = top->base (); sp < top->lim (); sp++)
225 av.push_back (*sp);
226 av.push_back (NULL);
227
228 if (mini_env.empty ())
229 init_mini_env ();
230 rps = runprog (av[0], av.base (), fd, true,
231 wrap (this, &enqmsg_file::smcb, av[0], cb),
232 (opt->sendmailpriv ? NULL
233 : wrap (become_user, opt->av_user, false)),
234 mini_env.base ());
235 }
236
237 void
smcb(str sm,cbs cb,ref<progout> po)238 enqmsg_file::smcb (str sm, cbs cb, ref<progout> po)
239 {
240 rps = NULL;
241 if (!po->status) {
242 (*cb) (NULL);
243 return;
244 }
245
246 const char *smbn = strrchr (sm, '/');
247 if (smbn)
248 smbn++;
249 else
250 smbn = sm;
251
252 for (u_int i = 0; i < 5 && i < po->output.size (); i++)
253 warn << smbn << ": " << po->output[i] << "\n";
254
255 warn << sm << ": exited with " << exitstr (po->status) << "\n";
256
257 if (str resp = po->response (451))
258 (*cb) (resp);
259 else
260 (*cb) (strbuf () << "451 " << smbn << " failed\r\n");
261 }
262
263 EXITFN (cleanup);
264 static void
cleanup()265 cleanup ()
266 {
267 if (!enqmsg_file::spooldir)
268 return;
269 int olddir = open (".", O_RDONLY);
270 if (chdir (enqmsg_file::spooldir)) {
271 warn ("%s: %m\n", enqmsg_file::spooldir.cstr ());
272 return;
273 }
274
275 DIR *dir = opendir (".");
276 if (!dir) {
277 warn ("%s: %m\n", enqmsg_file::spooldir.cstr ());
278 if (fchdir (olddir) && chdir ("/"))
279 warn ("chdir: %m\n");
280 close (olddir);
281 return;
282 }
283
284 while (struct dirent *de = readdir (dir))
285 if (!strncmp (de->d_name, "msg", 3))
286 unlink (de->d_name);
287 closedir (dir);
288
289 if (fchdir (olddir) && chdir ("/"))
290 warn ("chdir: %m\n");
291 close (olddir);
292 if (rmdir (enqmsg_file::spooldir))
293 warn ("%s: %m\n", enqmsg_file::spooldir.cstr ());
294 enqmsg_file::spooldir = NULL;
295 }
296
297