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