diff --git a/conf/icecast.xml b/conf/icecast.xml index b194e416..f98dec37 100644 --- a/conf/icecast.xml +++ b/conf/icecast.xml @@ -34,4 +34,12 @@ access.log error.log + + + 0 + + diff --git a/configure.in b/configure.in index f13d3eda..b4dc20ae 100644 --- a/configure.in +++ b/configure.in @@ -72,6 +72,8 @@ dnl Checks for header files. AC_HEADER_STDC AC_CHECK_HEADER(stdint.h, AC_DEFINE(HAVE_STDINT_H, 1),,) +AC_CHECK_HEADER(pwd.h, AC_DEFINE(CHUID, 1),,) +AC_CHECK_HEADER(unistd.h, AC_DEFINE(CHROOT, 1),,) dnl Checks for typedefs, structures, and compiler characteristics. AC_C_CONST diff --git a/src/config.c b/src/config.c index b2042e06..b22212be 100644 --- a/src/config.c +++ b/src/config.c @@ -19,6 +19,10 @@ #define CONFIG_DEFAULT_PORT 8888 #define CONFIG_DEFAULT_ACCESS_LOG "access.log" #define CONFIG_DEFAULT_ERROR_LOG "error.log" +#define CONFIG_DEFAULT_CHROOT 0 +#define CONFIG_DEFAULT_CHUID 0 +#define CONFIG_DEFAULT_USER NULL +#define CONFIG_DEFAULT_GROUP NULL #ifndef _WIN32 #define CONFIG_DEFAULT_BASE_DIR "/usr/local/icecast" @@ -37,6 +41,7 @@ static void _parse_limits(xmlDocPtr doc, xmlNodePtr node); static void _parse_directory(xmlDocPtr doc, xmlNodePtr node); static void _parse_paths(xmlDocPtr doc, xmlNodePtr node); static void _parse_logging(xmlDocPtr doc, xmlNodePtr node); +static void _parse_security(xmlDocPtr doc, xmlNodePtr node); static void _add_server(xmlDocPtr doc, xmlNodePtr node); void config_initialize(void) @@ -60,6 +65,8 @@ void config_shutdown(void) if (_configuration.access_log) free(_configuration.access_log); if (_configuration.error_log) free(_configuration.error_log); if (_configuration.bind_address) free(_configuration.bind_address); + if (_configuration.user) free(_configuration.user); + if (_configuration.group) free(_configuration.group); dirnode = _configuration.dir_list; while(dirnode) { nextdirnode = dirnode->next; @@ -138,6 +145,10 @@ static void _set_defaults(void) _configuration.log_dir = (char *)strdup(CONFIG_DEFAULT_LOG_DIR); _configuration.access_log = (char *)strdup(CONFIG_DEFAULT_ACCESS_LOG); _configuration.error_log = (char *)strdup(CONFIG_DEFAULT_ERROR_LOG); + _configuration.chroot = CONFIG_DEFAULT_CHROOT; + _configuration.chuid = CONFIG_DEFAULT_CHUID; + _configuration.user = CONFIG_DEFAULT_USER; + _configuration.group = CONFIG_DEFAULT_GROUP; } static void _parse_root(xmlDocPtr doc, xmlNodePtr node) @@ -175,6 +186,8 @@ static void _parse_root(xmlDocPtr doc, xmlNodePtr node) _parse_paths(doc, node->xmlChildrenNode); } else if (strcmp(node->name, "logging") == 0) { _parse_logging(doc, node->xmlChildrenNode); + } else if (strcmp(node->name, "security") == 0) { + _parse_security(doc, node->xmlChildrenNode); } } while ((node = node->next)); } @@ -265,6 +278,39 @@ static void _parse_logging(xmlDocPtr doc, xmlNodePtr node) } while ((node = node->next)); } +static void _parse_security(xmlDocPtr doc, xmlNodePtr node) +{ + char *tmp; + xmlNodePtr oldnode; + + do { + if (node == NULL) break; + if (xmlIsBlankNode(node)) continue; + + if (strcmp(node->name, "chroot") == 0) { + tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + _configuration.chroot = atoi(tmp); + if (tmp) free(tmp); + } else if (strcmp(node->name, "changeowner") == 0) { + _configuration.chuid = 1; + oldnode = node; + node = node->xmlChildrenNode; + do { + if(node == NULL) break; + if(xmlIsBlankNode(node)) continue; + if(strcmp(node->name, "user") == 0) { + if(_configuration.user) free(_configuration.user); + _configuration.user = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } else if(strcmp(node->name, "group") == 0) { + if(_configuration.group) free(_configuration.group); + _configuration.group = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); + } + } while((node = node->next)); + node = oldnode; + } + } while ((node = node->next)); +} + static void _add_server(xmlDocPtr doc, xmlNodePtr node) { ice_config_dir_t *dirnode, *server; diff --git a/src/config.h b/src/config.h index a4c0c45c..1a81b4b2 100644 --- a/src/config.h +++ b/src/config.h @@ -38,6 +38,11 @@ typedef struct ice_config_tag char *access_log; char *error_log; + + int chroot; + int chuid; + char *user; + char *group; } ice_config_t; void config_initialize(void); diff --git a/src/main.c b/src/main.c index edd30ce8..1fce1af0 100644 --- a/src/main.c +++ b/src/main.c @@ -8,6 +8,12 @@ #include "resolver.h" #include "httpp.h" +#ifdef CHUID +#include +#include +#include +#include +#endif #include "config.h" #include "sighandler.h" @@ -43,7 +49,6 @@ static void _initialize_subsystems(void) config_initialize(); connection_initialize(); global_initialize(); - stats_initialize(); refbuf_initialize(); } @@ -135,8 +140,8 @@ static int _start_listening(void) return 1; } -/* this is the heart of the beast */ -static void _server_proc(void) +/* bind the socket and start listening */ +static void _server_proc_init(void) { if (!_setup_socket()) { ERROR1("Could not create listener socket on port %d", config_get_config()->port); @@ -147,12 +152,76 @@ static void _server_proc(void) ERROR0("Failed trying to listen on server socket"); return; } +} +/* this is the heart of the beast */ +static void _server_proc(void) +{ connection_accept_loop(); sock_close(global.serversock); } +#ifdef CHROOT +/* chroot the process. Watch out - we need to do this before starting other + * threads */ + +static void _chroot_setup(void) +{ + ice_config_t *conf = config_get_config(); + + if (conf->chroot) + { + if(getuid()) /* root check */ + { + fprintf(stderr, "WARNING: Cannot change server root unless running as root.\n"); + return; + } + if(chroot(conf->base_dir)) + { + fprintf(stderr,"WARNING: Couldn't change server root: %s\n", strerror(errno)); + return; + } + else + fprintf(stdout, "Changed root successfully to \"%s\".\n", conf->base_dir); + + } +} +#endif + +#ifdef CHUID +/* change uid and gid */ +static void _chuid_setup(void) +{ + ice_config_t *conf = config_get_config(); + struct passwd *user; + struct group *group; + + if(conf->chuid) + { + if(getuid()) /* root check */ + { + fprintf(stderr, "WARNING: Can't change user id unless you are root.\n"); + return; + } + + user = getpwnam(conf->user); + group = getgrnam(conf->group); + + if(!setgid(group->gr_gid)) + fprintf(stdout, "Changed groupid to %i.\n", group->gr_gid); + else + fprintf(stdout, "Error changing groupid: %s.\n", strerror(errno)); + + if(!setuid(user->pw_uid)) + fprintf(stdout, "Changed userid to %i.\n", user->pw_uid); + else + fprintf(stdout, "Error changing userid: %s.\n", strerror(errno)); + + } +} +#endif + int main(int argc, char **argv) { int res, ret; @@ -161,9 +230,6 @@ int main(int argc, char **argv) /* startup all the modules */ _initialize_subsystems(); - /* setup the default signal handlers */ - sighandler_initialize(); - /* parse the '-c icecast.xml' option ** only, so that we can read a configfile */ @@ -197,6 +263,33 @@ int main(int argc, char **argv) /* override config file options with commandline options */ config_parse_cmdline(argc, argv); +#ifdef CHROOT + _chroot_setup(); /* Perform chroot, if requested */ +#endif + + _server_proc_init(); /* Bind socket, before we change userid */ + +#ifdef CHUID + _chuid_setup(); /* change user id */ +#endif + + stats_initialize(); /* We have to do this later on because of threading */ + +#ifdef CHUID + /* We'll only have getuid() if we also have setuid(), it's reasonable to + * assume */ + if(!getuid()) /* Running as root! Don't allow this */ + { + fprintf(stderr, "WARNING: You should not run icecast2 as root\n"); + fprintf(stderr, "Use the changeowner directive in the config file\n"); + _shutdown_subsystems(); + return 1; + } +#endif + + /* setup default signal handlers */ + sighandler_initialize(); + if (!_start_logging()) { fprintf(stderr, "FATAL: Could not start logging\n"); _shutdown_subsystems();