• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

proftpd / proftpd / 30135626854

25 Jul 2026 12:15AM UTC coverage: 93.034% (+0.6%) from 92.428%
30135626854

push

github

51363 of 55209 relevant lines covered (93.03%)

219.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

27.74
/src/privs.c
1
/*
2
 * ProFTPD - FTP server daemon
3
 * Copyright (c) 2009-2026 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, see <https://www.gnu.org/licenses/>.
17
 *
18
 * As a special exemption, The ProFTPD Project and other respective copyright
19
 * holders give permission to link this program with OpenSSL, and distribute
20
 * the resulting executable, without including the source code for OpenSSL in
21
 * the source distribution.
22
 */
23

24
#include "conf.h"
25
#include "privs.h"
26

27
/* If proftpd was started up without root privs, then this is set to TRUE.
28
 * It is used to prevent spamming the logs with error messages about being
29
 * unable to switch privs.
30
 */
31
static int nonroot_daemon = FALSE;
32

33
/* Functions for manipulating saved, real and effective UID for easy switching
34
 * from/to root.
35
 *
36
 * Note: In version 1.1.5, all of this changed.  We USED to play games with
37
 * the saved-UID/GID AND setreuid()/setregid(); however this appears to be
38
 * slightly non-portable (i.e. w/ BSDs).  Since POSIX.1 saved-UIDs are pretty
39
 * much useless without setre* (in the case of root), we now use basic UID
40
 * swapping if we have seteuid(), and setreuid() swapping if not.
41
 *
42
 * If seteuid() is present, we set the saved UID/GID using setuid/seteuid().
43
 * setreuid() is no longer used as it is considered obsolete on many systems.
44
 * GIDS are also no longer swapped, as they are unnecessary.
45
 *
46
 * If run as root, proftpd now normally runs as:
47
 *   real user            : root
48
 *   effective user       : <user>
49
 *   saved user           : root
50
 *   real/eff/saved group : <group>
51
 */
52

53
/* Porters, please put the most reasonable and secure method of
54
 * doing this in here.
55
 */
56

57
static const char *trace_channel = "privs";
58

59
/* We keep a count of the number of times PRIVS_ROOT/PRIVS_RELINQUISH have
60
 * been called.  This allows for nesting calls to PRIVS_ROOT/PRIVS_RELINQUISH,
61
 * so that the last PRIVS_RELINQUISH call actually releases the privs.
62
 */
63
static unsigned int root_privs = 0;
64
static unsigned int user_privs = 0;
65

66
static void privs_log_error(const char *msg, int xerrno) {
67
  if (xerrno == EPERM) {
×
68
    pr_log_debug(DEBUG2, "%s: %s", msg, strerror(xerrno));
×
69

×
70
  } else {
71
    pr_log_pri(PR_LOG_ERR, "%s: %s", msg, strerror(xerrno));
72
  }
×
73
}
74

