diff --git a/CMakeLists.txt b/CMakeLists.txt index b7f9a01..d9541bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ MACRO(ADD_TARGET_PROPERTIES _target _name _properties) LIST(REMOVE_AT _properties 0) LIST(REMOVE_AT _properties 0) GET_TARGET_PROPERTY(_old_properties ${_target} ${_name}) - MESSAGE("adding property to ${_target} ${_name}: ${_properties}") + #MESSAGE("adding property to ${_target} ${_name}: ${_properties}") IF(NOT _old_properties) # in case it's NOTFOUND SET(_old_properties) @@ -17,24 +17,36 @@ PROJECT(spawn-fcgi) SET(PACKAGE_VERSION 1.0) EXEC_PROGRAM(date ARGS "'+%b %d %Y %H:%M:%S UTC'" OUTPUT_VARIABLE PACKAGE_BUILD_DATE) +OPTION(USE_LIMITS "Enable /etc/security/limits.conf support" ON) + +# GLIB 2 INCLUDE(FindPkgConfig) - pkg_check_modules (GLIB2 REQUIRED glib-2.0) - SET(GLIB_INCLUDES ${GLIB2_INCLUDE_DIRS} ${GLIB2_INCLUDE_DIRS}/glib-2.0/ ${GLIB2_INCLUDE_DIRS}/glib-2.0/include/) INCLUDE_DIRECTORIES(${GLIB_INCLUDES}) -add_executable(spawn-fcgi spawn-fcgi.c) +SET(MAIN_SOURCE spawn-fcgi.c) + +IF(USE_LIMITS) +SET(MAIN_SOURCE ${MAIN_SOURCE} pam_limits.c) +ADD_DEFINITIONS(-DUSE_LIMITS) +ENDIF(USE_LIMITS) + +add_executable(spawn-fcgi ${MAIN_SOURCE}) ADD_TARGET_PROPERTIES(spawn-fcgi COMPILE_FLAGS "-std=gnu99 -Wall -g -Wshadow -W -pedantic -fPIC -D_GNU_SOURCE") + +# GLIB 2 ADD_TARGET_PROPERTIES(spawn-fcgi LINK_FLAGS "${GLIB2_LDFLAGS}") ADD_TARGET_PROPERTIES(spawn-fcgi COMPILE_FLAGS "${GLIB2_CFLAGS_OTHER}") + ADD_DEFINITIONS( -DPACKAGE_NAME="\\"${CMAKE_PROJECT_NAME}\\"" -DPACKAGE_VERSION="\\"${PACKAGE_VERSION}\\"" -DPACKAGE_BUILD_DATE="\\"${PACKAGE_BUILD_DATE}\\"" ) + INSTALL(TARGETS spawn-fcgi DESTINATION bin) diff --git a/pam_limits.c b/pam_limits.c new file mode 100644 index 0000000..880de31 --- /dev/null +++ b/pam_limits.c @@ -0,0 +1,486 @@ +/* + * pam_limits - impose resource limits when opening a user session + * + * 1.6 - modified for PLD (added process priority settings) + * by Marcin Korzonek + * 1.5 - Elliot Lee's "max system logins patch" + * 1.4 - addressed bug in configuration file parser + * 1.3 - modified the configuration file format + * 1.2 - added 'debug' and 'conf=' arguments + * 1.1 - added @group support + * 1.0 - initial release - Linux ONLY + * + * See end for Copyright information + */ + +#if !defined(linux) && !defined(__linux) +#error THIS CODE IS KNOWN TO WORK ONLY ON LINUX !!! +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#ifndef UT_USER /* some systems have ut_name instead of ut_user */ +#define UT_USER ut_user +#endif + +#include +#include + +/* Module defines */ +#ifndef LIMITS_FILE +#define LIMITS_FILE "/etc/security/limits.conf" +#endif + +#define LINE_LENGTH 1024 + +#define LIMITS_DEF_USER 0 /* limit was set by an user entry */ +#define LIMITS_DEF_GROUP 1 /* limit was set by a group entry */ +#define LIMITS_DEF_ALLGROUP 2 /* limit was set by a group entry */ +#define LIMITS_DEF_ALL 3 /* limit was set by an default entry */ +#define LIMITS_DEF_DEFAULT 4 /* limit was set by an default entry */ +#define LIMITS_DEF_NONE 5 /* this limit was not set yet */ + +struct user_limits_struct { + int supported; + int src_soft; + int src_hard; + struct rlimit limit; +}; + +/* internal data */ +struct pam_limit_s { + int priority; /* the priority to run user process with */ + struct user_limits_struct limits[RLIM_NLIMITS]; + struct passwd *pwd; +}; + +#define LIMIT_PRI RLIM_NLIMITS+1 + +#define LIMIT_SOFT 1 +#define LIMIT_HARD 2 + +#define LIMITED_OK 0 /* limit setting appeared to work */ +#define LIMIT_ERR 1 /* error setting a limit */ +#define LOGIN_ERR 2 /* too many logins err */ + +typedef enum { + LIMHANDLE_OK = 0, + LIMHANDLE_ERROR = -1, + LIMHANDLE_IGNORE = 1 +} limhandle_t; + +static limhandle_t init_limits(struct pam_limit_s *pl) { + int i, r; + + for(i = 0; i < RLIM_NLIMITS; i++) { + errno = 0; + r = getrlimit(i, &pl->limits[i].limit); + if (r == -1) { + pl->limits[i].supported = 0; + if (errno != EINVAL) { + g_printerr("Couldn't getrlimit: %s\n", g_strerror(errno)); + return LIMHANDLE_ERROR; + } + } else { + pl->limits[i].supported = 1; + pl->limits[i].src_soft = LIMITS_DEF_NONE; + pl->limits[i].src_hard = LIMITS_DEF_NONE; + } + } + + errno = 0; + pl->priority = getpriority (PRIO_PROCESS, 0); + if (pl->priority == -1 && errno != 0) { + g_printerr("Couldn't getpriority: %s\n", g_strerror(errno)); + return LIMHANDLE_ERROR; + } + + return LIMHANDLE_OK; +} + +static void +process_limit (int source, const char *lim_type, + const char *lim_item, const char *lim_value, + struct pam_limit_s *pl) { + int limit_item; + int limit_type = 0; + int int_value = 0; + rlim_t rlimit_value = 0; + char *endptr; + const char *value_orig = lim_value; + +/* + if (ctrl & PAM_DEBUG_ARG) + pam_syslog(pamh, LOG_DEBUG, "%s: processing %s %s %s for %s", + __FUNCTION__, lim_type, lim_item, lim_value, + limits_def_names[source]);*/ + + if (strcmp(lim_item, "cpu") == 0) { + limit_item = RLIMIT_CPU; + } else if (strcmp(lim_item, "fsize") == 0) { + limit_item = RLIMIT_FSIZE; + } else if (strcmp(lim_item, "data") == 0) { + limit_item = RLIMIT_DATA; + } else if (strcmp(lim_item, "stack") == 0) { + limit_item = RLIMIT_STACK; + } else if (strcmp(lim_item, "core") == 0) { + limit_item = RLIMIT_CORE; + } else if (strcmp(lim_item, "rss") == 0) { + limit_item = RLIMIT_RSS; + } else if (strcmp(lim_item, "nproc") == 0) { + limit_item = RLIMIT_NPROC; + } else if (strcmp(lim_item, "nofile") == 0) { + limit_item = RLIMIT_NOFILE; + } else if (strcmp(lim_item, "memlock") == 0) { + limit_item = RLIMIT_MEMLOCK; + } else if (strcmp(lim_item, "as") == 0) { + limit_item = RLIMIT_AS; +#ifdef RLIMIT_LOCKS + } else if (strcmp(lim_item, "locks") == 0) { + limit_item = RLIMIT_LOCKS; +#endif +#ifdef RLIMIT_SIGPENDING + } else if (strcmp(lim_item, "sigpending") == 0) { + limit_item = RLIMIT_SIGPENDING; +#endif +#ifdef RLIMIT_MSGQUEUE + } else if (strcmp(lim_item, "msgqueue") == 0) { + limit_item = RLIMIT_MSGQUEUE; +#endif +#ifdef RLIMIT_NICE + } else if (strcmp(lim_item, "nice") == 0) { + limit_item = RLIMIT_NICE; +#endif +#ifdef RLIMIT_RTPRIO + } else if (strcmp(lim_item, "rtprio") == 0) { + limit_item = RLIMIT_RTPRIO; +#endif + } else if (strcmp(lim_item, "priority") == 0) { + limit_item = LIMIT_PRI; + } else { + g_printerr("unknown limit item '%s'\n", lim_item); + return; + } + + if (strcmp(lim_type,"soft")==0) { + limit_type=LIMIT_SOFT; + } else if (strcmp(lim_type, "hard")==0) { + limit_type=LIMIT_HARD; + } else if (strcmp(lim_type,"-")==0) { + limit_type=LIMIT_SOFT | LIMIT_HARD; + } else { + g_printerr("unknown limit type '%s'\n", lim_type); + return; + } + if (limit_item != LIMIT_PRI +#ifdef RLIMIT_NICE + && limit_item != RLIMIT_NICE +#endif + && (strcmp(lim_value, "-1") == 0 + || strcmp(lim_value, "-") == 0 || strcmp(lim_value, "unlimited") == 0 + || strcmp(lim_value, "infinity") == 0)) { + int_value = -1; + rlimit_value = RLIM_INFINITY; + } else if (limit_item == LIMIT_PRI +#ifdef RLIMIT_NICE + || limit_item == RLIMIT_NICE +#endif + ) { + long temp; + temp = strtol (lim_value, &endptr, 10); + temp = temp < INT_MAX ? temp : INT_MAX; + int_value = temp > INT_MIN ? temp : INT_MIN; + if (int_value == 0 && value_orig == endptr) { + g_printerr("wrong limit value '%s' for limit type '%s'\n", + lim_value, lim_type); + return; + } + } else { +#ifdef __USE_FILE_OFFSET64 + rlimit_value = strtoull (lim_value, &endptr, 10); +#else + rlimit_value = strtoul (lim_value, &endptr, 10); +#endif + if (rlimit_value == 0 && value_orig == endptr) { + g_printerr("wrong limit value '%s' for limit type '%s'", + lim_value, lim_type); + return; + } + } + + switch(limit_item) { + case RLIMIT_CPU: + if (rlimit_value != RLIM_INFINITY) + rlimit_value *= 60; + break; + case RLIMIT_FSIZE: + case RLIMIT_DATA: + case RLIMIT_STACK: + case RLIMIT_CORE: + case RLIMIT_RSS: + case RLIMIT_MEMLOCK: + case RLIMIT_AS: + if (rlimit_value != RLIM_INFINITY) + rlimit_value *= 1024; + break; +#ifdef RLIMIT_NICE + case RLIMIT_NICE: + if (int_value > 19) + int_value = 19; + rlimit_value = 19 - int_value; + break; +#endif + } + + if (limit_item != LIMIT_PRI) { + if (limit_type & LIMIT_SOFT) { + if (pl->limits[limit_item].src_soft < source) { + return; + } else { + pl->limits[limit_item].limit.rlim_cur = rlimit_value; + pl->limits[limit_item].src_soft = source; + } + } + if (limit_type & LIMIT_HARD) { + if (pl->limits[limit_item].src_hard < source) { + return; + } else { + pl->limits[limit_item].limit.rlim_max = rlimit_value; + pl->limits[limit_item].src_hard = source; + } + } + } else { + /* recent kernels support negative priority limits (=raise priority) */ + + if (limit_item == LIMIT_PRI) { + pl->priority = int_value; + } + } + return; +} + +static gboolean pam_modutil_user_in_group_nam_nam(struct passwd *pwd, const char *group) { + struct group *grp; + grp = getgrnam(group); + size_t i; + + if (pwd == NULL || grp == NULL) return FALSE; + if (pwd->pw_gid == grp->gr_gid) return TRUE; + + for (i = 0; (grp->gr_mem != NULL) && (grp->gr_mem[i] != NULL); i++) { + if (strcmp(pwd->pw_name, grp->gr_mem[i]) == 0) { + return TRUE; + } + } + + return FALSE; +} + +static int parse_config_file(const char *conf_file, struct passwd *pwd, struct pam_limit_s *pl) { + FILE *fil; + char buf[LINE_LENGTH]; + + fil = fopen(conf_file, "r"); + if (fil == NULL) { + g_printerr("cannot read settings from '%s': %s", conf_file, g_strerror(errno)); + return -1; + } + + /* init things */ + memset(buf, 0, sizeof(buf)); + /* start the show */ + while (fgets(buf, LINE_LENGTH, fil) != NULL) { + char domain[LINE_LENGTH]; + char ltype[LINE_LENGTH]; + char item[LINE_LENGTH]; + char value[LINE_LENGTH]; + int i; + size_t j; + char *tptr; + + tptr = buf; + /* skip the leading white space */ + while (*tptr && isspace(*tptr)) + tptr++; + strncpy(buf, tptr, sizeof(buf)-1); + buf[sizeof(buf)-1] = '\0'; + + /* Rip off the comments */ + tptr = strchr(buf,'#'); + if (tptr) + *tptr = '\0'; + /* Rip off the newline char */ + tptr = strchr(buf,'\n'); + if (tptr) + *tptr = '\0'; + /* Anything left ? */ + if (!strlen(buf)) { + memset(buf, 0, sizeof(buf)); + continue; + } + + memset(domain, 0, sizeof(domain)); + memset(ltype, 0, sizeof(ltype)); + memset(item, 0, sizeof(item)); + memset(value, 0, sizeof(value)); + + i = sscanf(buf,"%s%s%s%s", domain, ltype, item, value); +/* + D(("scanned line[%d]: domain[%s], ltype[%s], item[%s], value[%s]", + i, domain, ltype, item, value)); +*/ + + for(j=0; j < strlen(ltype); j++) + ltype[j]=tolower(ltype[j]); + for(j=0; j < strlen(item); j++) + item[j]=tolower(item[j]); + for(j=0; j < strlen(value); j++) + value[j]=tolower(value[j]); + + if (i == 4) { /* a complete line */ + if (strcmp(pwd->pw_name, domain) == 0) /* this user have a limit */ + process_limit(LIMITS_DEF_USER, ltype, item, value, pl); + else if (domain[0]=='@') { + if (pam_modutil_user_in_group_nam_nam(pwd, domain+1)) + process_limit(LIMITS_DEF_GROUP, ltype, item, value, pl); + } else if (domain[0]=='%') { + if (strcmp(domain,"%") == 0) + process_limit(LIMITS_DEF_ALL, ltype, item, value, pl); + else if (pam_modutil_user_in_group_nam_nam(pwd, domain+1)) { + process_limit(LIMITS_DEF_ALLGROUP, ltype, item, value, pl); + } + } else if (strcmp(domain, "*") == 0) { + process_limit(LIMITS_DEF_DEFAULT, ltype, item, value, pl); + } + } else if (i == 2 && ltype[0] == '-') { /* Probably a no-limit line */ + if (strcmp(pwd->pw_name, domain) == 0) { + fclose(fil); + return LIMHANDLE_IGNORE; + } else if (domain[0] == '@' && pam_modutil_user_in_group_nam_nam(pwd, domain+1)) { + fclose(fil); + return LIMHANDLE_IGNORE; + } + } else { + g_printerr("invalid line '%s' - skipped\n", buf); + } + } + fclose(fil); + return LIMHANDLE_OK; +} + +static int setup_limits(struct pam_limit_s *pl) { + int i; + int status; + int retval = LIMHANDLE_OK; + + for (i=0, status=LIMITED_OK; ilimits[i].supported) { + /* skip it if its not known to the system */ + continue; + } + if (pl->limits[i].src_soft == LIMITS_DEF_NONE && + pl->limits[i].src_hard == LIMITS_DEF_NONE) { + /* skip it if its not initialized */ + continue; + } + if (pl->limits[i].limit.rlim_cur > pl->limits[i].limit.rlim_max) + pl->limits[i].limit.rlim_cur = pl->limits[i].limit.rlim_max; + status |= setrlimit(i, &pl->limits[i].limit); + } + + if (status) { + retval = LIMHANDLE_ERROR; + } + + status = setpriority(PRIO_PROCESS, 0, pl->priority); + if (status != 0) { + retval = LIMHANDLE_ERROR; + } + + return retval; +} + +/* now the session stuff */ +int pam_set_limits(const char *conf_file, const char *username) { + int retval; + struct pam_limit_s pl; + struct passwd *pwd; + + pwd = getpwnam(username); + if (NULL == pwd) return LIMHANDLE_ERROR; + + if (!conf_file) conf_file = LIMITS_FILE; + memset(&pl, 0, sizeof(pl)); + + retval = init_limits(&pl); + if (retval != LIMHANDLE_OK) { + g_printerr("cannot initialize\n"); + return retval; + } + + retval = parse_config_file(conf_file, pwd, &pl); + if (retval == LIMHANDLE_IGNORE) { + return LIMHANDLE_OK; + } + if (retval != LIMHANDLE_OK) { + g_printerr("error parsing the configuration file\n"); + return retval; + } + + retval = setup_limits(&pl); + if (retval != LIMHANDLE_OK) { + return retval; + } + + return LIMHANDLE_OK; +} + +/* + * Copyright (c) Cristian Gafton, 1996-1997, + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/spawn-fcgi.c b/spawn-fcgi.c index 1897da4..9e59a6e 100644 --- a/spawn-fcgi.c +++ b/spawn-fcgi.c @@ -28,6 +28,9 @@ # define __attribute__(x) /*NOTHING*/ #endif +#ifdef USE_LIMITS +int pam_set_limits(const char *conf_file, const char *username); +#endif /* spawn-fcgi - spawns fastcgi processes @@ -79,6 +82,10 @@ typedef struct { gboolean show_version; gboolean keep_fds, close_fds; /* keep/close STDOUT/STDERR */ +#ifdef USE_LIMITS + gboolean use_limits; +#endif + gchar *chroot; gchar *uid, *gid; gchar *socketuid, *socketgid; @@ -110,6 +117,10 @@ static options opts = { FALSE, FALSE, FALSE, +#ifdef USE_LIMITS + FALSE, +#endif + NULL, NULL, NULL, NULL, NULL, @@ -310,6 +321,13 @@ int drop_priv() { return -1; } +#ifdef USE_LIMITS + if (opts.use_limits && data.username) { + if (0 != pam_set_limits(NULL, data.username)) { + } + } +#endif + /* do the change before we do the chroot() */ if (data.gid != (gid_t) -1) { if (0 != setgid(data.gid)) { @@ -455,11 +473,14 @@ static const GOptionEntry entries[] = { { "pid", 'P', 0, G_OPTION_ARG_FILENAME, &opts.pid_file, "Name of PID-file for spawned process", "path" }, { "no-daemon", 'n', 0, G_OPTION_ARG_NONE, &opts.no_fork, "Don't fork (for daemontools)", NULL }, { "keep-fds", 0, 0, G_OPTION_ARG_NONE, &opts.keep_fds, "Keep stdout/stderr open (default for --no-daemon)", NULL }, - { "close-fds", 0, 0, G_OPTION_ARG_NONE, &opts.close_fds, "Close stdout/stderr (default it not --no-daemon)", NULL }, + { "close-fds", 0, 0, G_OPTION_ARG_NONE, &opts.close_fds, "Close stdout/stderr (default if not --no-daemon)", NULL }, { "version", 'v', 0, G_OPTION_ARG_NONE, &opts.show_version, "Show version", NULL }, { "chroot", 'c', 0, G_OPTION_ARG_FILENAME, &opts.chroot, "(root only) chroot to directory", "dir" }, { "uid", 'u', 0, G_OPTION_ARG_STRING, &opts.uid, "(root only) change to user-id", "user" }, { "gid", 'g', 0, G_OPTION_ARG_STRING, &opts.gid, "(root only) change to group-id", "group" }, +#ifdef USE_LIMITS + { "limits", 'l', 0, G_OPTION_ARG_NONE, &opts.use_limits, "Set limits using /etc/security/limits.conf", NULL }, +#endif { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &opts.args, " [fcgi app arguments]", NULL }, { NULL, 0, 0, 0, NULL, NULL, NULL } }; diff --git a/wscript b/wscript index 5d143f0..2ca50b0 100644 --- a/wscript +++ b/wscript @@ -14,6 +14,8 @@ blddir = 'build' def set_options(opt): opt.tool_options('compiler_cc') + opt.add_option('--without-limits', action='store_false', help='without /etc/security/limits.conf support', dest = 'limits', default = True) + def tolist(x): if type(x) is types.ListType: @@ -36,6 +38,10 @@ def configure(conf): conf.check_tool('compiler_cc') + if opts.limits: + conf.define("USE_LIMITS", 1) + conf.env['USE_LIMITS'] = opts.limits + conf.define("PACKAGE_NAME", APPNAME) conf.define("PACKAGE_VERSION", VERSION) conf.define("PACKAGE_BUILD_DATE", strftime("%b %d %Y %H:%M:%S UTC", gmtime())); @@ -54,12 +60,22 @@ def configure(conf): conf.write_config_header('config.h') +main_source = ''' + spawn-fcgi.c +''' + +limits_source = ''' + pam_limits.c +''' + def build(bld): + env = bld.env + spawnfcgi = bld.new_task_gen('cc', 'program') spawnfcgi.name = 'spawn-fcgi' - spawnfcgi.source = ''' - spawn-fcgi.c - ''' + spawnfcgi.source = main_source + if env['USE_LIMITS']: + spawnfcgi.source += limits_source spawnfcgi.target = 'spawn-fcgi' spawnfcgi.uselib += 'glib spawnfcgi' spawnfcgi.includes = '.'