/src/dovecot/src/lib/mkdir-parents.c
Line | Count | Source |
1 | | /* Copyright (c) Dovecot authors, see top-level COPYING file */ |
2 | | |
3 | | #include "lib.h" |
4 | | #include "str.h" |
5 | | #include "eacces-error.h" |
6 | | #include "mkdir-parents.h" |
7 | | #include "ipwd.h" |
8 | | |
9 | | #include <sys/stat.h> |
10 | | #include <fcntl.h> |
11 | | #include <unistd.h> |
12 | | |
13 | | mode_t mkdir_get_executable_mode(mode_t mode) |
14 | 0 | { |
15 | | /* add the execute bit if either read or write bit is set */ |
16 | 0 | if ((mode & 0600) != 0) mode |= 0100; |
17 | 0 | if ((mode & 0060) != 0) mode |= 0010; |
18 | 0 | if ((mode & 0006) != 0) mode |= 0001; |
19 | 0 | return mode; |
20 | 0 | } |
21 | | |
22 | | static int ATTR_NULL(5) |
23 | | mkdir_chown_full(const char *path, mode_t mode, uid_t uid, |
24 | | gid_t gid, const char *gid_origin) |
25 | 0 | { |
26 | 0 | string_t *str; |
27 | 0 | mode_t old_mask; |
28 | 0 | unsigned int i; |
29 | 0 | int ret, fd = -1, orig_errno; |
30 | |
|
31 | 0 | for (i = 0;; i++) { |
32 | 0 | old_mask = umask(0); |
33 | 0 | ret = mkdir(path, mode); |
34 | 0 | umask(old_mask); |
35 | 0 | if (ret < 0) |
36 | 0 | break; |
37 | 0 | if (uid == (uid_t)-1 && gid == (gid_t)-1) { |
38 | | /* no changes to owner/group */ |
39 | 0 | return 0; |
40 | 0 | } |
41 | | |
42 | 0 | fd = open(path, O_RDONLY); |
43 | 0 | if (fd != -1) |
44 | 0 | break; |
45 | 0 | if (errno != ENOENT || i == 3) { |
46 | 0 | i_error("open(%s) failed: %m", path); |
47 | 0 | return -1; |
48 | 0 | } |
49 | | /* it was just rmdir()ed by someone else? retry */ |
50 | 0 | } |
51 | | |
52 | 0 | if (ret < 0) { |
53 | 0 | if (errno == EISDIR || errno == ENOSYS) { |
54 | | /* EISDIR check is for BSD/OS which returns it if path |
55 | | contains '/' at the end and it exists. |
56 | | |
57 | | ENOSYS check is for NFS mount points. */ |
58 | 0 | errno = EEXIST; |
59 | 0 | } |
60 | 0 | i_assert(fd == -1); |
61 | 0 | return -1; |
62 | 0 | } |
63 | 0 | if (fchown(fd, uid, gid) < 0) { |
64 | 0 | i_close_fd(&fd); |
65 | 0 | orig_errno = errno; |
66 | 0 | if (rmdir(path) < 0 && errno != ENOENT) |
67 | 0 | i_error("rmdir(%s) failed: %m", path); |
68 | 0 | errno = orig_errno; |
69 | |
|
70 | 0 | if (errno == EPERM && uid == (uid_t)-1) { |
71 | 0 | i_error("%s", eperm_error_get_chgrp("fchown", path, gid, |
72 | 0 | gid_origin)); |
73 | 0 | return -1; |
74 | 0 | } |
75 | | |
76 | 0 | str = t_str_new(256); |
77 | 0 | str_printfa(str, "fchown(%s, %ld", path, |
78 | 0 | uid == (uid_t)-1 ? -1L : (long)uid); |
79 | 0 | if (uid != (uid_t)-1) { |
80 | 0 | struct passwd pw; |
81 | |
|
82 | 0 | if (i_getpwuid(uid, &pw) > 0) |
83 | 0 | str_printfa(str, "(%s)", pw.pw_name); |
84 | |
|
85 | 0 | } |
86 | 0 | str_printfa(str, ", %ld", |
87 | 0 | gid == (gid_t)-1 ? -1L : (long)gid); |
88 | 0 | if (gid != (gid_t)-1) { |
89 | 0 | struct group gr; |
90 | |
|
91 | 0 | if (i_getgrgid(uid, &gr) > 0) |
92 | 0 | str_printfa(str, "(%s)", gr.gr_name); |
93 | 0 | } |
94 | 0 | errno = orig_errno; |
95 | 0 | i_error("%s) failed: %m", str_c(str)); |
96 | 0 | return -1; |
97 | 0 | } |
98 | 0 | if (gid != (gid_t)-1 && (mode & S_ISGID) == 0) { |
99 | | /* make sure the directory doesn't have setgid bit enabled |
100 | | (in case its parent had) */ |
101 | 0 | if (fchmod(fd, mode) < 0) { |
102 | 0 | orig_errno = errno; |
103 | 0 | if (rmdir(path) < 0 && errno != ENOENT) |
104 | 0 | i_error("rmdir(%s) failed: %m", path); |
105 | 0 | errno = orig_errno; |
106 | 0 | i_error("fchmod(%s) failed: %m", path); |
107 | 0 | i_close_fd(&fd); |
108 | 0 | return -1; |
109 | 0 | } |
110 | 0 | } |
111 | 0 | i_close_fd(&fd); |
112 | 0 | return 0; |
113 | 0 | } |
114 | | |
115 | | int mkdir_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) |
116 | 0 | { |
117 | 0 | return mkdir_chown_full(path, mode, uid, gid, NULL); |
118 | 0 | } |
119 | | |
120 | | int mkdir_chgrp(const char *path, mode_t mode, |
121 | | gid_t gid, const char *gid_origin) |
122 | 0 | { |
123 | 0 | return mkdir_chown_full(path, mode, (uid_t)-1, gid, gid_origin); |
124 | 0 | } |
125 | | |
126 | | static int ATTR_NULL(5) |
127 | | mkdir_parents_chown_full(const char *path, mode_t mode, uid_t uid, gid_t gid, |
128 | | const char *gid_origin) |
129 | 0 | { |
130 | 0 | const char *p; |
131 | 0 | int ret; |
132 | |
|
133 | 0 | if (mkdir_chown_full(path, mode, uid, gid, gid_origin) < 0) { |
134 | 0 | if (errno != ENOENT) |
135 | 0 | return -1; |
136 | | |
137 | | /* doesn't exist, try recursively creating our parent dir */ |
138 | 0 | p = strrchr(path, '/'); |
139 | 0 | if (p == NULL || p == path) |
140 | 0 | return -1; /* shouldn't happen */ |
141 | | |
142 | 0 | T_BEGIN { |
143 | 0 | ret = mkdir_parents_chown_full(t_strdup_until(path, p), |
144 | 0 | mode, uid, |
145 | 0 | gid, gid_origin); |
146 | 0 | } T_END; |
147 | 0 | if (ret < 0 && errno != EEXIST) |
148 | 0 | return -1; |
149 | | |
150 | | /* should work now */ |
151 | 0 | if (mkdir_chown_full(path, mode, uid, gid, gid_origin) < 0) |
152 | 0 | return -1; |
153 | 0 | } |
154 | 0 | return 0; |
155 | 0 | } |
156 | | |
157 | | int mkdir_parents_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) |
158 | 0 | { |
159 | 0 | return mkdir_parents_chown_full(path, mode, uid, gid, NULL); |
160 | 0 | } |
161 | | |
162 | | int mkdir_parents_chgrp(const char *path, mode_t mode, |
163 | | gid_t gid, const char *gid_origin) |
164 | 0 | { |
165 | 0 | return mkdir_parents_chown_full(path, mode, (uid_t)-1, gid, gid_origin); |
166 | 0 | } |
167 | | |
168 | | int mkdir_parents(const char *path, mode_t mode) |
169 | 0 | { |
170 | 0 | return mkdir_parents_chown(path, mode, (uid_t)-1, (gid_t)-1); |
171 | 0 | } |
172 | | |
173 | | int stat_first_parent(const char *path, const char **root_dir_r, |
174 | | struct stat *st_r) |
175 | 0 | { |
176 | 0 | const char *p; |
177 | |
|
178 | 0 | while (stat(path, st_r) < 0) { |
179 | 0 | if (errno != ENOENT || strcmp(path, "/") == 0) { |
180 | 0 | *root_dir_r = path; |
181 | 0 | return -1; |
182 | 0 | } |
183 | 0 | p = strrchr(path, '/'); |
184 | 0 | if (p == NULL) |
185 | 0 | path = "/"; |
186 | 0 | else |
187 | 0 | path = t_strdup_until(path, p); |
188 | 0 | } |
189 | 0 | *root_dir_r = path; |
190 | 0 | return 0; |
191 | 0 | } |