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