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