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