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