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