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