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