1/*++
2/* NAME
3/*	make_dirs 3
4/* SUMMARY
5/*	create directory hierarchy
6/* SYNOPSIS
7/*	#include <make_dirs.h>
8/*
9/*	int	make_dirs(path, perms)
10/*	const char *path;
11/*	int	perms;
12/* DESCRIPTION
13/*	make_dirs() creates the directory specified in \fIpath\fR, and
14/*	creates any missing intermediate directories as well. Directories
15/*	are created with the permissions specified in \fIperms\fR, as
16/*	modified by the process umask.
17/* DIAGNOSTICS:
18/*	Fatal: out of memory. make_dirs() returns 0 in case of success.
19/*	In case of problems. make_dirs() returns -1 and \fIerrno\fR
20/*	reflects the nature of the problem.
21/* SEE ALSO
22/*	mkdir(2)
23/* LICENSE
24/* .ad
25/* .fi
26/*	The Secure Mailer license must be distributed with this software.
27/* AUTHOR(S)
28/*	Wietse Venema
29/*	IBM T.J. Watson Research
30/*	P.O. Box 704
31/*	Yorktown Heights, NY 10598, USA
32/*--*/
33
34/* System library. */
35
36#include <sys_defs.h>
37#include <sys/stat.h>
38#include <errno.h>
39#include <string.h>
40#include <unistd.h>
41
42/* Utility library. */
43
44#include "msg.h"
45#include "mymalloc.h"
46#include "stringops.h"
47#include "make_dirs.h"
48#include "warn_stat.h"
49
50/* make_dirs - create directory hierarchy */
51
52int     make_dirs(const char *path, int perms)
53{
54    const char *myname = "make_dirs";
55    char   *saved_path;
56    unsigned char *cp;
57    int     saved_ch;
58    struct stat st;
59    int     ret;
60    mode_t  saved_mode = 0;
61    gid_t   egid = -1;
62
63    /*
64     * Initialize. Make a copy of the path that we can safely clobber.
65     */
66    cp = (unsigned char *) (saved_path = mystrdup(path));
67
68    /*
69     * I didn't like the 4.4BSD "mkdir -p" implementation, but coming up with
70     * my own took a day, spread out over several days.
71     */
72#define SKIP_WHILE(cond, ptr) { while(*ptr && (cond)) ptr++; }
73
74    SKIP_WHILE(*cp == '/', cp);
75
76    for (;;) {
77	SKIP_WHILE(*cp != '/', cp);
78	if ((saved_ch = *cp) != 0)
79	    *cp = 0;
80	if ((ret = stat(saved_path, &st)) >= 0) {
81	    if (!S_ISDIR(st.st_mode)) {
82		errno = ENOTDIR;
83		ret = -1;
84		break;
85	    }
86	    saved_mode = st.st_mode;
87	} else {
88	    if (errno != ENOENT)
89		break;
90
91	    /*
92	     * mkdir(foo) fails with EEXIST if foo is a symlink.
93	     */
94#if 0
95
96	    /*
97	     * Create a new directory. Unfortunately, mkdir(2) has no
98	     * equivalent of open(2)'s O_CREAT|O_EXCL safety net, so we must
99	     * require that the parent directory is not world writable.
100	     * Detecting a lost race condition after the fact is not
101	     * sufficient, as an attacker could repeat the attack and add one
102	     * directory level at a time.
103	     */
104	    if (saved_mode & S_IWOTH) {
105		msg_warn("refusing to mkdir %s: parent directory is writable by everyone",
106			 saved_path);
107		errno = EPERM;
108		ret = -1;
109		break;
110	    }
111#endif
112	    if ((ret = mkdir(saved_path, perms)) < 0) {
113		if (errno != EEXIST)
114		    break;
115		/* Race condition? */
116		if ((ret = stat(saved_path, &st)) < 0)
117		    break;
118		if (!S_ISDIR(st.st_mode)) {
119		    errno = ENOTDIR;
120		    ret = -1;
121		    break;
122		}
123	    }
124
125	    /*
126	     * Fix directory ownership when mkdir() ignores the effective
127	     * GID. Don't change the effective UID for doing this.
128	     */
129	    if ((ret = stat(saved_path, &st)) < 0) {
130		msg_warn("%s: stat %s: %m", myname, saved_path);
131		break;
132	    }
133	    if (egid == -1)
134		egid = getegid();
135	    if (st.st_gid != egid && (ret = chown(saved_path, -1, egid)) < 0) {
136		msg_warn("%s: chgrp %s: %m", myname, saved_path);
137		break;
138	    }
139	}
140	if (saved_ch != 0)
141	    *cp = saved_ch;
142	SKIP_WHILE(*cp == '/', cp);
143	if (*cp == 0)
144	    break;
145    }
146
147    /*
148     * Cleanup.
149     */
150    myfree(saved_path);
151    return (ret);
152}
153
154#ifdef TEST
155
156 /*
157  * Test program. Usage: make_dirs path...
158  */
159#include <stdlib.h>
160#include <msg_vstream.h>
161
162int     main(int argc, char **argv)
163{
164    msg_vstream_init(argv[0], VSTREAM_ERR);
165    if (argc < 2)
166	msg_fatal("usage: %s path...", argv[0]);
167    while (--argc > 0 && *++argv != 0)
168	if (make_dirs(*argv, 0755))
169	    msg_fatal("%s: %m", *argv);
170    exit(0);
171}
172
173#endif
174