/* * ProFTPD - FTP server daemon * Copyright (c) 1997, 1998 Public Flood Software * Copyright (c) 2003-2010 The ProFTPD Project team * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * * As a special exemption, the copyright holders give permission to link * this program with OpenSSL and distribute the resulting executable without * including the source code for OpenSSL in the source distribution. */ /* Use Solaris privileges to severely limit root's access. After user * authentication, this module _completely_ gives up most privileges, * except for the * bare minimum functionality that is required. * VERY highly recommended for security-consious admins. * * The concept of this was copied from the Linux mod_cap. Solaris * also has the concept of basic privileges that we can take away to further * restrict a process lower than what a normal user process can do, this * module removes some of those as well. */ #include #include #include #include "conf.h" #include "privs.h" #define MOD_SOLARIS_PRIV_VERSION "mod_solaris_priv/1.0" /* Configuration handlers */ #define PRIV_USE_FILE_CHOWN 0x0001 #define PRIV_USE_FILE_CHOWN_SELF 0x0002 #define PRIV_USE_DAC_READ 0x0004 #define PRIV_USE_DAC_WRITE 0x0008 #define PRIV_USE_DAC_SEARCH 0x0010 #define PRIV_USE_SETID 0x0020 #define PRIV_USE_FILE_OWNER 0x0040 #define PRIV_DROP_FILE_WRITE 0x0080 #define PRIV_SOL_ROOT_PRIVS \ (PRIV_USE_FILE_CHOWN | PRIV_USE_FILE_CHOWN_SELF | \ PRIV_USE_DAC_READ | PRIV_USE_DAC_WRITE | PRIV_USE_DAC_SEARCH | \ PRIV_USE_FILE_OWNER) static unsigned int solaris_priv_flags = 0; static unsigned char use_privs = TRUE; MODRET set_solaris_priv(cmd_rec *cmd) { unsigned int flags = 0; config_rec *c = NULL; register unsigned int i = 0; if (cmd->argc - 1 < 1) CONF_ERROR(cmd, "need at least one parameter"); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); /* PRIV_CHOWN is enabled by default. */ flags |= PRIV_USE_FILE_CHOWN; for (i = 1; i < cmd->argc; i++) { char *cm = cmd->argv[i]; char *cp = cmd->argv[i]; cp++; if (cm[0] != '+' && cm[0] != '-') CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, ": bad option: '", cm, "'", NULL)); if (strcasecmp(cp, "PRIV_USE_FILE_CHOWN") == 0) { if (cm[0] == '-') flags &= ~PRIV_USE_FILE_CHOWN; } else if (strcasecmp(cp, "PRIV_FILE_CHOWN_SELF") == 0) { if (cm[0] == '-') flags &= ~PRIV_USE_FILE_CHOWN_SELF; } else if (strcasecmp(cp, "PRIV_DAC_READ") == 0) { if (cm[0] == '+') flags |= PRIV_USE_DAC_READ; } else if (strcasecmp(cp, "PRIV_DAC_WRITE") == 0) { if (cm[0] == '+') flags |= PRIV_USE_DAC_WRITE; } else if (strcasecmp(cp, "PRIV_DAC_SEARCH") == 0) { if (cm[0] == '+') flags |= PRIV_USE_DAC_SEARCH; } else if (strcasecmp(cp, "PRIV_FILE_OWNER") == 0) { if (cm[0] == '+') flags |= PRIV_USE_FILE_OWNER; } else { CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown privilege: '", cp, "'", NULL)); } } c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(unsigned int)); *((unsigned int *) c->argv[0]) = flags; return PR_HANDLED(cmd); } MODRET set_solaris_priv_engine(cmd_rec *cmd) { int bool = -1; config_rec *c = NULL; CHECK_ARGS(cmd, 1); CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL); bool = get_boolean(cmd, 1); if (bool == -1) CONF_ERROR(cmd, "expecting Boolean parameter"); c = add_config_param(cmd->argv[0], 1, NULL); c->argv[0] = pcalloc(c->pool, sizeof(unsigned char)); *((unsigned char *) c->argv[0]) = bool; return PR_HANDLED(cmd); } /* Command handlers */ /* The POST_CMD handler for "PASS" is only called after PASS has * successfully completed, which means authentication is successful, * so we can "tweak" our root access down to almost nothing. */ MODRET solaris_priv_post_pass(cmd_rec *cmd) { int res = -1; int priv_flags = solaris_priv_flags; priv_set_t *p = NULL; priv_set_t *i = NULL; if (!use_privs) return PR_DECLINED(cmd); /* If we authenticated as root, we get all appropriate privs */ if (session.uid == 0) { priv_flags = PRIV_SOL_ROOT_PRIVS; } pr_signals_block(); /* The only privilege we need is PRIV_NET_PRIVADDR (bind * ports < 1024). Everything else can be discarded. We set this * in the permitted set only, as when we switch away from root * we lose effective anyhow, and must reset it. * * We also remove the basic Solaris privileges we know we will * never need. */ i = priv_allocset(); if (i == NULL) goto out; priv_basicset(i); priv_delset(i, PRIV_PROC_EXEC); priv_delset(i, PRIV_PROC_FORK); priv_delset(i, PRIV_PROC_INFO); priv_delset(i, PRIV_PROC_SESSION); setppriv(PRIV_SET, PRIV_INHERITABLE, i); p = priv_allocset(); if (p == NULL) goto out; priv_basicset(p); priv_addset(p, PRIV_NET_PRIVADDR); priv_addset(p, PRIV_PROC_AUDIT); priv_delset(p, PRIV_PROC_EXEC); priv_delset(p, PRIV_PROC_FORK); priv_delset(p, PRIV_PROC_INFO); priv_delset(p, PRIV_PROC_SESSION); if (priv_flags & PRIV_USE_SETID) priv_addset(p, PRIV_PROC_SETID); /* Add any of the configurable privileges. */ if (priv_flags & PRIV_USE_FILE_CHOWN) priv_addset(p, PRIV_FILE_CHOWN); if (priv_flags & PRIV_USE_FILE_CHOWN_SELF) priv_addset(p, PRIV_FILE_CHOWN_SELF); if (priv_flags & PRIV_USE_DAC_READ) priv_addset(p, PRIV_FILE_DAC_READ); if (priv_flags & PRIV_USE_DAC_WRITE) priv_addset(p, PRIV_FILE_DAC_WRITE); if (priv_flags & PRIV_USE_DAC_SEARCH) priv_addset(p, PRIV_FILE_DAC_SEARCH); if (priv_flags & PRIV_USE_FILE_OWNER) priv_addset(p, PRIV_FILE_OWNER); if (priv_flags & PRIV_DROP_FILE_WRITE) priv_delset(p, PRIV_FILE_WRITE); res = setppriv(PRIV_SET, PRIV_PERMITTED, p); res = setppriv(PRIV_SET, PRIV_EFFECTIVE, p); if (setreuid(session.uid, session.uid) == -1) { pr_log_pri(PR_LOG_ERR, MOD_SOLARIS_PRIV_VERSION ": setreuid: %s", strerror(errno)); priv_freeset(i); priv_freeset(p); pr_signals_unblock(); end_login(1); } out: if (i != NULL) priv_freeset(i); if (p != NULL) priv_freeset(p); pr_signals_unblock(); if (res != -1) { /* That's it! Disable all further id switching */ session.disable_id_switching = TRUE; } else { pr_log_pri(PR_LOG_NOTICE, MOD_SOLARIS_PRIV_VERSION ": attempt to configure " "privileges failed, reverting to normal operation"); } return PR_DECLINED(cmd); } static void log_err_effective(const char* fn) { pr_log_pri(PR_LOG_ERR, MOD_SOLARIS_PRIV_VERSION ": %s(%s): %s", fn, "effective", strerror(errno)); } /* Initialization routines */ static int solaris_priv_sess_init(void) { /* Check to see if the lowering of privileges has been disabled in the * configuration file. */ if (use_privs) { unsigned char *solaris_priv_engine; solaris_priv_engine = get_param_ptr(main_server->conf, "PrivilegeEngine", FALSE); if (solaris_priv_engine && *solaris_priv_engine == FALSE) { pr_log_debug(DEBUG3, MOD_SOLARIS_PRIV_VERSION ": lowering of privileges disabled"); use_privs = FALSE; } } /* Check for which specific privileges to include/exclude. */ if (use_privs) { int use_setuid = FALSE; config_rec *c; c = find_config(main_server->conf, CONF_PARAM, "PrivilegeSet", FALSE); if (c != NULL) { solaris_priv_flags = *((unsigned int *) c->argv[0]); if (!(solaris_priv_flags & PRIV_USE_FILE_CHOWN)) { pr_log_debug(DEBUG3, MOD_SOLARIS_PRIV_VERSION ": removing PRIV_CHOWN privilege"); } if (solaris_priv_flags & PRIV_USE_DAC_READ) { pr_log_debug(DEBUG3, MOD_SOLARIS_PRIV_VERSION ": adding PRIV_FILE_DAC_READ privilege"); } if (solaris_priv_flags & PRIV_USE_DAC_WRITE) { pr_log_debug(DEBUG3, MOD_SOLARIS_PRIV_VERSION ": adding PRIV_FILE_DAC_WRITE privilege"); } if (solaris_priv_flags & PRIV_USE_DAC_SEARCH) { pr_log_debug(DEBUG3, MOD_SOLARIS_PRIV_VERSION ": adding PRIV_DAC_SEARCH privilege"); } if (solaris_priv_flags & PRIV_USE_FILE_OWNER) { pr_log_debug(DEBUG3, MOD_SOLARIS_PRIV_VERSION ": adding PRIV_FILE_OWNER privilege"); } } c = find_config(main_server->conf, CONF_PARAM, "AllowOverwrite", FALSE); if (c && *((int *) c->argv[0]) == FALSE) { pr_log_debug(DEBUG3, MOD_SOLARIS_PRIV_VERSION ": removing PRIV_FILE_WRITE basic privilege"); solaris_priv_flags |= PRIV_DROP_FILE_WRITE; } /* We also need to check for things which want to revoke root privs * altogether: mod_exec, mod_sftp, and the RootRevoke directive. * Revoking root privs completely requires the SETUID/SETGID * privileges. */ if (use_setuid == FALSE && pr_module_exists("mod_sftp.c")) { c = find_config(main_server->conf, CONF_PARAM, "SFTPEngine", FALSE); if (c && *((int *) c->argv[0]) == TRUE) { use_setuid = TRUE; } } if (use_setuid == FALSE && pr_module_exists("mod_exec.c")) { c = find_config(main_server->conf, CONF_PARAM, "ExecEngine", FALSE); if (c && *((unsigned char *) c->argv[0]) == TRUE) { use_setuid = TRUE; } } if (use_setuid == FALSE) { c = find_config(main_server->conf, CONF_PARAM, "RootRevoke", FALSE); if (c && *((unsigned char *) c->argv[0]) == TRUE) { use_setuid = TRUE; } } if (use_setuid) { solaris_priv_flags |= PRIV_USE_SETID; pr_log_debug(DEBUG3, MOD_SOLARIS_PRIV_VERSION ": adding PRIV_SETID "); } } return 0; } static int solaris_priv_module_init(void) { return 0; } /* Module API tables */ static conftable solaris_priv_conftab[] = { { "PrivilegeEngine", set_solaris_priv_engine, NULL }, { "PrivilegeSet", set_solaris_priv, NULL }, { NULL, NULL, NULL } }; static cmdtable solaris_priv_cmdtab[] = { { POST_CMD, C_PASS, G_NONE, solaris_priv_post_pass, FALSE, FALSE }, { 0, NULL } }; module solaris_priv_module = { NULL, NULL, /* Module API version */ 0x20, /* Module name */ "privileges", /* Module configuration handler table */ solaris_priv_conftab, /* Module command handler table */ solaris_priv_cmdtab, /* Module authentication handler table */ NULL, /* Module initialization */ solaris_priv_module_init, /* Session initialization */ solaris_priv_sess_init, /* Module version */ MOD_SOLARIS_PRIV_VERSION };