chiark / gitweb /
b2881c80144468c476e558f2108247b636467586
[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 = 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 {
229   fprintf(fp, "Usage: %s [-bc] [-v PATH]\n", QUIS);
230 }
231
232 /* --- @version@ --- */
233
234 static void version(FILE *fp)
235 {
236   fprintf(fp, "%s version %s\n", QUIS, VERSION);
237 }
238
239 /* --- @help@ --- */
240
241 static void help(FILE *fp)
242 {
243   version(fp);
244   putc('\n', fp);
245   usage(fp);
246   fputs("\n\
247 Sets a suitable and secure temporary directory, or checks that a given\n\
248 directory is suitable for use with temporary files.  A directory is\n\
249 considered good for a particular user if it's readable and writable only\n\
250 by that user, and if all its parents are modifiable only by the user or\n\
251 root.\n\
252 \n\
253 Options supported:\n\
254 \n\
255 -h, --help              Display this help text.\n\
256 -V, --version           Display the program's version number.\n\
257 -u, --usage             Display a terse usage summary.\n\
258 \n\
259 -b, --bourne            Output a `TMPDIR' setting for Bourne shell users.\n\
260 -c, --cshell            Output a `TMPDIR' setting for C shell users.\n\
261 -v, --verify PATH       Check whether PATH is good, setting exit status.\n\
262 \n\
263 The default action is to examine the caller's shell and output a suitable\n\
264 setting for that shell type.\n\
265 ",
266         fp);
267 }
268
269 /* --- @main@ --- *
270  *
271  * Arguments:   @int argc@ = number of command line arguments
272  *              @char *argv[]@ = the actual argument strings
273  *
274  * Returns:     Zero if all went well, else nonzero.
275  *
276  * Use:         Performs helpful operations on temporary directories.
277  */
278
279 int main(int argc, char *argv[])
280 {
281   int shell = 0;
282   int duff = 0;
283
284   enum {
285     sh_unknown,
286     sh_bourne,
287     sh_csh
288   };
289
290   /* --- Initialize variables --- */
291
292   ego(argv[0]);
293   me = geteuid();
294   cp.cp_what = CP_WRWORLD | CP_WRGRP | CP_WROTHUSR | CP_STICKYOK;
295   cp.cp_verbose = 0;
296   cp.cp_report = 0;
297   checkpath_setids(&cp);
298   pw = getpwuid(me);
299   if (!pw)
300     die(1, "you don't exist");
301
302   /* --- Parse arguments --- */
303
304   for (;;) {
305     static struct option opts[] = {
306       { "help",         0,              0,      'h' },
307       { "version",      0,              0,      'V' },
308       { "usage",        0,              0,      'u' },
309       { "bourne",       0,              0,      'b' },
310       { "cshell",       0,              0,      'c' },
311       { "verify",       OPTF_ARGREQ,    0,      'v' },
312       { 0,              0,              0,      0 }
313     };
314     int i = mdwopt(argc, argv, "hVu bcv:", opts, 0, 0, 0);
315
316     if (i < 0)
317       break;
318     switch (i) {
319       case 'h':
320         help(stdout);
321         exit(0);
322       case 'V':
323         version(stdout);
324         exit(0);
325       case 'u':
326         usage(stdout);
327         exit(0);
328       case 'b':
329         shell = sh_bourne;
330         break;
331       case 'c':
332         shell = sh_csh;
333         break;
334       case 'v':
335         return (!fullcheck(optarg));
336         break;
337       default:
338         duff = 1;
339         break;
340     }
341   }
342
343   if (duff || optind != argc) {
344     usage(stderr);
345     exit(1);
346   }
347
348   /* --- Choose a shell --- */
349
350   if (!shell) {
351     char *p;
352     if (!(p = getenv("SHELL")))
353       p = pw->pw_shell;
354     if (strstr(p, "csh"))
355       shell = sh_csh;
356     else
357       shell = sh_bourne;
358   }
359
360   /* --- Start the checking --- */
361
362   {
363     char *p = goodtmp();
364     if (!p)
365       die(1, "no good tmp directory");
366     switch (shell) {
367       case sh_bourne:
368         printf("TMPDIR=\"%s\"; export TMPDIR\n", p);
369         break;
370       case sh_csh:
371         printf("setenv TMPDIR \"%s\"\n", p);
372         break;
373     }
374   }
375
376   return (0);
377 }
378
379 /*----- That's all, folks -------------------------------------------------*/