×
75
int pr_privs_setup(uid_t uid, gid_t gid, const char *file, int lineno) {
76
  if (nonroot_daemon == TRUE) {
×
77
    session.ouid = session.uid = getuid();
×
78
    session.gid = getgid();
×
79

×
80
    pr_trace_msg(trace_channel, 9,
81
      "PRIVS_SETUP called at %s:%d for nonroot daemon, ignoring", file, lineno);
×
82
    return 0;
83
  }
×
84

85
  pr_log_debug(DEBUG9, "SETUP PRIVS at %s:%d", file, lineno);
86

×
87
  /* Reset the user/root privs counters. */
88
  root_privs = user_privs = 0;
89
  pr_trace_msg(trace_channel, 9, "PRIVS_SETUP called, "
×
90
    "resetting user/root privs count");
×
91

92
  pr_signals_block();
93

×
94
  if (getuid() != PR_ROOT_UID) {
95
    session.ouid = session.uid = getuid();
×
96
    session.gid = getgid();
×
97

×
98
    if (setgid(session.gid) < 0) {
99
      privs_log_error("SETUP PRIVS: unable to setgid()", errno);
×
100
    }
×
101

102
#if defined(HAVE_SETEUID)
103
    if (setuid(session.uid) < 0) {
104
      privs_log_error("SETUP PRIVS: unable to setuid()", errno);
×
105
    }
×
106

107
    if (seteuid(session.uid) < 0) {
108
      privs_log_error("SETUP PRIVS: unable to seteuid()", errno);
×
109
    }
×
110
#else
111
    if (setreuid(session.uid, session.uid) < 0) {
112
      privs_log_error("SETUP PRIVS: unable to setreuid()", errno);
113
    }
114
#endif /* !HAVE_SETEUID */
115

116
  } else {
117
    session.ouid = getuid();
118
    session.uid = uid;
×
119
    session.gid = gid;
×
120

×
121
#if defined(HAVE_SETEUID)
122
    if (setuid(PR_ROOT_UID) < 0) {
123
      privs_log_error("SETUP PRIVS: unable to setuid()", errno);
×
124
    }
×
125

126
    if (setgid(gid) < 0) {
127
      privs_log_error("SETUP PRIVS: unable to setgid()", errno);
×
128
    }
×
129

130
    if (seteuid(uid) < 0) {
131
      privs_log_error("SETUP PRIVS: unable to seteuid()", errno);
×
132
    }
×
133
#else
134
    if (setgid(session.gid) < 0) {
135
      privs_log_error("SETUP PRIVS: unable to setgid()", errno);
136
    }
137

138
    if (setreuid(PR_ROOT_UID, session.uid) < 0) {
139
      privs_log_error("SETUP PRIVS: unable to setreuid()", errno);
140
    }
141
#endif /* !HAVE_SETEUID */
142
  }
143

144
  pr_signals_unblock();
145
  return 0;
×
146
}
×
147

148
int pr_privs_root(const char *file, int lineno) {
149
  if (nonroot_daemon == TRUE) {
18✔
150
    pr_trace_msg(trace_channel, 9,
18✔
151
      "PRIVS_ROOT called at %s:%d for nonroot daemon, ignoring", file, lineno);
×
152
    return 0;
153
  }
×
154

155
  pr_log_debug(DEBUG9, "ROOT PRIVS at %s:%d", file, lineno);
156

18✔
157
  if (root_privs > 0) {
158
    pr_trace_msg(trace_channel, 9, "root privs count = %u, ignoring PRIVS_ROOT",
18✔
159
      root_privs);
×
160
    return 0;
161
  }
×
162

163
  pr_trace_msg(trace_channel, 9, "root privs count = %u, honoring PRIVS_ROOT",
164
    root_privs);
18✔
165
  root_privs++;
166

18✔
167
  pr_signals_block();
168

18✔
169
  if (!session.disable_id_switching) {
170

18✔
171
#if defined(HAVE_SETEUID)
172
    if (seteuid(PR_ROOT_UID) < 0) {
173
      privs_log_error("ROOT PRIVS: unable to seteuid()", errno);
18✔
174
    }
×
175

176
    if (setegid(PR_ROOT_GID) < 0) {
177
      privs_log_error("ROOT PRIVS: unable to setegid()", errno);
18✔
178
    }
×
179
#else
180
    if (setreuid(session.uid, PR_ROOT_UID) < 0) {
181
      privs_log_error("ROOT PRIVS: unable to setreuid()", errno);
182
    }
183

184
    if (setregid(session.gid, PR_ROOT_GID)) {
185
      privs_log_error("ROOT PRIVS: unable to setregid()", errno);
186
    }
187
#endif /* !HAVE_SETEUID */
188

189
  } else {
190
    pr_log_debug(DEBUG9, "ROOT PRIVS: ID switching disabled");
191
  }
×
192

193
  pr_signals_unblock();
194
  return 0;
18✔
195
}
18✔
196

