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