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