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