tmpdir: Use checkpath functions.
[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 = cp.cp_uid = 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 cp.cp_gids = 0; /* ignore group membership */
346 pw = getpwuid(me);
347 if (!pw)
348 die(1, "you don't exist");
349
350 /* --- Parse arguments --- */
351
352 for (;;) {
353 static struct option opts[] = {
354 { "help", 0, 0, 'h' },
355 { "version", 0, 0, 'V' },
356 { "usage", 0, 0, 'u' },
357 { "bourne", 0, 0, 'b' },
358 { "cshell", 0, 0, 'c' },
359 { "check", OPTF_ARGREQ, 0, 'C' },
360 { "verify", OPTF_ARGREQ, 0, 'C' },
361 { "verbose", 0, 0, 'v' },
362 { "trust-groups", 0, 0, 't' },
363 { "group", OPTF_ARGREQ, 0, 'g' },
364 { 0, 0, 0, 0 }
365 };
366 int i = mdwopt(argc, argv, "hVu" "bcvtg:c:", opts, 0, 0, 0);
367
368 if (i < 0)
369 break;
370 switch (i) {
371 case 'h':
372 help(stdout);
373 exit(0);
374 case 'V':
375 version(stdout);
376 exit(0);
377 case 'u':
378 usage(stdout);
379 exit(0);
380 case 'b':
381 shell = sh_bourne;
382 break;
383 case 'c':
384 shell = sh_csh;
385 break;
386 case 'C':
387 return (!fullcheck(optarg));
388 break;
389 case 'g':
390 allowgroup(optarg);
391 break;
392 case 'v':
393 cp.cp_verbose++;
394 break;
395 default:
396 duff = 1;
397 break;
398 }
399 }
400
401 if (duff || optind != argc) {
402 usage(stderr);
403 exit(1);
404 }
405
406 /* --- Choose a shell --- */
407
408 if (!shell) {
409 if (!(p = getenv("SHELL")))
410 p = pw->pw_shell;
411 if (strstr(p, "csh"))
412 shell = sh_csh;
413 else
414 shell = sh_bourne;
415 }
416
417 /* --- Start the checking --- */
418
419 if ((p = goodtmp()) == 0)
420 die(1, "no good tmp directory");
421 switch (shell) {
422 case sh_bourne:
423 printf("TMPDIR=\"%s\"; export TMPDIR\n", p);
424 break;
425 case sh_csh:
426 printf("setenv TMPDIR \"%s\"\n", p);
427 break;
428 }
429
430 return (0);
431 }
432
433 /*----- That's all, folks -------------------------------------------------*/