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