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