1 /* $NetBSD: make_dirs.c,v 1.1.1.2 2011/03/02 19:32:44 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 51 /* make_dirs - create directory hierarchy */ 52 53 int make_dirs(const char *path, int perms) 54 { 55 const char *myname = "make_dirs"; 56 char *saved_path; 57 unsigned char *cp; 58 int saved_ch; 59 struct stat st; 60 int ret; 61 mode_t saved_mode = 0; 62 gid_t egid = -1; 63 64 /* 65 * Initialize. Make a copy of the path that we can safely clobber. 66 */ 67 cp = (unsigned char *) (saved_path = mystrdup(path)); 68 69 /* 70 * I didn't like the 4.4BSD "mkdir -p" implementation, but coming up with 71 * my own took a day, spread out over several days. 72 */ 73 #define SKIP_WHILE(cond, ptr) { while(*ptr && (cond)) ptr++; } 74 75 SKIP_WHILE(*cp == '/', cp); 76 77 for (;;) { 78 SKIP_WHILE(*cp != '/', cp); 79 if ((saved_ch = *cp) != 0) 80 *cp = 0; 81 if ((ret = stat(saved_path, &st)) >= 0) { 82 if (!S_ISDIR(st.st_mode)) { 83 errno = ENOTDIR; 84 ret = -1; 85 break; 86 } 87 saved_mode = st.st_mode; 88 } else { 89 if (errno != ENOENT) 90 break; 91 92 /* 93 * mkdir(foo) fails with EEXIST if foo is a symlink. 94 */ 95 #if 0 96 97 /* 98 * Create a new directory. Unfortunately, mkdir(2) has no 99 * equivalent of open(2)'s O_CREAT|O_EXCL safety net, so we must 100 * require that the parent directory is not world writable. 101 * Detecting a lost race condition after the fact is not 102 * sufficient, as an attacker could repeat the attack and add one 103 * directory level at a time. 104 */ 105 if (saved_mode & S_IWOTH) { 106 msg_warn("refusing to mkdir %s: parent directory is writable by everyone", 107 saved_path); 108 errno = EPERM; 109 ret = -1; 110 break; 111 } 112 #endif 113 if ((ret = mkdir(saved_path, perms)) < 0) { 114 if (errno != EEXIST) 115 break; 116 /* Race condition? */ 117 if ((ret = stat(saved_path, &st)) < 0) 118 break; 119 if (!S_ISDIR(st.st_mode)) { 120 errno = ENOTDIR; 121 ret = -1; 122 break; 123 } 124 } 125 126 /* 127 * Fix directory ownership when mkdir() ignores the effective 128 * GID. Don't change the effective UID for doing this. 129 */ 130 if ((ret = stat(saved_path, &st)) < 0) { 131 msg_warn("%s: stat %s: %m", myname, saved_path); 132 break; 133 } 134 if (egid == -1) 135 egid = getegid(); 136 if (st.st_gid != egid && (ret = chown(saved_path, -1, egid)) < 0) { 137 msg_warn("%s: chgrp %s: %m", myname, saved_path); 138 break; 139 } 140 } 141 if (saved_ch != 0) 142 *cp = saved_ch; 143 SKIP_WHILE(*cp == '/', cp); 144 if (*cp == 0) 145 break; 146 } 147 148 /* 149 * Cleanup. 150 */ 151 myfree(saved_path); 152 return (ret); 153 } 154 155 #ifdef TEST 156 157 /* 158 * Test program. Usage: make_dirs path... 159 */ 160 #include <stdlib.h> 161 #include <msg_vstream.h> 162 163 int main(int argc, char **argv) 164 { 165 msg_vstream_init(argv[0], VSTREAM_ERR); 166 if (argc < 2) 167 msg_fatal("usage: %s path...", argv[0]); 168 while (--argc > 0 && *++argv != 0) 169 if (make_dirs(*argv, 0755)) 170 msg_fatal("%s: %m", *argv); 171 exit(0); 172 } 173 174 #endif 175