Coverage Report

Created: 2025-08-29 06:11

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