tmpdir: Ignore environment variables; use effective uid only.
[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 {
184 return (checkpath(p, &cp) == 0 && ok(p, 0));
185 }
186
187 /* --- @goodtmp@ --- *
188 *
189 * Arguments: ---
190 *
191 * Returns: Pointer to a known-good secure temporary directory, or
192 * null.
193 *
194 * Use: Finds a good place to store temporary files.
195 */
196
197 static char *goodtmp(void)
198 {
199 char *p, *q;
200
201 /* --- First of all, try the user's current choice --- */
202
203 if ((p = getenv("TMPDIR")) != 0 && fullcheck(p))
204 return (p);
205
206 /* --- Try making a directory in `/tmp' --- */
207
208 if ((q = trytmp("/tmp", pw->pw_name)) != 0)
209 return (q);
210
211 /* --- That failed: try a directory in the user's home --- */
212
213 if ((q = trytmp(pw->pw_dir, "tmp")) != 0)
214 return (q);
215
216 /* --- Still no joy: give up --- *
217 *
218 * To be fair, if the user can't find a safe place in his own home
219 * directory then he's pretty stuffed.
220 */
221
222 return (0);
223 }
224
225 /* --- @usage@ --- */
226
227 static void usage(FILE *fp)
228 {
229 fprintf(fp, "Usage: %s [-bc] [-v PATH]\n", QUIS);
230 }
231
232 /* --- @version@ --- */
233
234 static void version(FILE *fp)
235 {
236 fprintf(fp, "%s version %s\n", QUIS, VERSION);
237 }
238
239 /* --- @help@ --- */
240
241 static void help(FILE *fp)
242 {
243 version(fp);
244 putc('\n', fp);
245 usage(fp);
246 fputs("\n\
247 Sets a suitable and secure temporary directory, or checks that a given\n\
248 directory is suitable for use with temporary files. A directory is\n\
249 considered good for a particular user if it's readable and writable only\n\
250 by that user, and if all its parents are modifiable only by the user or\n\
251 root.\n\
252 \n\
253 Options supported:\n\
254 \n\
255 -h, --help Display this help text.\n\
256 -V, --version Display the program's version number.\n\
257 -u, --usage Display a terse usage summary.\n\
258 \n\
259 -b, --bourne Output a `TMPDIR' setting for Bourne shell users.\n\
260 -c, --cshell Output a `TMPDIR' setting for C shell users.\n\
261 -v, --verify PATH Check whether PATH is good, setting exit status.\n\
262 \n\
263 The default action is to examine the caller's shell and output a suitable\n\
264 setting for that shell type.\n\
265 ",
266 fp);
267 }
268
269 /* --- @main@ --- *
270 *
271 * Arguments: @int argc@ = number of command line arguments
272 * @char *argv[]@ = the actual argument strings
273 *
274 * Returns: Zero if all went well, else nonzero.
275 *
276 * Use: Performs helpful operations on temporary directories.
277 */
278
279 int main(int argc, char *argv[])
280 {
281 int shell = 0;
282 int duff = 0;
283
284 enum {
285 sh_unknown,
286 sh_bourne,
287 sh_csh
288 };
289
290 /* --- Initialize variables --- */
291
292 ego(argv[0]);
293 me = geteuid();
294 cp.cp_what = CP_WRWORLD | CP_WRGRP | CP_WROTHUSR | CP_STICKYOK;
295 cp.cp_verbose = 0;
296 cp.cp_report = 0;
297 checkpath_setids(&cp);
298 pw = getpwuid(me);
299 if (!pw)
300 die(1, "you don't exist");
301
302 /* --- Parse arguments --- */
303
304 for (;;) {
305 static struct option opts[] = {
306 { "help", 0, 0, 'h' },
307 { "version", 0, 0, 'V' },
308 { "usage", 0, 0, 'u' },
309 { "bourne", 0, 0, 'b' },
310 { "cshell", 0, 0, 'c' },
311 { "verify", OPTF_ARGREQ, 0, 'v' },
312 { 0, 0, 0, 0 }
313 };
314 int i = mdwopt(argc, argv, "hVu bcv:", opts, 0, 0, 0);
315
316 if (i < 0)
317 break;
318 switch (i) {
319 case 'h':
320 help(stdout);
321 exit(0);
322 case 'V':
323 version(stdout);
324 exit(0);
325 case 'u':
326 usage(stdout);
327 exit(0);
328 case 'b':
329 shell = sh_bourne;
330 break;
331 case 'c':
332 shell = sh_csh;
333 break;
334 case 'v':
335 return (!fullcheck(optarg));
336 break;
337 default:
338 duff = 1;
339 break;
340 }
341 }
342
343 if (duff || optind != argc) {
344 usage(stderr);
345 exit(1);
346 }
347
348 /* --- Choose a shell --- */
349
350 if (!shell) {
351 char *p;
352 if (!(p = getenv("SHELL")))
353 p = pw->pw_shell;
354 if (strstr(p, "csh"))
355 shell = sh_csh;
356 else
357 shell = sh_bourne;
358 }
359
360 /* --- Start the checking --- */
361
362 {
363 char *p = goodtmp();
364 if (!p)
365 die(1, "no good tmp directory");
366 switch (shell) {
367 case sh_bourne:
368 printf("TMPDIR=\"%s\"; export TMPDIR\n", p);
369 break;
370 case sh_csh:
371 printf("setenv TMPDIR \"%s\"\n", p);
372 break;
373 }
374 }
375
376 return (0);
377 }
378
379 /*----- That's all, folks -------------------------------------------------*/