chiark / gitweb /
chkpath.c, tmpdir.c, utils.c: Add option to trust private groups.
[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 gid_t mygroup;
57 static struct checkpath cp;
58 static struct passwd *pw;
59 static unsigned flags;
60 #define F_PRIVGRP 1u
61
62 /*----- Main code ---------------------------------------------------------*/
63
64 /* --- @ok@ --- *
65  *
66  * Arguments:   @const char *p@ = pathname to check
67  *              @int *f@ = try-to-create flag
68  *
69  * Returns:     Nonzero if the directory is OK
70  *
71  * Use:         Ensures that a directory is OK.  If @f@ is a real pointer,
72  *              and @*f@ is set, then try to create the directory.
73  */
74
75 static void complain(const char *p, const char *msg, int err)
76 {
77   dstr d = DSTR_INIT;
78
79   if (!cp.cp_verbose) return;
80   dstr_putf(&d, "Path: %s: %s", p, msg);
81   if (err) dstr_putf(&d, ": %s", strerror(err));
82   moan("%s", d.buf);
83   dstr_destroy(&d);
84 }
85
86 static int ok(const char *p, int *f)
87 {
88   struct stat st;
89
90   /* --- Read the directory status --- */
91
92   if (lstat(p, &st)) {
93
94     /* --- Maybe create it if it doesn't exist --- */
95
96     if (errno != ENOENT || !f || !*f) {
97       complain(p, "can't stat", errno);
98       return (0);
99     }
100     if (mkdir(p, 0700)) {
101       complain(p, "can't create", errno);
102       *f = 0;
103       return (0);
104     }
105
106     /* --- Now read the new status --- *
107      *
108      * This fixes a race condition between the previous @lstat@ call and
109      * the @mkdir@.
110      */
111
112     if (lstat(p, &st)) {
113       complain(p, "can't stat after creating", errno);
114       return (0);
115     }
116   }
117
118   /* --- Make sure the directory is good --- *
119    *
120    * It must be a genuine directory (not a link); it must be readable
121    * and writable only by its owner, and that owner must be me.
122    */
123
124   if (!S_ISDIR(st.st_mode))
125     complain(p, "not a directory", 0);
126   else if (st.st_uid != me)
127     complain(p, "not owner", 0);
128   else if (st.st_mode & S_IRWXO)
129     complain(p, "other access permitted", 0);
130   else if (!((flags & F_PRIVGRP) && st.st_gid != mygroup) &&
131            (st.st_mode & S_IRWXG))
132     complain(p, "other-group access permitted", 0);
133   else if (~st.st_mode & S_IRWXU)
134     complain(p, "owner lacks permissions", 0);
135   else
136     return (1);
137   return (0);
138 }
139
140 /* --- @trytmp@ --- *
141  *
142  * Arguments:   @const char *parent@ = parent directory name
143  *              @const char *base@ = subdirectory base name
144  *
145  * Returns:     Pointer to directory name, or null.  (String needs freeing.)
146  *
147  * Use:         Tries to find or create an appropriate temporary directory.
148  */
149
150 static char *trytmp(const char *parent, const char *base)
151 {
152   static char c[] = { "0123456789"
153                       "abcdefghijklmnopqrstuvwxyz"
154                       "ABCDEFGHIJKLMNOPQRSTUVWXYZ" };
155   char *p, *q;
156   char *qq;
157   dstr d = DSTR_INIT;
158   int createflag = 1;
159
160   /* --- Make sure the parent directory is sane --- *
161    *
162    * Must make sure that all the lead-up to the temporary directory is safe.
163    * Otherwise other people can play with symlinks or rename directories
164    * after I've done all this careful work to make the endpoint directory
165    * safe.
166    */
167
168   if (checkpath(parent, &cp))
169     return (0);
170
171   /* --- See whether the trivial version will work --- */
172
173   dstr_putf(&d, "%s/%s", parent, base);
174   if (ok(d.buf, &createflag))
175     goto good;
176
177   /* --- Now try with various suffixes --- */
178
179   DENSURE(&d, 4);
180   qq = d.buf + d.len;
181   *qq++ = '-';
182
183   for (p = c; *p; p++) {
184     qq[0] = *p;
185     qq[1] = 0;
186     if (ok(d.buf, &createflag))
187       goto good;
188     for (q = c; *q; q++) {
189       qq[1] = *q;
190       qq[2] = 0;
191       if (ok(d.buf, &createflag))
192         goto good;
193     }
194   }
195
196   /* --- No joy --- */
197
198   dstr_destroy(&d);
199   return (0);
200
201 good:
202   p = xstrdup(d.buf);
203   dstr_destroy(&d);
204   return (p);
205 }
206
207 /* --- @fullcheck@ --- *
208  *
209  * Arguments:   @const char *p@ = pointer to path to check
210  *
211  * Returns:     Zero if it's a bad directory.
212  *
213  * Use:         Runs a thorough check on a directory.
214  */
215
216 static int fullcheck(const char *p)
217   { return (checkpath(p, &cp) == 0 && ok(p, 0)); }
218
219 /* --- @goodtmp@ --- *
220  *
221  * Arguments:   ---
222  *
223  * Returns:     Pointer to a known-good secure temporary directory, or
224  *              null.
225  *
226  * Use:         Finds a good place to store temporary files.
227  */
228
229 static char *goodtmp(void)
230 {
231   char *p, *q;
232
233   /* --- First of all, try the user's current choice --- */
234
235   if ((p = getenv("TMPDIR")) != 0 && fullcheck(p))
236     return (p);
237
238   /* --- Try making a directory in `/tmp' --- */
239
240   if ((q = trytmp("/tmp", pw->pw_name)) != 0)
241     return (q);
242
243   /* --- That failed: try a directory in the user's home --- */
244
245   if ((q = trytmp(pw->pw_dir, "tmp")) != 0)
246     return (q);
247
248   /* --- Still no joy: give up --- *
249    *
250    * To be fair, if the user can't find a safe place in his own home
251    * directory then he's pretty stuffed.
252    */
253
254   return (0);
255 }
256
257 /* --- @report@ --- */
258
259 static void report(unsigned what, int verbose,
260                    const char *p, const char *msg,
261                    void *arg)
262   { moan("%s", msg); }
263
264 /* --- @usage@ --- */
265
266 static void usage(FILE *fp)
267   { fprintf(fp, "Usage: %s [-Tbcv] [-C PATH] [-g NAME]\n", QUIS); }
268
269 /* --- @version@ --- */
270
271 static void version(FILE *fp)
272   { fprintf(fp, "%s version %s\n", QUIS, VERSION); }
273
274 /* --- @help@ --- */
275
276 static void help(FILE *fp)
277 {
278   version(fp);
279   putc('\n', fp);
280   usage(fp);
281   fputs("\n\
282 Sets a suitable and secure temporary directory, or checks that a given\n\
283 directory is suitable for use with temporary files.  A directory is\n\
284 considered good for a particular user if it's readable and writable only\n\
285 by that user, and if all its parents are modifiable only by the user or\n\
286 root.\n\
287 \n\
288 Options supported:\n\
289 \n\
290 -h, --help              Display this help text.\n\
291 -V, --version           Display the program's version number.\n\
292 -u, --usage             Display a terse usage summary.\n\
293 \n\
294 -C, --check PATH        Check whether PATH is good, setting exit status.\n\
295 -T, --private-group     Accept paths writable by primary group if it has\n\
296                         no other members.\n\
297 -b, --bourne            Output a `TMPDIR' setting for Bourne shell users.\n\
298 -c, --cshell            Output a `TMPDIR' setting for C shell users.\n\
299 -g, --group NAME        Trust group NAME to be honest and true.\n\
300 -v, --verbose           Report problems to standard error.\n\
301 \n\
302 The default action is to examine the caller's shell and output a suitable\n\
303 setting for that shell type.\n\
304 ",
305         fp);
306 }
307
308 /* --- @main@ --- *
309  *
310  * Arguments:   @int argc@ = number of command line arguments
311  *              @char *argv[]@ = the actual argument strings
312  *
313  * Returns:     Zero if all went well, else nonzero.
314  *
315  * Use:         Performs helpful operations on temporary directories.
316  */
317
318 int main(int argc, char *argv[])
319 {
320   int shell = 0;
321   int duff = 0;
322   int i;
323   gid_t gid;
324   char *p;
325
326   enum {
327     sh_unknown,
328     sh_bourne,
329     sh_csh
330   };
331
332   /* --- Initialize variables --- */
333
334   ego(argv[0]);
335   me = cp.cp_uid = geteuid();
336   cp.cp_what = (CP_WRWORLD | CP_WROTHGRP | CP_WROTHUSR |
337                 CP_STICKYOK | CP_REPORT | CP_ERROR);
338   cp.cp_verbose = 0;
339   cp.cp_report = report;
340   cp.cp_gids = 0;                       /* ignore group membership */
341   pw = getpwuid(me);
342   if (!pw)
343     die(1, "you don't exist");
344
345   /* --- Parse arguments --- */
346
347   for (;;) {
348     static struct option opts[] = {
349       { "help",         0,              0,      'h' },
350       { "version",      0,              0,      'V' },
351       { "usage",        0,              0,      'u' },
352       { "check",        OPTF_ARGREQ,    0,      'C' },
353       { "verify",       OPTF_ARGREQ,    0,      'C' },
354       { "private-group", 0,             0,      'T' },
355       { "bourne",       0,              0,      'b' },
356       { "cshell",       0,              0,      'c' },
357       { "group",        OPTF_ARGREQ,    0,      'g' },
358       { "quiet",        0,              0,      'q' },
359       { "verbose",      0,              0,      'v' },
360       { 0,              0,              0,      0 }
361     };
362
363     i = mdwopt(argc, argv, "hVu" "C:Tbcg:qv", opts, 0, 0, 0);
364     if (i < 0)
365       break;
366     switch (i) {
367       case 'h':
368         help(stdout);
369         exit(0);
370       case 'V':
371         version(stdout);
372         exit(0);
373       case 'u':
374         usage(stdout);
375         exit(0);
376       case 'C':
377         return (!fullcheck(optarg));
378         break;
379       case 'T':
380         if (!private_group(&gid, cp.cp_verbose)) {
381           mygroup = gid; flags |= F_PRIVGRP;
382           if (checkpath_addgid(&cp, gid))
383             die(1, "too many groups");
384         }
385         break;
386       case 'b':
387         shell = sh_bourne;
388         break;
389       case 'c':
390         shell = sh_csh;
391         break;
392       case 'g':
393         allowgroup(&cp, optarg);
394         break;
395       case 'q':
396         if (cp.cp_verbose)
397           cp.cp_verbose--;
398         break;
399       case 'v':
400         cp.cp_verbose++;
401         break;
402       default:
403         duff = 1;
404         break;
405     }
406   }
407
408   if (duff || optind != argc) {
409     usage(stderr);
410     exit(1);
411   }
412
413   /* --- Choose a shell --- */
414
415   if (!shell) {
416     if (!(p = getenv("SHELL")))
417       p = pw->pw_shell;
418     if (strstr(p, "csh"))
419       shell = sh_csh;
420     else
421       shell = sh_bourne;
422   }
423
424   /* --- Start the checking --- */
425
426   if ((p = goodtmp()) == 0)
427     die(1, "no good tmp directory");
428   switch (shell) {
429     case sh_bourne:
430       printf("TMPDIR=\"%s\"; export TMPDIR\n", p);
431       break;
432     case sh_csh:
433       printf("setenv TMPDIR \"%s\"\n", p);
434         break;
435   }
436
437   return (0);
438 }
439
440 /*----- That's all, folks -------------------------------------------------*/