chiark / gitweb /
574e16f8ecdb07f27c563d90098c51792f40e4b1
[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] [-C PATH] [-g NAME]\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 -C, --check PATH        Check whether PATH is good, setting exit status.\n\
289 -b, --bourne            Output a `TMPDIR' setting for Bourne shell users.\n\
290 -c, --cshell            Output a `TMPDIR' setting for C shell users.\n\
291 -g, --group NAME        Trust group NAME to be honest and true.\n\
292 -v, --verbose           Report problems to standard error.\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       { "check",        OPTF_ARGREQ,    0,      'C' },
343       { "verify",       OPTF_ARGREQ,    0,      'C' },
344       { "bourne",       0,              0,      'b' },
345       { "cshell",       0,              0,      'c' },
346       { "group",        OPTF_ARGREQ,    0,      'g' },
347       { "verbose",      0,              0,      'v' },
348       { 0,              0,              0,      0 }
349     };
350     int i = mdwopt(argc, argv, "hVu" "C:bcg:v", opts, 0, 0, 0);
351
352     if (i < 0)
353       break;
354     switch (i) {
355       case 'h':
356         help(stdout);
357         exit(0);
358       case 'V':
359         version(stdout);
360         exit(0);
361       case 'u':
362         usage(stdout);
363         exit(0);
364       case 'C':
365         return (!fullcheck(optarg));
366         break;
367       case 'b':
368         shell = sh_bourne;
369         break;
370       case 'c':
371         shell = sh_csh;
372         break;
373       case 'g':
374         allowgroup(&cp, optarg);
375         break;
376       case 'v':
377         cp.cp_verbose++;
378         break;
379       default:
380         duff = 1;
381         break;
382     }
383   }
384
385   if (duff || optind != argc) {
386     usage(stderr);
387     exit(1);
388   }
389
390   /* --- Choose a shell --- */
391
392   if (!shell) {
393     if (!(p = getenv("SHELL")))
394       p = pw->pw_shell;
395     if (strstr(p, "csh"))
396       shell = sh_csh;
397     else
398       shell = sh_bourne;
399   }
400
401   /* --- Start the checking --- */
402
403   if ((p = goodtmp()) == 0)
404     die(1, "no good tmp directory");
405   switch (shell) {
406     case sh_bourne:
407       printf("TMPDIR=\"%s\"; export TMPDIR\n", p);
408       break;
409     case sh_csh:
410       printf("setenv TMPDIR \"%s\"\n", p);
411         break;
412   }
413
414   return (0);
415 }
416
417 /*----- That's all, folks -------------------------------------------------*/