1 /*
2 * ProFTPD - FTP server daemon
3 * Copyright (c) 2003-2017 The ProFTPD Project team
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
18 *
19 * As a special exemption, The ProFTPD Project team and other respective
20 * copyright holders give permission to link this program with OpenSSL, and
21 * distribute the resulting executable, without including the source code for
22 * OpenSSL in the source distribution.
23 */
24
25 /* Home-on-demand support */
26
27 #include "conf.h"
28 #include "privs.h"
29
30 static const char *trace_channel = "mkhome";
31
create_dir(const char * dir,uid_t uid,gid_t gid,mode_t mode)32 static int create_dir(const char *dir, uid_t uid, gid_t gid,
33 mode_t mode) {
34 mode_t prev_mask;
35 struct stat st;
36 int res = -1;
37
38 pr_fs_clear_cache2(dir);
39 res = pr_fsio_stat(dir, &st);
40
41 if (res == -1 &&
42 errno != ENOENT) {
43 int xerrno = errno;
44
45 pr_log_pri(PR_LOG_WARNING, "error checking '%s': %s", dir,
46 strerror(xerrno));
47
48 errno = xerrno;
49 return -1;
50 }
51
52 /* The directory already exists. */
53 if (res == 0) {
54 pr_trace_msg(trace_channel, 8, "'%s' already exists", dir);
55 pr_log_debug(DEBUG3, "CreateHome: '%s' already exists", dir);
56 return 0;
57 }
58
59 /* The given mode is absolute, not subject to any Umask setting. */
60 prev_mask = umask(0);
61
62 if (pr_fsio_mkdir(dir, mode) < 0) {
63 int xerrno = errno;
64
65 umask(prev_mask);
66 pr_log_pri(PR_LOG_WARNING, "error creating '%s': %s", dir,
67 strerror(xerrno));
68
69 errno = xerrno;
70 return -1;
71 }
72
73 umask(prev_mask);
74
75 if (pr_fsio_chown(dir, uid, gid) < 0) {
76 int xerrno = errno;
77
78 pr_log_pri(PR_LOG_WARNING, "error setting ownership of '%s': %s", dir,
79 strerror(xerrno));
80
81 errno = xerrno;
82 return -1;
83 }
84
85 pr_trace_msg(trace_channel, 8, "directory '%s' created", dir);
86 pr_log_debug(DEBUG6, "CreateHome: directory '%s' created", dir);
87 return 0;
88 }
89
90 /* Walk along a path, making sure that all directories in that path exist,
91 * creating them if necessary.
92 */
create_path(pool * p,const char * path,const char * user,uid_t dir_uid,gid_t dir_gid,mode_t dir_mode,uid_t dst_uid,gid_t dst_gid,mode_t dst_mode)93 static int create_path(pool *p, const char *path, const char *user,
94 uid_t dir_uid, gid_t dir_gid, mode_t dir_mode,
95 uid_t dst_uid, gid_t dst_gid, mode_t dst_mode) {
96 char *currpath = NULL, *tmppath = NULL;
97 struct stat st;
98
99 pr_fs_clear_cache2(path);
100 if (pr_fsio_stat(path, &st) == 0) {
101 /* Path already exists, nothing to be done. */
102 errno = EEXIST;
103 return -1;
104 }
105
106 /* The special-case values of -1 for dir UID/GID mean that the destination
107 * UID/GID should be used for the parent directories.
108 */
109
110 if (dir_uid == (uid_t) -1) {
111 dir_uid = dst_uid;
112 }
113
114 if (dir_gid == (gid_t) -1) {
115 dir_gid = dst_gid;
116 }
117
118 pr_trace_msg(trace_channel, 5, "creating home directory '%s' for user '%s'",
119 path, user);
120 pr_log_debug(DEBUG3, "creating home directory '%s' for user '%s'", path,
121 user);
122 tmppath = pstrdup(p, path);
123
124 currpath = "/";
125 while (tmppath && *tmppath) {
126 char *currdir = strsep(&tmppath, "/");
127 currpath = pdircat(p, currpath, currdir, NULL);
128
129 /* If tmppath is NULL, we are creating the last part of the path, so we
130 * use the configured mode, and chown it to the given UID and GID.
131 */
132 if (tmppath == NULL ||
133 (*tmppath == '\0')) {
134 create_dir(currpath, dst_uid, dst_gid, dst_mode);
135
136 } else {
137 create_dir(currpath, dir_uid, dir_gid, dir_mode);
138 }
139
140 pr_signals_handle();
141 }
142
143 pr_trace_msg(trace_channel, 5, "home directory '%s' created", path);
144 pr_log_debug(DEBUG3, "home directory '%s' created", path);
145 return 0;
146 }
147
copy_symlink(pool * p,const char * src_dir,const char * src_path,const char * dst_dir,const char * dst_path,uid_t uid,gid_t gid)148 static int copy_symlink(pool *p, const char *src_dir, const char *src_path,
149 const char *dst_dir, const char *dst_path, uid_t uid, gid_t gid) {
150 char *link_path = pcalloc(p, PR_TUNABLE_BUFFER_SIZE);
151 int len;
152
153 len = pr_fsio_readlink(src_path, link_path, PR_TUNABLE_BUFFER_SIZE-1);
154 if (len < 0) {
155 int xerrno = errno;
156
157 pr_log_pri(PR_LOG_WARNING, "CreateHome: error reading link '%s': %s",
158 src_path, strerror(xerrno));
159
160 errno = xerrno;
161 return -1;
162 }
163 link_path[len] = '\0';
164
165 /* If the target of the link lies within the src path, rename that portion
166 * of the link to be the corresponding part of the dst path.
167 */
168 if (strncmp(link_path, src_dir, strlen(src_dir)) == 0) {
169 link_path = pdircat(p, dst_dir, link_path + strlen(src_dir), NULL);
170 }
171
172 if (pr_fsio_symlink(link_path, dst_path) < 0) {
173 int xerrno = errno;
174
175 pr_log_pri(PR_LOG_WARNING, "CreateHome: error symlinking '%s' to '%s': %s",
176 link_path, dst_path, strerror(xerrno));
177
178 errno = xerrno;
179 return -1;
180 }
181
182 /* Make sure the new symlink has the proper ownership. */
183 if (pr_fsio_chown(dst_path, uid, gid) < 0) {
184 pr_log_pri(PR_LOG_WARNING, "CreateHome: error chown'ing '%s' to %s/%s: %s",
185 dst_path, pr_uid2str(p, uid), pr_gid2str(p, gid), strerror(errno));
186 }
187
188 return 0;
189 }
190
191 /* srcdir is to be considered a "skeleton" directory, in the manner of
192 * /etc/skel, and destdir is a user's newly created home directory that needs
193 * to be populated with the files in srcdir.
194 */
copy_dir(pool * p,const char * src_dir,const char * dst_dir,uid_t uid,gid_t gid)195 static int copy_dir(pool *p, const char *src_dir, const char *dst_dir,
196 uid_t uid, gid_t gid) {
197 DIR *dh = NULL;
198 struct dirent *dent = NULL;
199
200 dh = opendir(src_dir);
201 if (dh == NULL) {
202 int xerrno = errno;
203
204 pr_log_pri(PR_LOG_WARNING, "CreateHome: error copying '%s' skel files: %s",
205 src_dir, strerror(xerrno));
206
207 errno = xerrno;
208 return -1;
209 }
210
211 while ((dent = readdir(dh)) != NULL) {
212 struct stat st;
213 char *src_path, *dst_path;
214
215 pr_signals_handle();
216
217 /* Skip "." and ".." */
218 if (strncmp(dent->d_name, ".", 2) == 0 ||
219 strncmp(dent->d_name, "..", 3) == 0) {
220 continue;
221 }
222
223 src_path = pdircat(p, src_dir, dent->d_name, NULL);
224 dst_path = pdircat(p, dst_dir, dent->d_name, NULL);
225
226 if (pr_fsio_lstat(src_path, &st) < 0) {
227 pr_log_debug(DEBUG3, "CreateHome: unable to stat '%s' (%s), skipping",
228 src_path, strerror(errno));
229 continue;
230 }
231
232 /* Is this path to a directory? */
233 if (S_ISDIR(st.st_mode)) {
234 create_dir(dst_path, uid, gid, st.st_mode);
235 copy_dir(p, src_path, dst_path, uid, gid);
236 continue;
237
238 /* Is this path to a regular file? */
239 } else if (S_ISREG(st.st_mode)) {
240 mode_t dst_mode = st.st_mode;
241
242 /* Make sure to prevent S{U,G}ID permissions on target files. */
243
244 if (dst_mode & S_ISUID) {
245 dst_mode &= ~S_ISUID;
246 }
247
248 if (dst_mode & S_ISGID) {
249 dst_mode &= ~S_ISGID;
250 }
251
252 (void) pr_fs_copy_file(src_path, dst_path);
253
254 /* Make sure the destination file has the proper ownership and mode. */
255 if (pr_fsio_chown(dst_path, uid, gid) < 0) {
256 pr_log_pri(PR_LOG_WARNING, "CreateHome: error chown'ing '%s' "
257 "to %s/%s: %s", dst_path, pr_uid2str(p, uid), pr_gid2str(p, gid),
258 strerror(errno));
259 }
260
261 if (pr_fsio_chmod(dst_path, dst_mode) < 0) {
262 pr_log_pri(PR_LOG_WARNING, "CreateHome: error chmod'ing '%s' to "
263 "%04o: %s", dst_path, (unsigned int) dst_mode, strerror(errno));
264 }
265
266 continue;
267
268 /* Is this path a symlink? */
269 } else if (S_ISLNK(st.st_mode)) {
270 copy_symlink(p, src_dir, src_path, dst_dir, dst_path, uid, gid);
271 continue;
272
273 /* All other file types are skipped */
274 } else {
275 pr_log_debug(DEBUG3, "CreateHome: skipping skel file '%s'", src_path);
276 continue;
277 }
278 }
279
280 closedir(dh);
281 return 0;
282 }
283
284 /* Check for a CreateHome directive, and act on it if present. If not, do
285 * nothing.
286 */
create_home(pool * p,const char * home,const char * user,uid_t uid,gid_t gid)287 int create_home(pool *p, const char *home, const char *user, uid_t uid,
288 gid_t gid) {
289 int res;
290 unsigned long flags = 0;
291 config_rec *c;
292 mode_t dir_mode, dst_mode;
293 uid_t dir_uid, dst_uid;
294 gid_t dir_gid, dst_gid, home_gid;
295
296 c = find_config(main_server->conf, CONF_PARAM, "CreateHome", FALSE);
297 if (c == NULL ||
298 (c && *((unsigned char *) c->argv[0]) == FALSE)) {
299 return 0;
300 }
301
302 /* Create the configured path. */
303
304 dir_uid = *((uid_t *) c->argv[4]);
305 dir_gid = *((gid_t *) c->argv[5]);
306 dir_mode = *((mode_t *) c->argv[2]);
307 home_gid = *((gid_t *) c->argv[6]);
308 flags = *((unsigned long *) c->argv[7]);
309
310 dst_uid = uid;
311 dst_gid = (home_gid == (gid_t) -1) ? gid : home_gid;
312
313 dst_mode = *((mode_t *) c->argv[1]);
314
315 if (flags & PR_MKHOME_FL_USE_USER_PRIVS) {
316 /* Make sure we are the actual end user here (Issue#568). Without this,
317 * we will not be using root privs, true, but we will not be creating
318 * the directory as the logging-in user; we will be creating the directory
319 * using the User/Group identity, which is not expected.
320 */
321 PRIVS_USER
322
323 } else {
324 PRIVS_ROOT
325 }
326
327 pr_event_generate("core.creating-home", user);
328
329 res = create_path(p, home, user, dir_uid, dir_gid, dir_mode,
330 dst_uid, dst_gid, dst_mode);
331
332 if (res < 0 &&
333 errno != EEXIST) {
334 int xerrno = errno;
335
336 PRIVS_RELINQUISH
337
338 errno = xerrno;
339 return -1;
340 }
341
342 if (res == 0 &&
343 c->argv[3]) {
344 char *skel_dir = c->argv[3];
345
346 /* Populate the home directory with files from the configured
347 * skeleton (a la /etc/skel) directory.
348 */
349
350 pr_trace_msg(trace_channel, 9, "copying skel files from '%s' into '%s'",
351 skel_dir, home);
352 pr_log_debug(DEBUG4, "CreateHome: copying skel files from '%s' into '%s'",
353 skel_dir, home);
354
355 pr_event_generate("core.copying-skel", user);
356
357 if (copy_dir(p, skel_dir, home, uid, gid) < 0) {
358 pr_log_debug(DEBUG4, "CreateHome: error copying skel files");
359
360 } else {
361 pr_event_generate("core.copied-skel", user);
362 }
363 }
364
365 if (res == 0) {
366 pr_event_generate("core.created-home", user);
367 }
368
369 PRIVS_RELINQUISH
370 return 0;
371 }
372