Home | History | Annotate | Line # | Download | only in util
      1 /*	$NetBSD: make_dirs.c,v 1.2 2023/12/23 20:30:46 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