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