chiark / gitweb /
Upgrade build system.
[checkpath] / tmpdir.c
1 /* -*-c-*-
2  *
3  * $Id: tmpdir.c,v 1.5 2004/04/08 01:36:22 mdw Exp $
4  *
5  * Choose and check temporary directories
6  *
7  * (c) 1999 Mark Wooding
8  */
9
10 /*----- Licensing notice --------------------------------------------------*
11  *
12  * This file is part of chkpath.
13  *
14  * chkpath is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later version.
18  *
19  * chkpath is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with chkpath; if not, write to the Free Software Foundation,
26  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27  */
28
29 /*----- Header files ------------------------------------------------------*/
30
31 #include "config.h"
32
33 #include <errno.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <unistd.h>
41 #include <pwd.h>
42
43 #include <mLib/alloc.h>
44 #include <mLib/dstr.h>
45 #include <mLib/mdwopt.h>
46 #include <mLib/quis.h>
47 #include <mLib/report.h>
48
49 #include "checkpath.h"
50
51 /*----- Static variables --------------------------------------------------*/
52
53 static uid_t me;
54 static struct checkpath cp;
55 static struct passwd *pw;
56
57 /*----- Main code ---------------------------------------------------------*/
58
59 /* --- @ok@ --- *
60  *
61  * Arguments:   @const char *p@ = pathname to check
62  *              @int *f@ = try-to-create flag
63  *
64  * Returns:     Nonzero if the directory is OK
65  *
66  * Use:         Ensures that a directory is OK.  If @f@ is a real pointer,
67  *              and @*f@ is set, then try to create the directory.
68  */
69
70 static int ok(const char *p, int *f)
71 {
72   struct stat st;
73
74   /* --- Read the directory status --- */
75
76   if (lstat(p, &st)) {
77
78     /* --- Maybe create it if it doesn't exist --- */
79
80     if (errno != ENOENT || !f || !*f)
81       return (0);
82     if (mkdir(p, 0700)) {
83       *f = 0;
84       return (0);
85     }
86
87     /* --- Now read the new status --- *
88      *
89      * This fixes a race condition between the previous @lstat@ call and
90      * the @mkdir@.
91      */
92
93     if (lstat(p, &st))
94       return (0);
95   }
96
97   /* --- Make sure the directory is good --- *
98    *
99    * It must be a genuine directory (not a link); it must be readable
100    * and writable only by its owner, and that owner must be me.
101    */
102
103   if (S_ISDIR(st.st_mode) && (st.st_mode & 0777) == 0700 && st.st_uid == me)
104     return (1);
105   return (0);
106 }
107
108 /* --- @trytmp@ --- *
109  *
110  * Arguments:   @const char *parent@ = parent directory name
111  *              @const char *base@ = subdirectory base name
112  *
113  * Returns:     Pointer to directory name, or null.  (String needs freeing.)
114  *
115  * Use:         Tries to find or create an appropriate temporary directory.
116  */
117
118 static char *trytmp(const char *parent, const char *base)
119 {
120   static char c[] = { "0123456789"
121                       "abcdefghijklmnopqrstuvwxyz"
122                       "ABCDEFGHIJKLMNOPQRSTUVWXYZ" };
123   char *p, *q;
124   char *qq;
125   dstr d = DSTR_INIT;
126   int createflag = 1;
127
128   /* --- Make sure the parent directory is sane --- *
129    *
130    * Must make sure that all the lead-up to the temporary directory is safe.
131    * Otherwise other people can play with symlinks or rename directories
132    * after I've done all this careful work to make the endpoint directory
133    * safe.
134    */
135
136   if (checkpath(parent, &cp))
137     return (0);
138
139   /* --- See whether the trivial version will work --- */
140
141   dstr_putf(&d, "%s/%s", parent, base);
142   if (ok(d.buf, &createflag))
143     goto good;
144
145   /* --- Now try with various suffixes --- */
146
147   DENSURE(&d, 4);
148   qq = d.buf + d.len;
149   *qq++ = '-';
150
151   for (p = c; *p; p++) {
152     qq[0] = *p;
153     qq[1] = 0;
154     if (ok(d.buf, &createflag))
155       goto good;
156     for (q = c; *q; q++) {
157       qq[1] = *q;
158       qq[2] = 0;
159       if (ok(d.buf, &createflag))
160         goto good;
161     }
162   }
163
164   /* --- No joy --- */
165
166   dstr_destroy(&d);
167   return (0);
168
169 good:
170   p = xstrdup(d.buf);
171   dstr_destroy(&d);
172   return (p);
173 }
174
175 /* --- @fullcheck@ --- *
176  *
177  * Arguments:   @const char *p@ = pointer to path to check
178  *
179  * Returns:     Zero if it's a bad directory.
180  *
181  * Use:         Runs a thorough check on a directory.
182  */
183
184 static int fullcheck(const char *p)
185   { return (checkpath(p, &cp) == 0 && ok(p, 0)); }
186
187 /* --- @goodtmp@ --- *
188  *
189  * Arguments:   ---
190  *
191  * Returns:     Pointer to a known-good secure temporary directory, or
192  *              null.
193  *
194  * Use:         Finds a good place to store temporary files.
195  */
196
197 static char *goodtmp(void)
198 {
199   char *p, *q;
200
201   /* --- First of all, try the user's current choice --- */
202
203   if ((p = getenv("TMPDIR")) != 0 && fullcheck(p))
204     return (p);
205
206   /* --- Try making a directory in `/tmp' --- */
207
208   if ((q = trytmp("/tmp", pw->pw_name)) != 0)
209     return (q);
210
211   /* --- That failed: try a directory in the user's home --- */
212
213   if ((q = trytmp(pw->pw_dir, "tmp")) != 0)
214     return (q);
215
216   /* --- Still no joy: give up --- *
217    *
218    * To be fair, if the user can't find a safe place in his own home
219    * directory then he's pretty stuffed.
220    */
221
222   return (0);
223 }
224
225 /* --- @usage@ --- */
226
227 static void usage(FILE *fp)
228   { fprintf(fp, "Usage: %s [-bc] [-v PATH]\n", QUIS); }
229
230 /* --- @version@ --- */
231
232 static void version(FILE *fp)
233   { fprintf(fp, "%s version %s\n", QUIS, VERSION); }
234
235 /* --- @help@ --- */
236
237 static void help(FILE *fp)
238 {
239   version(fp);
240   putc('\n', fp);
241   usage(fp);
242   fputs("\n\
243 Sets a suitable and secure temporary directory, or checks that a given\n\
244 directory is suitable for use with temporary files.  A directory is\n\
245 considered good for a particular user if it's readable and writable only\n\
246 by that user, and if all its parents are modifiable only by the user or\n\
247 root.\n\
248 \n\
249 Options supported:\n\
250 \n\
251 -h, --help              Display this help text.\n\
252 -V, --version           Display the program's version number.\n\
253 -u, --usage             Display a terse usage summary.\n\
254 \n\
255 -b, --bourne            Output a `TMPDIR' setting for Bourne shell users.\n\
256 -c, --cshell            Output a `TMPDIR' setting for C shell users.\n\
257 -v, --verify PATH       Check whether PATH is good, setting exit status.\n\
258 \n\
259 The default action is to examine the caller's shell and output a suitable\n\
260 setting for that shell type.\n\
261 ",
262         fp);
263 }
264
265 /* --- @main@ --- *
266  *
267  * Arguments:   @int argc@ = number of command line arguments
268  *              @char *argv[]@ = the actual argument strings
269  *
270  * Returns:     Zero if all went well, else nonzero.
271  *
272  * Use:         Performs helpful operations on temporary directories.
273  */
274
275 int main(int argc, char *argv[])
276 {
277   int shell = 0;
278   int duff = 0;
279
280   enum {
281     sh_unknown,
282     sh_bourne,
283     sh_csh
284   };
285
286   /* --- Initialize variables --- */
287
288   ego(argv[0]);
289   me = geteuid();
290   cp.cp_what = CP_WRWORLD | CP_WRGRP | CP_WROTHUSR | CP_STICKYOK;
291   cp.cp_verbose = 0;
292   cp.cp_report = 0;
293   checkpath_setids(&cp);
294   pw = getpwuid(me);
295   if (!pw)
296     die(1, "you don't exist");
297
298   /* --- Parse arguments --- */
299
300   for (;;) {
301     static struct option opts[] = {
302       { "help",         0,              0,      'h' },
303       { "version",      0,              0,      'V' },
304       { "usage",        0,              0,      'u' },
305       { "bourne",       0,              0,      'b' },
306       { "cshell",       0,              0,      'c' },
307       { "verify",       OPTF_ARGREQ,    0,      'v' },
308       { 0,              0,              0,      0 }
309     };
310     int i = mdwopt(argc, argv, "hVu" "bcv:", opts, 0, 0, 0);
311
312     if (i < 0)
313       break;
314     switch (i) {
315       case 'h':
316         help(stdout);
317         exit(0);
318       case 'V':
319         version(stdout);
320         exit(0);
321       case 'u':
322         usage(stdout);
323         exit(0);
324       case 'b':
325         shell = sh_bourne;
326         break;
327       case 'c':
328         shell = sh_csh;
329         break;
330       case 'v':
331         return (!fullcheck(optarg));
332         break;
333       default:
334         duff = 1;
335         break;
336     }
337   }
338
339   if (duff || optind != argc) {
340     usage(stderr);
341     exit(1);
342   }
343
344   /* --- Choose a shell --- */
345
346   if (!shell) {
347     char *p;
348     if (!(p = getenv("SHELL")))
349       p = pw->pw_shell;
350     if (strstr(p, "csh"))
351       shell = sh_csh;
352     else
353       shell = sh_bourne;
354   }
355
356   /* --- Start the checking --- */
357
358   {
359     char *p = goodtmp();
360     if (!p)
361       die(1, "no good tmp directory");
362     switch (shell) {
363       case sh_bourne:
364         printf("TMPDIR=\"%s\"; export TMPDIR\n", p);
365         break;
366       case sh_csh:
367         printf("setenv TMPDIR \"%s\"\n", p);
368         break;
369     }
370   }
371
372   return (0);
373 }
374
375 /*----- That's all, folks -------------------------------------------------*/