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