1 /* m_mktemp.c -- Construct a temporary file.
2  *
3  * This code is Copyright (c) 2010, by the authors of nmh.  See the
4  * COPYRIGHT file in the root directory of the nmh distribution for
5  * complete copyright information.
6  */
7 
8 #include <h/mh.h>
9 #include <h/utils.h>
10 #include <h/signals.h>
11 #include "m_maildir.h"
12 #include "m_mktemp.h"
13 
14 static void register_for_removal(const char *);
15 
16 
17 /*  Create a temporary file.  If pfx_in is null, the temporary file
18  *  will be created in the temporary directory (more on that later).
19  *  If pfx_in is not null, then the temporary file location will be
20  *  defined by the value pfx_in.
21  *
22  *  The file created will be at the pathname specified appended with
23  *  6 random (we hope :) characters.
24  *
25  *  The return value will be the pathname to the file created.
26  *
27  *  CAUTION: The return pointer references static data.  If
28  *  you need to modify, or save, the return string, make a copy of it
29  *  first.
30  *
31  *  When pfx_in is null, the temporary directory is determined as
32  *  follows, in order:
33  *
34  *    MHTMPDIR envvar
35  *    TMPDIR envvar
36  *    User's mail directory.
37  *
38  *  NOTE: One will probably use m_mktemp2() instead of this function.
39  *  For example, if you want to create a temp file in the defined
40  *  temporary directory, but with a custom basename prefix, do
41  *  something like the following:
42  *
43  *    char *tmp_pathname = m_mktemp2(NULL, "mypre", ...);
44  */
45 char *
m_mktemp(const char * pfx_in,int * fd_ret,FILE ** fp_ret)46 m_mktemp (
47     const char *pfx_in,   /* Pathname prefix for temporary file. */
48     int *fd_ret,          /* (return,optional) File descriptor to temp file. */
49     FILE **fp_ret         /* (return,optional) FILE pointer to temp file. */
50 )
51 {
52     static char tmpfil[BUFSIZ];
53     int fd = -1;
54     int keep_open = 0;
55     mode_t oldmode = umask(077);
56 
57     if (pfx_in == NULL) {
58         snprintf(tmpfil, sizeof(tmpfil), "%s/nmhXXXXXX", get_temp_dir());
59     } else {
60         snprintf(tmpfil, sizeof(tmpfil), "%sXXXXXX", pfx_in);
61     }
62 
63     fd = mkstemp(tmpfil);
64 
65     if (fd < 0) {
66         umask(oldmode);
67         return NULL;
68     }
69 
70     register_for_removal(tmpfil);
71 
72     if (fd_ret != NULL) {
73         *fd_ret = fd;
74         keep_open = 1;
75     }
76     if (fp_ret != NULL) {
77         FILE *fp = fdopen(fd, "w+");
78         if (fp == NULL) {
79             int olderr = errno;
80             (void) m_unlink(tmpfil);
81             close(fd);
82             errno = olderr;
83             umask(oldmode);
84             return NULL;
85         }
86         *fp_ret = fp;
87         keep_open = 1;
88     }
89     if (!keep_open) {
90         close(fd);
91     }
92     umask(oldmode);
93     return tmpfil;
94 }
95 
96 /* This version allows one to specify the directory the temp file should
97  * by created based on a given pathname.  Although m_mktemp() technically
98  * supports this, this version is when the directory is defined by
99  * a separate variable from the prefix, eliminating the caller from having
100  * to do string manipulation to generate the desired pathname prefix.
101  *
102  * The pfx_in parameter specifies a basename prefix for the file.  If dir_in
103  * is NULL, then the defined temporary directory (see comments to m_mktemp()
104  * above) is used to create the temp file.
105  */
106 char *
m_mktemp2(const char * dir_in,const char * pfx_in,int * fd_ret,FILE ** fp_ret)107 m_mktemp2 (
108     const char *dir_in,   /* Directory to place temp file. */
109     const char *pfx_in,   /* Basename prefix for temp file. */
110     int *fd_ret,          /* (return,optional) File descriptor to temp file. */
111     FILE **fp_ret         /* (return,optional) FILE pointer to temp file. */
112 )
113 {
114     static char buffer[BUFSIZ];
115     char *cp;
116     int n;
117 
118     if (dir_in == NULL) {
119         if (pfx_in == NULL) {
120             return m_mktemp(NULL, fd_ret, fp_ret);
121         }
122         snprintf(buffer, sizeof(buffer), "%s/%s", get_temp_dir(), pfx_in);
123         return m_mktemp(buffer, fd_ret, fp_ret);
124     }
125 
126     if ((cp = r1bindex ((char *)dir_in, '/')) == dir_in) {
127         /* No directory component */
128         return m_mktemp(pfx_in, fd_ret, fp_ret);
129     }
130     n = (int)(cp-dir_in); /* Length of dir component */
131     snprintf(buffer, sizeof(buffer), "%.*s%s", n, dir_in, pfx_in);
132     return m_mktemp(buffer, fd_ret, fp_ret);
133 }
134 
135 
136 /*
137  * This version supports a suffix for the temp filename.
138  * It has two other differences from m_mktemp() and m_mktemp2():
139  * 1) It does not support specification of a directory for the temp
140  *    file.  Instead it always uses get_temp_dir().
141  * 2) It returns a dynamically allocated string.  The caller is
142  *    responsible for freeing the allocated memory.
143  */
144 char *
m_mktemps(const char * pfx_in,const char * suffix,int * fd_ret,FILE ** fp_ret)145 m_mktemps(
146     const char *pfx_in,   /* Basename prefix for temp file. */
147     const char *suffix,   /* Suffix, including any '.', for temp file. */
148     int *fd_ret,          /* (return,optional) File descriptor to temp file. */
149     FILE **fp_ret         /* (return,optional) FILE pointer to temp file. */
150 )
151 {
152     char *tmpfil;
153     int fd = -1;
154     int keep_open = 0;
155     mode_t oldmode = umask(077);
156 
157     if (suffix == NULL) {
158         if ((tmpfil = m_mktemp2(NULL, pfx_in, fd_ret, fp_ret))) {
159             return mh_xstrdup(tmpfil);
160         }
161         return NULL;
162     }
163 
164 #if HAVE_MKSTEMPS
165     if (pfx_in == NULL) {
166         tmpfil = concat(get_temp_dir(), "/nmhXXXXXX", suffix, NULL);
167     } else {
168         tmpfil = concat(get_temp_dir(), "/", pfx_in, "XXXXXX", suffix, NULL);
169     }
170 
171     fd = mkstemps(tmpfil, (int) strlen(suffix));
172 #else  /* ! HAVE_MKSTEMPS */
173     /* NetBSD, Solaris 10 */
174 
175     if (pfx_in == NULL) {
176         tmpfil = concat(get_temp_dir(), "/nmhXXXXXX", NULL);
177     } else {
178         tmpfil = concat(get_temp_dir(), "/", pfx_in, "XXXXXX", NULL);
179     }
180 
181     fd = mkstemp(tmpfil);
182     {
183         char *oldfilename = tmpfil;
184         tmpfil = concat(oldfilename, suffix, NULL);
185 
186         if (rename(oldfilename, tmpfil) != 0) {
187             (void) unlink(oldfilename);
188             free(oldfilename);
189             free(tmpfil);
190             return NULL;
191         }
192 
193         free(oldfilename);
194     }
195 #endif /* ! HAVE_MKSTEMPS */
196 
197     if (fd < 0) {
198         umask(oldmode);
199         free(tmpfil);
200         return NULL;
201     }
202 
203     register_for_removal(tmpfil);
204 
205     if (fd_ret != NULL) {
206         *fd_ret = fd;
207         keep_open = 1;
208     }
209     if (fp_ret != NULL) {
210         FILE *fp = fdopen(fd, "w+");
211         if (fp == NULL) {
212             int olderr = errno;
213             (void) m_unlink(tmpfil);
214             close(fd);
215             errno = olderr;
216             umask(oldmode);
217             free(tmpfil);
218             return NULL;
219         }
220         *fp_ret = fp;
221         keep_open = 1;
222     }
223     if (!keep_open) {
224         close(fd);
225     }
226     umask(oldmode);
227 
228     return tmpfil;
229 }
230 
231 
232 char *
get_temp_dir(void)233 get_temp_dir(void)
234 {
235     /* Ignore envvars if we are setuid */
236     if ((getuid()==geteuid()) && (getgid()==getegid())) {
237         char *tmpdir = NULL;
238         tmpdir = getenv("MHTMPDIR");
239         if (tmpdir != NULL && *tmpdir != '\0') return tmpdir;
240 
241         tmpdir = getenv("TMPDIR");
242         if (tmpdir != NULL && *tmpdir != '\0') return tmpdir;
243     }
244     return m_maildir("");
245 }
246 
247 
248 /*
249  * Array of files (full pathnames) to remove on process exit.
250  * Instead of removing slots from the array, just set to NULL
251  * to indicate that the file should no longer be removed.
252  */
253 static svector_t exit_filelist = NULL;
254 
255 /*
256  * Register a file for removal at program termination.
257  */
258 static void
register_for_removal(const char * pathname)259 register_for_removal(const char *pathname) {
260     if (exit_filelist == NULL) exit_filelist = svector_create(20);
261     (void) svector_push_back(exit_filelist, add(pathname, NULL));
262 }
263 
264 /*
265  * Unregister all files that were registered to be removed at program
266  * termination.  When called with remove_files of 0, this function is
267  * intended for use by one of the programs after a fork(3) without a
268  * subsequent call to one of the exec(3) functions or _exit(3).  When
269  * called with non-0 remove_files argument, it is intended for use by
270  * an atexit() function.
271  *
272  * Right after a fork() and before calling exec() or _exit(), if the
273  * child catches one of the appropriate signals, it will remove
274  * all the registered temporary files.  Some of those may be needed by
275  * the parent.  Some nmh programs ignore or block some of the signals
276  * in the child right after fork().  For the other programs, rather
277  * than complicate things by preventing that, just take the chance
278  * that it might happen.  It is harmless to attempt to unlink a
279  * temporary file twice, assuming another one wasn't created too
280  * quickly created with the same name.
281  */
282 void
unregister_for_removal(int remove_files)283 unregister_for_removal(int remove_files) {
284     if (exit_filelist) {
285         size_t i;
286 
287         for (i = 0; i < svector_size(exit_filelist); ++i) {
288             char *filename = svector_at(exit_filelist, i);
289 
290             if (filename) {
291                 if (remove_files) (void) unlink(filename);
292                 free(filename);
293             }
294         }
295 
296         svector_free(exit_filelist);
297         exit_filelist = NULL;
298     }
299 }
300 
301 /*
302  * If the file was registered for removal, deregister it.  In
303  * any case, unlink it.
304  */
305 int
m_unlink(const char * pathname)306 m_unlink(const char *pathname) {
307     if (exit_filelist) {
308         char **slot = svector_find(exit_filelist, pathname);
309 
310         if (slot  &&  *slot) {
311             free(*slot);
312             *slot = NULL;
313         }
314     }
315 
316     return unlink(pathname);
317     /* errno should be set to ENOENT if file was not found */
318 }
319 
320 /*
321  * Remove all registered temporary files.
322  */
323 void
remove_registered_files_atexit(void)324 remove_registered_files_atexit(void) {
325     unregister_for_removal(1);
326 }
327 
328 /*
329  * Remove all registered temporary files.  Suitable for use as a
330  * signal handler.  If the signal is one of the usual process
331  * termination signals, calls exit().  Otherwise, disables itself
332  * by restoring the default action and then re-raises the signal,
333  * in case the use was expecting a core dump.
334  */
335 void
remove_registered_files(int sig)336 remove_registered_files(int sig) {
337     struct sigaction act;
338 
339     /*
340      * Ignore this signal for the duration.  And we either exit() or
341      * disable this signal handler below, so don't restore this handler.
342      */
343     act.sa_handler = SIG_IGN;
344     (void) sigemptyset(&act.sa_mask);
345     act.sa_flags = 0;
346     (void) sigaction(sig, &act, 0);
347 
348     if (sig == SIGHUP || sig == SIGINT || sig == SIGQUIT || sig == SIGTERM) {
349         /*
350          * We don't need to call remove_registered_files_atexit() if
351          * it was registered with atexit(), but let's not assume that.
352          * It's harmless to call it twice.
353          */
354         remove_registered_files_atexit();
355 
356         exit(1);
357     } else {
358         remove_registered_files_atexit();
359 
360         act.sa_handler = SIG_DFL;
361         (void) sigemptyset(&act.sa_mask);
362         act.sa_flags = 0;
363         (void) sigaction(sig, &act, 0);
364 
365         (void) raise(sig);
366     }
367 }
368