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