diff --git a/configure.in b/configure.in index bc11de5758..fa94afec3f 100644 --- a/configure.in +++ b/configure.in @@ -170,7 +170,7 @@ AC_SUBST(DYNAMIC_LIB_EXTEN) # Checks for header files. AC_HEADER_DIRENT AC_HEADER_STDC -AC_CHECK_HEADERS([sys/types.h]) +AC_CHECK_HEADERS([sys/types.h sys/resource.h sched.h]) # Checks for typedefs, structures, and compiler characteristics. AC_C_CONST @@ -185,6 +185,59 @@ AC_FUNC_MALLOC AC_TYPE_SIGNAL AC_FUNC_STRFTIME AC_CHECK_FUNCS([gethostname vasprintf mmap mlock mlockall usleep]) +AC_CHECK_FUNCS([sched_setscheduler setpriority setrlimit setgroups initgroups]) + +AC_CHECK_DECL([RLIMIT_MEMLOCK], + [AC_DEFINE([HAVE_RLIMIT_MEMLOCK],[1],[RLIMIT_MEMLOCK constant for setrlimit])],, + [#ifdef HAVE_SYS_RESOURCE_H + #include + #endif]) + +AC_CHECK_DECL([SCHED_RR], + [AC_DEFINE([HAVE_SCHED_RR],[1],[SCHED_RR constant for sched_setscheduler])],, + [#ifdef HAVE_SCHED_H + #include + #endif]) + +# +# use mlockall only on linux (for now; if available) +# +if test "x${ac_cv_func_mlockall}" = "xyes"; then + AC_MSG_CHECKING([whether to use mlockall]) + case "$host" in + *-linux-*) + AC_DEFINE([USE_MLOCKALL],[1],[Enable mlockall support]) + AC_MSG_RESULT([yes]) + USE_MLOCKALL=yes + ;; + *-freebsd*) + AC_MSG_RESULT([no, broken for non-root users]) + ;; + *) + AC_MSG_RESULT([no]) + ;; + esac + + # + # setrlimit prerequisites + # + if test "x${USE_MLOCKALL}" = "xyes" -a \ + "x${ac_cv_func_setrlimit}" = "xyes" -a \ + "x${ac_cv_have_decl_RLIMIT_MEMLOCK}" = "xyes" + then + AC_DEFINE([USE_SETRLIMIT],[1],[Use setrlimit to disable mlock limit for non-root users]) + fi +fi + +# +# sched_setcheduler + round-robin scheduler prerequisites +# +if test "x${ac_cv_func_sched_setscheduler}" = "xyes" -a \ + "x${ac_cv_have_decl_SCHED_RR}" = "xyes" +then + AC_DEFINE([USE_SCHED_SETSCHEDULER],[1],[Enable round-robin scheduler using sched_setscheduler]) +fi + AC_C_BIGENDIAN(AC_DEFINE([SWITCH_BYTE_ORDER],__BIG_ENDIAN,[Big Endian]),AC_DEFINE([SWITCH_BYTE_ORDER],__LITTLE_ENDIAN,[Little Endian])) diff --git a/src/include/private/switch_core_pvt.h b/src/include/private/switch_core_pvt.h index 3c61fb0d8d..af75dc9d31 100644 --- a/src/include/private/switch_core_pvt.h +++ b/src/include/private/switch_core_pvt.h @@ -60,6 +60,19 @@ typedef apr_os_thread_t switch_thread_id_t; #include #endif +#ifndef WIN32 +/* setuid, setgid */ +#include + +/* getgrnam, getpwnam */ +#include +#include + +#ifdef HAVE_SYS_RESOURCE_H +#include +#endif +#endif + /* #define DEBUG_ALLOC */ #define DO_EVENTS diff --git a/src/include/switch_core.h b/src/include/switch_core.h index d4e32d34ab..47a4210d28 100644 --- a/src/include/switch_core.h +++ b/src/include/switch_core.h @@ -1413,6 +1413,18 @@ SWITCH_DECLARE(switch_status_t) switch_core_management_exec(char *relative_oid, */ SWITCH_DECLARE(int32_t) set_high_priority(void); +/*! + \brief Change user and/or group of the running process + \long Several possible combinations: + - user only (group NULL): switch to user and his primary group (and supplementary groups, if supported) + - user and group: switch to user and specified group (only) + - group only (user NULL): switch group only + \param user name of the user to switch to (or NULL) + \param group name of the group to switch to (or NULL) + \return 0 on success, -1 otherwise +*/ +SWITCH_DECLARE(int32_t) change_user_group(const char *user, const char *group); + /*! \brief Run endlessly until the system is shutdown \param bg divert console to the background diff --git a/src/switch.c b/src/switch.c index 193be311f2..21db4b95e0 100644 --- a/src/switch.c +++ b/src/switch.c @@ -205,6 +205,8 @@ int main(int argc, char *argv[]) const char *err = NULL; /* error value for return from freeswitch initialization */ #ifndef WIN32 int nf = 0; /* TRUE if we are running in nofork mode */ + char *runas_user = NULL; + char *runas_group = NULL; #endif int nc = 0; /* TRUE if we are running in noconsole mode */ FILE *f; /* file handle to the pid file */ @@ -214,6 +216,7 @@ int main(int argc, char *argv[]) char *usageDesc; int alt_dirs = 0; int known_opt; + int high_prio = 0; switch_core_flag_t flags = SCF_USE_SQL; #ifdef WIN32 @@ -229,6 +232,8 @@ int main(int argc, char *argv[]) "\t-uninstall -- remove freeswitch as a service\n" #else "\t-nf -- no forking\n" + "\t-u [user] -- specify user to switch to\n" + "\t-g [group] -- specify group to switch to\n" #endif "\t-help -- this message\n" "\t-hp -- enable high priority settings\n" @@ -280,6 +285,21 @@ int main(int argc, char *argv[]) } } #else + if (argv[x] && !strcmp(argv[x], "-u")) { + x++; + if (argv[x] && strlen(argv[x])) { + runas_user = argv[x]; + } + known_opt++; + } + + if (argv[x] && !strcmp(argv[x], "-g")) { + x++; + if (argv[x] && strlen(argv[x])) { + runas_group = argv[x]; + } + known_opt++; + } if (argv[x] && !strcmp(argv[x], "-nf")) { nf++; @@ -287,7 +307,7 @@ int main(int argc, char *argv[]) } #endif if (argv[x] && !strcmp(argv[x], "-hp")) { - set_high_priority(); + high_prio++; known_opt++; } @@ -389,6 +409,19 @@ int main(int argc, char *argv[]) #endif } + if (high_prio) { + set_high_priority(); + } + +#ifndef WIN32 + if (runas_user || runas_group) { + if(change_user_group(runas_user, runas_group) < 0) { + fprintf(stderr, "Failed to switch user / group\n" ); + return 255; + } + } +#endif + if (switch_core_init_and_modload(nc ? lfile : NULL, flags, &err) != SWITCH_STATUS_SUCCESS) { fprintf(stderr, "Cannot Initilize [%s]\n", err); return 255; diff --git a/src/switch_core.c b/src/switch_core.c index 801c39d14a..25b96547d5 100644 --- a/src/switch_core.c +++ b/src/switch_core.c @@ -360,7 +360,19 @@ SWITCH_DECLARE(void) switch_core_set_globals(void) SWITCH_DECLARE(int32_t) set_high_priority(void) { -#ifdef __linux__ +#ifdef WIN32 + SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); +#else + +#ifdef USE_SETRLIMIT + struct rlimit lim = { RLIM_INFINITY, RLIM_INFINITY }; +#endif + +#ifdef USE_SCHED_SETSCHEDULER + /* + * Try to use a round-robin scheduler + * with a fallback if that does not work + */ struct sched_param sched = { 0 }; sched.sched_priority = 1; if (sched_setscheduler(0, SCHED_RR, &sched)) { @@ -371,19 +383,126 @@ SWITCH_DECLARE(int32_t) set_high_priority(void) } #endif -#ifdef WIN32 - SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); +#ifdef HAVE_SETPRIORITY + /* + * setpriority() works on FreeBSD (6.2), nice() doesn't + */ + if (setpriority(PRIO_PROCESS, getpid(), -10) < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Could not set nice level\n"); + } #else - if(nice(-10)!= -10) { + if (nice(-10) != -10) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Could not set nice level\n"); } #endif -#define USE_MLOCKALL -#ifdef HAVE_MLOCKALL +#ifdef USE_SETRLIMIT + /* + * The amount of memory which can be mlocked is limited for non-root users. + * FS will segfault (= hitting the limit) soon after mlockall has been called + * and we've switched to a different user. + * So let's try to remove the mlock limit here... + */ + if (setrlimit(RLIMIT_MEMLOCK, &lim) < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, + "Failed to disable memlock limit, application may crash if run as non-root user!\n"); + } +#endif + #ifdef USE_MLOCKALL + /* + * Pin memory pages to RAM to prevent being swapped to disk + */ mlockall(MCL_CURRENT | MCL_FUTURE); #endif + +#endif + return 0; +} + +SWITCH_DECLARE(int32_t) change_user_group(const char *user, const char *group) +{ +#ifndef WIN32 + uid_t runas_uid = 0; + gid_t runas_gid = 0; + struct passwd *runas_pw = NULL; + + if (user) { + /* + * Lookup user information in the system's db + */ + runas_pw = getpwnam( user ); + if (!runas_pw) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Unknown user \"%s\"\n", user); + return -1; + } + runas_uid = runas_pw->pw_uid; + } + + if (group) { + struct group *gr = NULL; + + /* + * Lookup group information in the system's db + */ + gr = getgrnam( group ); + if (!gr) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Unknown group \"%s\"\n", group); + return -1; + } + runas_gid = gr->gr_gid; + } + + if (runas_uid) { +#ifdef HAVE_SETGROUPS + /* + * Drop all group memberships prior to changing anything + * or else we're going to inherit the parent's list of groups + * (which is not what we want...) + */ + if (setgroups(0, NULL) < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed to drop group access list\n"); + return -1; + } +#endif + if (runas_gid) { + /* + * A group has been passed, switch to it + * (without loading the user's other groups) + */ + if (setgid(runas_gid) < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed to change gid!\n"); + return -1; + } + } else { + /* + * No group has been passed, use the user's primary group in this case + */ + if (setgid(runas_pw->pw_gid) < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed to change gid!\n"); + return -1; + } + +#ifdef HAVE_INITGROUPS + /* + * Set all the other groups the user is a member of + * (This can be really useful for fine-grained access control) + */ + if (initgroups(runas_pw->pw_name, runas_pw->pw_gid) < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed to set group access list for user\n"); + return -1; + } +#endif + } + + /* + * Finally drop all privileges by switching to the new userid + */ + if (setuid(runas_uid) < 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Failed to change uid!\n"); + return -1; + } + } #endif return 0; }