Coverage Report

Created: 2026-07-16 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}