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