1 /* $NetBSD: make_dirs.c,v 1.1.1.4 2023/12/23 20:24:59 christos 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 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; 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 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