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