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