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