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