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 -------------------------------------------------*/