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