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