197
int pr_privs_user(const char *file, int lineno) {
198
  if (nonroot_daemon == TRUE) {
×
199
    pr_trace_msg(trace_channel, 9,
×
200
      "PRIVS_USER called at %s:%d for nonroot daemon, ignoring", file, lineno);
×
201
    return 0;
202
  }
×
203

204
  pr_log_debug(DEBUG9, "USER PRIVS %s at %s:%d",
205
    pr_uid2str(NULL, session.login_uid), file, lineno);
×
206

207
  if (user_privs > 0) {
208
    pr_trace_msg(trace_channel, 9, "user privs count = %u, ignoring PRIVS_USER",
×
209
      user_privs);
×
210
    return 0;
211
  }
×
212

213
  pr_trace_msg(trace_channel, 9, "user privs count = %u, honoring PRIVS_USER",
214
    user_privs);
×
215
  user_privs++;
216

×
217
  pr_signals_block();
218

×
219
  if (!session.disable_id_switching) {
220
#if defined(HAVE_SETEUID)
×
221
    if (seteuid(PR_ROOT_UID) < 0) {
222
      privs_log_error("USER PRIVS: unable to seteuid(PR_ROOT_UID)", errno);
×
223
    }
×
224

225
    if (setegid(session.login_gid) < 0) {
226
      privs_log_error("USER PRIVS: unable to setegid(session.login_gid)",
×
227
        errno);
×
228
    }
×
229

230
    if (seteuid(session.login_uid) < 0) {
231
      privs_log_error("USER PRIVS: unable to seteuid(session.login_uid)",
×
232
        errno);
×
233
    }
×
234
#else
235
    if (setreuid(session.uid, PR_ROOT_UID) < 0) {
236
      privs_log_error(
237
        "USER PRIVS: unable to setreuid(session.uid, PR_ROOT_UID)", errno);
238
    }
239

240
    if (setregid(session.gid, session.login_gid) < 0) {
241
      privs_log_error(
242
        "USER PRIVS: unable to setregid(session.gid, session.login_gid)",
243
        errno);
244
    }
245

246
    if (setreuid(session.uid, session.login_uid) < 0) {
247
      privs_log_error(
248
        "USER PRIVS: unable to setreuid(session.uid, session.login_uid)",
249
        errno);
250
    }
251
#endif /* !HAVE_SETEUID */
252

253
  } else {
254
    pr_log_debug(DEBUG9, "USER PRIVS: ID switching disabled");
255
  }
×
256

257
  pr_signals_unblock();
258
  return 0;
×
259
}
×
260

261
int pr_privs_relinquish(const char *file, int lineno) {
262
  if (nonroot_daemon == TRUE) {
18✔
263
    pr_trace_msg(trace_channel, 9,
18✔
264
      "PRIVS_RELINQUISH called at %s:%d for nonroot daemon, ignoring", file,
×
265
      lineno);
266
    return 0;
267
  }
×
268

269
  pr_log_debug(DEBUG9, "RELINQUISH PRIVS at %s:%d", file, lineno);
270

18✔
271
  if (root_privs == 0 &&
272
      user_privs == 0) {
18✔
273
    /* No privs to relinquish here. */
×
274
    pr_trace_msg(trace_channel, 9,
275
      "user/root privs count = 0, ignoring PRIVS_RELINQUISH");
×
276
    return 0;
277
  }
×
278

279
  /* We only want to actually relinquish the privs (user or root) when
280
   * the nesting count reaches 1.
281
   */
282
  if (root_privs + user_privs > 1) {
283
    pr_trace_msg(trace_channel, 9,
18✔
284
      "root privs count = %u, user privs count = %u, ignoring PRIVS_RELINQUISH",
×
285
      root_privs, user_privs);
286
    return 0;
287
  }
×
288

289
  pr_trace_msg(trace_channel, 9, "root privs count = %u, user privs "
290
    "count = %u, honoring PRIVS_RELINQUISH", root_privs, user_privs);
18✔
291

292
  pr_signals_block();
293

18✔
294
  if (!session.disable_id_switching) {
295
#if defined(HAVE_SETEUID)
18✔
296
    if (geteuid() != PR_ROOT_UID) {
297
      if (seteuid(PR_ROOT_UID) < 0) {
18✔
298
        privs_log_error(
×
299
          "RELINQUISH PRIVS: unable to seteuid(PR_ROOT_UID)", errno);
×
300
      }
×
301

302
      if (user_privs > 0) {
303
        user_privs--;
×
304
      }
×
305

306
    } else {
307
      if (root_privs > 0) {
308
        root_privs--;
18✔
309
      }
18✔
310
    }
311

312
    if (setegid(session.gid) < 0) {
313
      privs_log_error(
18✔
314
        "RELINQUISH PRIVS: unable to setegid(session.gid)", errno);
×
315
    }
×
316

317
    if (seteuid(session.uid) < 0) {
318
      privs_log_error(
18✔
319
        "RELINQUISH PRIVS: unable to seteuid(session.uid)", errno);
×
320
    }
×
321
#else
322
    if (geteuid() != PR_ROOT_UID) {
323
      if (setreuid(session.uid, PR_ROOT_UID) < 0) {
324
        privs_log_error(
325
          "RELINQUISH PRIVS: unable to setreuid(session.uid, PR_ROOT_UID)",
326
          errno);
327
      }
328

329
      if (user_privs > 0) {
330
        user_privs--;
331
      }
332

333
    } else {
334
      if (root_privs > 0) {
335
        root_privs--;
336
      }
337
    }
338

339
    if (getegid() != PR_ROOT_GID) {
340
      if (setregid(session.gid, PR_ROOT_GID) < 0) {
341
        privs_log_error(
342
          "RELINQUISH PRIVS: unable to setregid(session.gid, PR_ROOT_GID)",
343
          errno);
344
      }
345
    }
346

347
    if (setregid(session.gid, session.gid)) {
348
      privs_log_error(
349
        "RELINQUISH PRIVS: unable to setregid(session.gid, session.gid)",
350
        errno);
351
    }
352

353
    if (setreuid(session.uid, session.uid)) {
354
      privs_log_error(
355
        "RELINQUISH PRIVS: unable to setreuid(session.uid, session.uid)",
356
        errno);
357
    }
358

359
#endif /* !HAVE_SETEUID */
360
  } else {
361
    pr_log_debug(DEBUG9, "RELINQUISH PRIVS: ID switching disabled");
362
  }
×
363

364
  pr_signals_unblock();
365
  return 0;
18✔
366
}
18✔
367

