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