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