368
int pr_privs_revoke(const char *file, int lineno) {
369
  if (nonroot_daemon == TRUE) {
×
370
    pr_trace_msg(trace_channel, 9,
×
371
      "PRIVS_REVOKE called at %s:%d for nonroot daemon, ignoring", file,
×
372
      lineno);
373
    return 0;
374
  }
×
375

376
  pr_log_debug(DEBUG9, "REVOKE PRIVS at %s:%d", file, lineno);
377

×
378
  root_privs = user_privs = 0;
379
  pr_trace_msg(trace_channel, 9, "PRIVS_REVOKE called, "
×
380
    "clearing user/root privs count");
×
381

382
  pr_signals_block();
383

×
384
#if defined(HAVE_SETEUID)
385
  if (seteuid(PR_ROOT_UID) < 0) {
386
    privs_log_error("REVOKE PRIVS: unable to seteuid()", errno);
×
387
  }
×
388

389
  if (setgid(session.gid) < 0) {
390
    privs_log_error("REVOKE PRIVS: unable to setgid()", errno);
×
391
  }
×
392

393
  if (setuid(session.uid) < 0) {
394
    privs_log_error("REVOKE PRIVS: unable to setuid()", errno);
×
395
  }
×
396
#else
397
  if (setreuid(PR_ROOT_UID, PR_ROOT_UID) < 0) {
398
    privs_log_error(
399
      "REVOKE PRIVS: unable to setreuid(PR_ROOT_UID, PR_ROOT_UID)", errno);
400
  }
401

402
  if (setgid(session.gid) < 0) {
403
    privs_log_error("REVOKE PRIVS: unable to setgid()", errno);
404
  }
405

406
  if (setuid(session.uid) < 0) {
407
    privs_log_error("REVOKE PRIVS: unable to setuid()", errno);
408
  }
409
#endif /* !HAVE_SETEUID */
410

411
  pr_signals_unblock();
412
  return 0;
×
413
}
×
414

415
/* Returns the previous value, or -1 on error. */
416
int set_nonroot_daemon(int nonroot) {
417
  int was_nonroot;
3✔
418

3✔
419
  if (nonroot != TRUE &&
420
      nonroot != FALSE) {
3✔
421
    errno = EINVAL;
422
    return -1;
1✔
423
  }
1✔
424

425
  was_nonroot = nonroot_daemon;
426
  nonroot_daemon = nonroot;
2✔
427

2✔
428
  return was_nonroot;
429
}
2✔
430

431
int init_privs(void) {
432
  /* Check to see if we have real root privs. */
6✔
433
  if (getuid() != PR_ROOT_UID) {
434
    set_nonroot_daemon(TRUE);
6✔
435
  }
×
436

437
  return 0;
438
}
6✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc