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