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