chiark / gitweb /
Make guts into official library.
[checkpath] / tmpdir.c
1 /* -*-c-*-
2  *
3  * $Id: tmpdir.c,v 1.4 2003/01/25 23:58:44 mdw Exp $
4  *
5  * Choose and check temporary directories
6  *
7  * (c) 1999 Mark Wooding
8  */
9
10 /*----- Licensing notice --------------------------------------------------* 
11  *
12  * This file is part of chkpath.
13  *
14  * chkpath is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later version.
18  * 
19  * chkpath is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  * 
24  * You should have received a copy of the GNU General Public License
25  * along with chkpath; if not, write to the Free Software Foundation,
26  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27  */
28
29 /*----- Revision history --------------------------------------------------* 
30  *
31  * $Log: tmpdir.c,v $
32  * Revision 1.4  2003/01/25 23:58:44  mdw
33  * Make guts into official library.
34  *
35  * Revision 1.3  1999/05/21 22:07:20  mdw
36  * Take advantage of new dynamic string macros.
37  *
38  * Revision 1.2  1999/05/19 20:37:28  mdw
39  * Track gratuitous change in mdwopt interface.
40  *
41  * Revision 1.1.1.1  1999/04/06 20:12:07  mdw
42  * Import new project.
43  *
44  */
45
46 /*----- Header files ------------------------------------------------------*/
47
48 #include <errno.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52
53 #include <sys/types.h>
54 #include <sys/stat.h>
55 #include <unistd.h>
56 #include <pwd.h>
57
58 #include <mLib/alloc.h>
59 #include <mLib/dstr.h>
60 #include <mLib/mdwopt.h>
61 #include <mLib/quis.h>
62 #include <mLib/report.h>
63
64 #include "checkpath.h"
65
66 /*----- Static variables --------------------------------------------------*/
67
68 static uid_t me;
69 static struct checkpath cp;
70 static struct passwd *pw;
71
72 /*----- Main code ---------------------------------------------------------*/
73
74 /* --- @ok@ --- *
75  *
76  * Arguments:   @const char *p@ = pathname to check
77  *              @int *f@ = try-to-create flag
78  *
79  * Returns:     Nonzero if the directory is OK
80  *
81  * Use:         Ensures that a directory is OK.  If @f@ is a real pointer,
82  *              and @*f@ is set, then try to create the directory.
83  */
84
85 static int ok(const char *p, int *f)
86 {
87   struct stat st;
88
89   /* --- Read the directory status --- */
90
91   if (lstat(p, &st)) {
92
93     /* --- Maybe create it if it doesn't exist --- */
94
95     if (errno != ENOENT || !f || !*f)
96       return (0);
97     if (mkdir(p, 0700)) {
98       *f = 0;
99       return (0);
100     }
101
102     /* --- Now read the new status --- *
103      *
104      * This fixes a race condition between the previous @lstat@ call and
105      * the @mkdir@.
106      */
107
108     if (lstat(p, &st))
109       return (0);
110   }
111
112   /* --- Make sure the directory is good --- *
113    *
114    * It must be a genuine directory (not a link); it must be readable
115    * and writable only by its owner, and that owner must be me.
116    */
117
118   if (S_ISDIR(st.st_mode) && (st.st_mode & 0777) == 0700 && st.st_uid == me)
119     return (1);
120   return (0);
121 }
122
123 /* --- @trytmp@ --- *
124  *
125  * Arguments:   @const char *parent@ = parent directory name
126  *              @const char *base@ = subdirectory base name
127  *
128  * Returns:     Pointer to directory name, or null.  (String needs freeing.)
129  *
130  * Use:         Tries to find or create an appropriate temporary directory.
131  */
132
133 static char *trytmp(const char *parent, const char *base)
134 {
135   static char c[] = { "0123456789"
136                       "abcdefghijklmnopqrstuvwxyz"
137                       "ABCDEFGHIJKLMNOPQRSTUVWXYZ" };
138   char *p, *q;
139   char *qq;
140   dstr d = DSTR_INIT;
141   int createflag = 1;
142
143   /* --- Make sure the parent directory is sane --- *
144    *
145    * Must make sure that all the lead-up to the temporary directory is safe.
146    * Otherwise other people can play with symlinks or rename directories
147    * after I've done all this careful work to make the endpoint directory
148    * safe.
149    */
150
151   if (checkpath(parent, &cp))
152     return (0);
153
154   /* --- See whether the trivial version will work --- */
155
156   dstr_putf(&d, "%s/%s", parent, base);
157   if (ok(d.buf, &createflag))
158     goto good;
159
160   /* --- Now try with various suffixes --- */
161
162   DENSURE(&d, 4);
163   qq = d.buf + d.len;
164   *qq++ = '-';
165
166   for (p = c; *p; p++) {
167     qq[0] = *p;
168     qq[1] = 0;
169     if (ok(d.buf, &createflag))
170       goto good;
171     for (q = c; *q; q++) {
172       qq[1] = *q;
173       qq[2] = 0;
174       if (ok(d.buf, &createflag))
175         goto good;
176     }
177   }
178
179   /* --- No joy --- */
180
181   dstr_destroy(&d);
182   return (0);
183
184 good:
185   p = xstrdup(d.buf);
186   dstr_destroy(&d);
187   return (p);
188 }
189
190 /* --- @fullcheck@ --- *
191  *
192  * Arguments:   @const char *p@ = pointer to path to check
193  *
194  * Returns:     Zero if it's a bad directory.
195  *
196  * Use:         Runs a thorough check on a directory.
197  */
198
199 static int fullcheck(const char *p)
200 {
201   return (checkpath(p, &cp) == 0 && ok(p, 0));
202 }
203
204 /* --- @goodtmp@ --- *
205  *
206  * Arguments:   ---
207  *
208  * Returns:     Pointer to a known-good secure temporary directory, or
209  *              null.
210  *
211  * Use:         Finds a good place to store temporary files.
212  */
213
214 static char *goodtmp(void)
215 {
216   char *p, *q;
217
218   /* --- First of all, try the user's current choice --- */
219
220   if ((p = getenv("TMPDIR")) != 0 && fullcheck(p))
221     return (p);
222
223   /* --- Try making a directory in `/tmp' --- */
224
225   if (!(q = getenv("USER")) && !(q = getenv("LOGNAME")))
226     q = pw->pw_name;
227   if ((q = trytmp("/tmp", q)) != 0)
228     return (q);
229
230   /* --- That failed: try a directory in the user's home --- */
231
232   if (!(q = getenv("HOME")))
233     q = pw->pw_dir;
234   if ((q = trytmp(q, "tmp")) != 0)
235     return (q);
236
237   /* --- Still no joy: give up --- *
238    *
239    * To be fair, if the user can't find a safe place in his own home
240    * directory then he's pretty stuffed.
241    */
242
243   return (0);
244 }
245
246 /* --- @usage@ --- */
247
248 static void usage(FILE *fp)
249 {
250   fprintf(fp, "Usage: %s [-bc] [-v PATH]\n", QUIS);
251 }
252
253 /* --- @version@ --- */
254
255 static void version(FILE *fp)
256 {
257   fprintf(fp, "%s version %s\n", QUIS, VERSION);
258 }
259
260 /* --- @help@ --- */
261
262 static void help(FILE *fp)
263 {
264   version(fp);
265   putc('\n', fp);
266   usage(fp);
267   fputs("\n\
268 Sets a suitable and secure temporary directory, or checks that a given\n\
269 directory is suitable for use with temporary files.  A directory is\n\
270 considered good for a particular user if it's readable and writable only\n\
271 by that user, and if all its parents are modifiable only by the user or\n\
272 root.\n\
273 \n\
274 Options supported:\n\
275 \n\
276 -h, --help              Display this help text.\n\
277 -V, --version           Display the program's version number.\n\
278 -u, --usage             Display a terse usage summary.\n\
279 \n\
280 -b, --bourne            Output a `TMPDIR' setting for Bourne shell users.\n\
281 -c, --cshell            Output a `TMPDIR' setting for C shell users.\n\
282 -v, --verify PATH       Check whether PATH is good, setting exit status.\n\
283 \n\
284 The default action is to examine the caller's shell and output a suitable\n\
285 setting for that shell type.\n\
286 ",
287         fp);
288 }
289
290 /* --- @main@ --- *
291  *
292  * Arguments:   @int argc@ = number of command line arguments
293  *              @char *argv[]@ = the actual argument strings
294  *
295  * Returns:     Zero if all went well, else nonzero.
296  *
297  * Use:         Performs helpful operations on temporary directories.
298  */
299
300 int main(int argc, char *argv[])
301 {
302   int shell = 0;
303   int duff = 0;
304
305   enum {
306     sh_unknown,
307     sh_bourne,
308     sh_csh
309   };
310
311   /* --- Initialize variables --- */
312
313   ego(argv[0]);
314   me = getuid();
315   cp.cp_what = CP_WRWORLD | CP_WRGRP | CP_WROTHUSR | CP_STICKYOK;
316   cp.cp_verbose = 0;
317   cp.cp_report = 0;
318   checkpath_setids(&cp);
319   pw = getpwuid(me);
320   if (!pw)
321     die(1, "you don't exist");
322
323   /* --- Parse arguments --- */
324
325   for (;;) {
326     static struct option opts[] = {
327       { "help",         0,              0,      'h' },
328       { "version",      0,              0,      'V' },
329       { "usage",        0,              0,      'u' },
330       { "bourne",       0,              0,      'b' },
331       { "cshell",       0,              0,      'c' },
332       { "verify",       OPTF_ARGREQ,    0,      'v' },
333       { 0,              0,              0,      0 }
334     };
335     int i = mdwopt(argc, argv, "hVu bcv:", opts, 0, 0, 0);
336
337     if (i < 0)
338       break;
339     switch (i) {
340       case 'h':
341         help(stdout);
342         exit(0);
343       case 'V':
344         version(stdout);
345         exit(0);
346       case 'u':
347         usage(stdout);
348         exit(0);
349       case 'b':
350         shell = sh_bourne;
351         break;
352       case 'c':
353         shell = sh_csh;
354         break;
355       case 'v':
356         return (!fullcheck(optarg));
357         break;
358       default:
359         duff = 1;
360         break;
361     }
362   }
363
364   if (duff || optind != argc) {
365     usage(stderr);
366     exit(1);
367   }
368
369   /* --- Choose a shell --- */
370
371   if (!shell) {
372     char *p;
373     if (!(p = getenv("SHELL")))
374       p = pw->pw_shell;
375     if (strstr(p, "csh"))
376       shell = sh_csh;
377     else
378       shell = sh_bourne;
379   }
380
381   /* --- Start the checking --- */
382
383   {
384     char *p = goodtmp();
385     if (!p)
386       die(1, "no good tmp directory");
387     switch (shell) {
388       case sh_bourne:
389         printf("TMPDIR=\"%s\"; export TMPDIR\n", p);
390         break;
391       case sh_csh:
392         printf("setenv TMPDIR \"%s\"\n", p);
393         break;
394     }
395   }
396
397   return (0);
398 }
399
400 /*----- That's all, folks -------------------------------------------------*/