2 * ProFTPD - FTP server daemon
3 * Copyright (c) 1997, 1998 Public Flood Software
4 * Copyright (c) 2003-2010 The ProFTPD Project team
5 * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
21 * As a special exemption, the copyright holders give permission to link
22 * this program with OpenSSL and distribute the resulting executable without
23 * including the source code for OpenSSL in the source distribution.
26 /* Use Solaris privileges to severely limit root's access. After user
27 * authentication, this module _completely_ gives up most privileges,
28 * except for the * bare minimum functionality that is required.
29 * VERY highly recommended for security-consious admins.
31 * The concept of this was copied from the Linux mod_cap. Solaris
32 * also has the concept of basic privileges that we can take away to further
33 * restrict a process lower than what a normal user process can do, this
34 * module removes some of those as well.
45 #define MOD_SOLARIS_PRIV_VERSION "mod_solaris_priv/1.0"
47 /* Configuration handlers
50 #define PRIV_USE_FILE_CHOWN 0x0001
51 #define PRIV_USE_FILE_CHOWN_SELF 0x0002
52 #define PRIV_USE_DAC_READ 0x0004
53 #define PRIV_USE_DAC_WRITE 0x0008
54 #define PRIV_USE_DAC_SEARCH 0x0010
55 #define PRIV_USE_SETID 0x0020
56 #define PRIV_USE_FILE_OWNER 0x0040
57 #define PRIV_DROP_FILE_WRITE 0x0080
59 #define PRIV_SOL_ROOT_PRIVS \
60 (PRIV_USE_FILE_CHOWN | PRIV_USE_FILE_CHOWN_SELF | \
61 PRIV_USE_DAC_READ | PRIV_USE_DAC_WRITE | PRIV_USE_DAC_SEARCH | \
64 static unsigned int solaris_priv_flags
= 0;
65 static unsigned char use_privs
= TRUE
;
67 MODRET
set_solaris_priv(cmd_rec
*cmd
) {
68 unsigned int flags
= 0;
70 register unsigned int i
= 0;
72 if (cmd
->argc
- 1 < 1)
73 CONF_ERROR(cmd
, "need at least one parameter");
75 CHECK_CONF(cmd
, CONF_ROOT
|CONF_VIRTUAL
|CONF_GLOBAL
);
77 /* PRIV_CHOWN is enabled by default. */
78 flags
|= PRIV_USE_FILE_CHOWN
;
80 for (i
= 1; i
< cmd
->argc
; i
++) {
81 char *cm
= cmd
->argv
[i
];
82 char *cp
= cmd
->argv
[i
];
85 if (cm
[0] != '+' && cm
[0] != '-')
86 CONF_ERROR(cmd
, pstrcat(cmd
->tmp_pool
, ": bad option: '",
89 if (strcasecmp(cp
, "PRIV_USE_FILE_CHOWN") == 0) {
91 flags
&= ~PRIV_USE_FILE_CHOWN
;
93 } else if (strcasecmp(cp
, "PRIV_FILE_CHOWN_SELF") == 0) {
95 flags
&= ~PRIV_USE_FILE_CHOWN_SELF
;
97 } else if (strcasecmp(cp
, "PRIV_DAC_READ") == 0) {
99 flags
|= PRIV_USE_DAC_READ
;
101 } else if (strcasecmp(cp
, "PRIV_DAC_WRITE") == 0) {
103 flags
|= PRIV_USE_DAC_WRITE
;
105 } else if (strcasecmp(cp
, "PRIV_DAC_SEARCH") == 0) {
107 flags
|= PRIV_USE_DAC_SEARCH
;
109 } else if (strcasecmp(cp
, "PRIV_FILE_OWNER") == 0) {
111 flags
|= PRIV_USE_FILE_OWNER
;
114 CONF_ERROR(cmd
, pstrcat(cmd
->tmp_pool
, "unknown privilege: '",
119 c
= add_config_param(cmd
->argv
[0], 1, NULL
);
120 c
->argv
[0] = pcalloc(c
->pool
, sizeof(unsigned int));
121 *((unsigned int *) c
->argv
[0]) = flags
;
123 return PR_HANDLED(cmd
);
127 MODRET
set_solaris_priv_engine(cmd_rec
*cmd
) {
129 config_rec
*c
= NULL
;
132 CHECK_CONF(cmd
, CONF_ROOT
|CONF_VIRTUAL
|CONF_GLOBAL
);
134 bool = get_boolean(cmd
, 1);
136 CONF_ERROR(cmd
, "expecting Boolean parameter");
138 c
= add_config_param(cmd
->argv
[0], 1, NULL
);
139 c
->argv
[0] = pcalloc(c
->pool
, sizeof(unsigned char));
140 *((unsigned char *) c
->argv
[0]) = bool;
142 return PR_HANDLED(cmd
);
148 /* The POST_CMD handler for "PASS" is only called after PASS has
149 * successfully completed, which means authentication is successful,
150 * so we can "tweak" our root access down to almost nothing.
152 MODRET
solaris_priv_post_pass(cmd_rec
*cmd
) {
154 int priv_flags
= solaris_priv_flags
;
155 priv_set_t
*p
= NULL
;
156 priv_set_t
*i
= NULL
;
159 return PR_DECLINED(cmd
);
161 /* If we authenticated as root, we get all appropriate privs */
162 if (session
.uid
== 0) {
163 priv_flags
= PRIV_SOL_ROOT_PRIVS
;
168 /* The only privilege we need is PRIV_NET_PRIVADDR (bind
169 * ports < 1024). Everything else can be discarded. We set this
170 * in the permitted set only, as when we switch away from root
171 * we lose effective anyhow, and must reset it.
173 * We also remove the basic Solaris privileges we know we will
181 priv_delset(i
, PRIV_PROC_EXEC
);
182 priv_delset(i
, PRIV_PROC_FORK
);
183 priv_delset(i
, PRIV_PROC_INFO
);
184 priv_delset(i
, PRIV_PROC_SESSION
);
185 setppriv(PRIV_SET
, PRIV_INHERITABLE
, i
);
192 priv_addset(p
, PRIV_NET_PRIVADDR
);
193 priv_addset(p
, PRIV_PROC_AUDIT
);
195 priv_delset(p
, PRIV_PROC_EXEC
);
196 priv_delset(p
, PRIV_PROC_FORK
);
197 priv_delset(p
, PRIV_PROC_INFO
);
198 priv_delset(p
, PRIV_PROC_SESSION
);
200 if (priv_flags
& PRIV_USE_SETID
)
201 priv_addset(p
, PRIV_PROC_SETID
);
203 /* Add any of the configurable privileges. */
204 if (priv_flags
& PRIV_USE_FILE_CHOWN
)
205 priv_addset(p
, PRIV_FILE_CHOWN
);
207 if (priv_flags
& PRIV_USE_FILE_CHOWN_SELF
)
208 priv_addset(p
, PRIV_FILE_CHOWN_SELF
);
210 if (priv_flags
& PRIV_USE_DAC_READ
)
211 priv_addset(p
, PRIV_FILE_DAC_READ
);
213 if (priv_flags
& PRIV_USE_DAC_WRITE
)
214 priv_addset(p
, PRIV_FILE_DAC_WRITE
);
216 if (priv_flags
& PRIV_USE_DAC_SEARCH
)
217 priv_addset(p
, PRIV_FILE_DAC_SEARCH
);
219 if (priv_flags
& PRIV_USE_FILE_OWNER
)
220 priv_addset(p
, PRIV_FILE_OWNER
);
222 if (priv_flags
& PRIV_DROP_FILE_WRITE
)
223 priv_delset(p
, PRIV_FILE_WRITE
);
225 res
= setppriv(PRIV_SET
, PRIV_PERMITTED
, p
);
226 res
= setppriv(PRIV_SET
, PRIV_EFFECTIVE
, p
);
228 if (setreuid(session
.uid
, session
.uid
) == -1) {
229 pr_log_pri(PR_LOG_ERR
, MOD_SOLARIS_PRIV_VERSION
": setreuid: %s",
233 pr_signals_unblock();
243 pr_signals_unblock();
246 /* That's it! Disable all further id switching */
247 session
.disable_id_switching
= TRUE
;
250 pr_log_pri(PR_LOG_NOTICE
, MOD_SOLARIS_PRIV_VERSION
": attempt to configure "
251 "privileges failed, reverting to normal operation");
254 return PR_DECLINED(cmd
);
257 static void log_err_effective(const char* fn
) {
258 pr_log_pri(PR_LOG_ERR
, MOD_SOLARIS_PRIV_VERSION
": %s(%s): %s",
259 fn
, "effective", strerror(errno
));
262 /* Initialization routines
265 static int solaris_priv_sess_init(void) {
266 /* Check to see if the lowering of privileges has been disabled in the
267 * configuration file.
270 unsigned char *solaris_priv_engine
;
272 solaris_priv_engine
= get_param_ptr(main_server
->conf
, "PrivilegeEngine", FALSE
);
273 if (solaris_priv_engine
&&
274 *solaris_priv_engine
== FALSE
) {
275 pr_log_debug(DEBUG3
, MOD_SOLARIS_PRIV_VERSION
276 ": lowering of privileges disabled");
281 /* Check for which specific privileges to include/exclude. */
283 int use_setuid
= FALSE
;
286 c
= find_config(main_server
->conf
, CONF_PARAM
, "PrivilegeSet", FALSE
);
288 solaris_priv_flags
= *((unsigned int *) c
->argv
[0]);
290 if (!(solaris_priv_flags
& PRIV_USE_FILE_CHOWN
)) {
291 pr_log_debug(DEBUG3
, MOD_SOLARIS_PRIV_VERSION
292 ": removing PRIV_CHOWN privilege");
295 if (solaris_priv_flags
& PRIV_USE_DAC_READ
) {
296 pr_log_debug(DEBUG3
, MOD_SOLARIS_PRIV_VERSION
297 ": adding PRIV_FILE_DAC_READ privilege");
300 if (solaris_priv_flags
& PRIV_USE_DAC_WRITE
) {
301 pr_log_debug(DEBUG3
, MOD_SOLARIS_PRIV_VERSION
302 ": adding PRIV_FILE_DAC_WRITE privilege");
305 if (solaris_priv_flags
& PRIV_USE_DAC_SEARCH
) {
306 pr_log_debug(DEBUG3
, MOD_SOLARIS_PRIV_VERSION
307 ": adding PRIV_DAC_SEARCH privilege");
310 if (solaris_priv_flags
& PRIV_USE_FILE_OWNER
) {
311 pr_log_debug(DEBUG3
, MOD_SOLARIS_PRIV_VERSION
312 ": adding PRIV_FILE_OWNER privilege");
316 c
= find_config(main_server
->conf
, CONF_PARAM
, "AllowOverwrite", FALSE
);
317 if (c
&& *((int *) c
->argv
[0]) == FALSE
) {
318 pr_log_debug(DEBUG3
, MOD_SOLARIS_PRIV_VERSION
319 ": removing PRIV_FILE_WRITE basic privilege");
320 solaris_priv_flags
|= PRIV_DROP_FILE_WRITE
;
324 /* We also need to check for things which want to revoke root privs
325 * altogether: mod_exec, mod_sftp, and the RootRevoke directive.
326 * Revoking root privs completely requires the SETUID/SETGID
330 if (use_setuid
== FALSE
&&
331 pr_module_exists("mod_sftp.c")) {
332 c
= find_config(main_server
->conf
, CONF_PARAM
, "SFTPEngine", FALSE
);
334 *((int *) c
->argv
[0]) == TRUE
) {
339 if (use_setuid
== FALSE
&&
340 pr_module_exists("mod_exec.c")) {
341 c
= find_config(main_server
->conf
, CONF_PARAM
, "ExecEngine", FALSE
);
343 *((unsigned char *) c
->argv
[0]) == TRUE
) {
348 if (use_setuid
== FALSE
) {
349 c
= find_config(main_server
->conf
, CONF_PARAM
, "RootRevoke", FALSE
);
351 *((unsigned char *) c
->argv
[0]) == TRUE
) {
357 solaris_priv_flags
|= PRIV_USE_SETID
;
358 pr_log_debug(DEBUG3
, MOD_SOLARIS_PRIV_VERSION
359 ": adding PRIV_SETID ");
367 static int solaris_priv_module_init(void) {
376 static conftable solaris_priv_conftab
[] = {
377 { "PrivilegeEngine", set_solaris_priv_engine
, NULL
},
378 { "PrivilegeSet", set_solaris_priv
, NULL
},
382 static cmdtable solaris_priv_cmdtab
[] = {
383 { POST_CMD
, C_PASS
, G_NONE
, solaris_priv_post_pass
, FALSE
, FALSE
},
387 module solaris_priv_module
= {
390 /* Module API version */
396 /* Module configuration handler table */
397 solaris_priv_conftab
,
399 /* Module command handler table */
402 /* Module authentication handler table */
405 /* Module initialization */
406 solaris_priv_module_init
,
408 /* Session initialization */
409 solaris_priv_sess_init
,
412 MOD_SOLARIS_PRIV_VERSION