chiark / gitweb /
checkpath.c: Move the message buffer into the state structure.
[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 & (S_IRWXG | S_IRWXO))
126     complain(p, "non-owner access permitted", 0);
127   else if (~st.st_mode & S_IRWXU)
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   int i;
315   char *p;
316
317   enum {
318     sh_unknown,
319     sh_bourne,
320     sh_csh
321   };
322
323   /* --- Initialize variables --- */
324
325   ego(argv[0]);
326   me = cp.cp_uid = geteuid();
327   cp.cp_what = (CP_WRWORLD | CP_WROTHGRP | CP_WROTHUSR |
328                 CP_STICKYOK | CP_REPORT | CP_ERROR);
329   cp.cp_verbose = 0;
330   cp.cp_report = report;
331   cp.cp_gids = 0;                       /* ignore group membership */
332   pw = getpwuid(me);
333   if (!pw)
334     die(1, "you don't exist");
335
336   /* --- Parse arguments --- */
337
338   for (;;) {
339     static struct option opts[] = {
340       { "help",         0,              0,      'h' },
341       { "version",      0,              0,      'V' },
342       { "usage",        0,              0,      'u' },
343       { "check",        OPTF_ARGREQ,    0,      'C' },
344       { "verify",       OPTF_ARGREQ,    0,      'C' },
345       { "bourne",       0,              0,      'b' },
346       { "cshell",       0,              0,      'c' },
347       { "group",        OPTF_ARGREQ,    0,      'g' },
348       { "quiet",        0,              0,      'q' },
349       { "verbose",      0,              0,      'v' },
350       { 0,              0,              0,      0 }
351     };
352
353     i = mdwopt(argc, argv, "hVu" "C:bcg:v", opts, 0, 0, 0);
354     if (i < 0)
355       break;
356     switch (i) {
357       case 'h':
358         help(stdout);
359         exit(0);
360       case 'V':
361         version(stdout);
362         exit(0);
363       case 'u':
364         usage(stdout);
365         exit(0);
366       case 'C':
367         return (!fullcheck(optarg));
368         break;
369       case 'b':
370         shell = sh_bourne;
371         break;
372       case 'c':
373         shell = sh_csh;
374         break;
375       case 'g':
376         allowgroup(&cp, optarg);
377         break;
378       case 'q':
379         if (cp.cp_verbose)
380           cp.cp_verbose--;
381         break;
382       case 'v':
383         cp.cp_verbose++;
384         break;
385       default:
386         duff = 1;
387         break;
388     }
389   }
390
391   if (duff || optind != argc) {
392     usage(stderr);
393     exit(1);
394   }
395
396   /* --- Choose a shell --- */
397
398   if (!shell) {
399     if (!(p = getenv("SHELL")))
400       p = pw->pw_shell;
401     if (strstr(p, "csh"))
402       shell = sh_csh;
403     else
404       shell = sh_bourne;
405   }
406
407   /* --- Start the checking --- */
408
409   if ((p = goodtmp()) == 0)
410     die(1, "no good tmp directory");
411   switch (shell) {
412     case sh_bourne:
413       printf("TMPDIR=\"%s\"; export TMPDIR\n", p);
414       break;
415     case sh_csh:
416       printf("setenv TMPDIR \"%s\"\n", p);
417         break;
418   }
419
420   return (0);
421 }
422
423 /*----- That's all, folks -------------------------------------------------*/