chiark / gitweb /
tmpdir: Use checkpath functions.
[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 = cp.cp_uid = 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   cp.cp_gids = 0;                       /* ignore group membership */
346   pw = getpwuid(me);
347   if (!pw)
348     die(1, "you don't exist");
349
350   /* --- Parse arguments --- */
351
352   for (;;) {
353     static struct option opts[] = {
354       { "help",         0,              0,      'h' },
355       { "version",      0,              0,      'V' },
356       { "usage",        0,              0,      'u' },
357       { "bourne",       0,              0,      'b' },
358       { "cshell",       0,              0,      'c' },
359       { "check",        OPTF_ARGREQ,    0,      'C' },
360       { "verify",       OPTF_ARGREQ,    0,      'C' },
361       { "verbose",      0,              0,      'v' },
362       { "trust-groups", 0,              0,      't' },
363       { "group",        OPTF_ARGREQ,    0,      'g' },
364       { 0,              0,              0,      0 }
365     };
366     int i = mdwopt(argc, argv, "hVu" "bcvtg:c:", opts, 0, 0, 0);
367
368     if (i < 0)
369       break;
370     switch (i) {
371       case 'h':
372         help(stdout);
373         exit(0);
374       case 'V':
375         version(stdout);
376         exit(0);
377       case 'u':
378         usage(stdout);
379         exit(0);
380       case 'b':
381         shell = sh_bourne;
382         break;
383       case 'c':
384         shell = sh_csh;
385         break;
386       case 'C':
387         return (!fullcheck(optarg));
388         break;
389       case 'g':
390         allowgroup(optarg);
391         break;
392       case 'v':
393         cp.cp_verbose++;
394         break;
395       default:
396         duff = 1;
397         break;
398     }
399   }
400
401   if (duff || optind != argc) {
402     usage(stderr);
403     exit(1);
404   }
405
406   /* --- Choose a shell --- */
407
408   if (!shell) {
409     if (!(p = getenv("SHELL")))
410       p = pw->pw_shell;
411     if (strstr(p, "csh"))
412       shell = sh_csh;
413     else
414       shell = sh_bourne;
415   }
416
417   /* --- Start the checking --- */
418
419   if ((p = goodtmp()) == 0)
420     die(1, "no good tmp directory");
421   switch (shell) {
422     case sh_bourne:
423       printf("TMPDIR=\"%s\"; export TMPDIR\n", p);
424       break;
425     case sh_csh:
426       printf("setenv TMPDIR \"%s\"\n", p);
427         break;
428   }     
429
430   return (0);
431 }
432
433 /*----- That's all, folks -------------------------------------------------*/