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