2 * pam_limits - impose resource limits when opening a user session
4 * 1.6 - modified for PLD (added process priority settings)
5 * by Marcin Korzonek <mkorz@shadow.eu.org>
6 * 1.5 - Elliot Lee's "max system logins patch"
7 * 1.4 - addressed bug in configuration file parser
8 * 1.3 - modified the configuration file format
9 * 1.2 - added 'debug' and 'conf=' arguments
10 * 1.1 - added @group support
11 * 1.0 - initial release - Linux ONLY
13 * See end for Copyright information
17 #error THIS CODE IS KNOWN TO WORK ONLY ON LINUX !!!
20 #include <security/_pam_aconf.h>
30 #include <sys/types.h>
32 #include <sys/resource.h>
35 #ifndef UT_USER /* some systems have ut_name instead of ut_user */
36 #define UT_USER ut_user
43 #define LINE_LENGTH 1024
45 #define LIMITS_DEF_USER 0 /* limit was set by an user entry */
46 #define LIMITS_DEF_GROUP 1 /* limit was set by a group entry */
47 #define LIMITS_DEF_DEFAULT 2 /* limit was set by an default entry */
48 #define LIMITS_DEF_NONE 3 /* this limit was not set yet */
49 #define LIMITS_DEF_ALL 4 /* limit was set by an default entry */
50 #define LIMITS_DEF_ALLGROUP 5 /* limit was set by a group entry */
52 static const char *limits_def_names[] = {
62 struct user_limits_struct {
70 int login_limit; /* the max logins limit */
71 int login_limit_def; /* which entry set the login limit */
72 int flag_numsyslogins; /* whether to limit logins only for a
73 specific user or to count all logins */
74 int priority; /* the priority to run user process with */
75 int supported[RLIM_NLIMITS];
76 struct user_limits_struct limits[RLIM_NLIMITS];
77 char conf_file[BUFSIZ];
78 int utmp_after_pam_call;
81 #define LIMIT_LOGIN RLIM_NLIMITS+1
82 #define LIMIT_NUMSYSLOGINS RLIM_NLIMITS+2
84 #define LIMIT_PRI RLIM_NLIMITS+3
89 #define PAM_SM_SESSION
91 #include <security/pam_modules.h>
92 #include <security/_pam_macros.h>
93 #include <security/_pam_modutil.h>
96 static void _pam_log(int err, const char *format, ...)
100 va_start(args, format);
101 openlog("pam_limits", LOG_CONS|LOG_PID, LOG_AUTH);
102 vsyslog(err, format, args);
107 /* argument parsing */
109 #define PAM_DEBUG_ARG 0x0001
110 #define PAM_DO_SETREUID 0x0002
111 #define PAM_UTMP_EARLY 0x0004
113 static int _pam_parse(int argc, const char **argv, struct pam_limit_s *pl)
117 /* step through arguments */
118 for (ctrl=0; argc-- > 0; ++argv) {
120 /* generic options */
122 if (!strcmp(*argv,"debug")) {
123 ctrl |= PAM_DEBUG_ARG;
124 } else if (!strncmp(*argv,"conf=",5)) {
125 strncpy(pl->conf_file,*argv+5,sizeof(pl->conf_file)-1);
126 } else if (!strncmp(*argv,"change_uid",10)) {
127 ctrl |= PAM_DO_SETREUID;
128 } else if (!strcmp(*argv,"utmp_early")) {
129 ctrl |= PAM_UTMP_EARLY;
131 _pam_log(LOG_ERR,"pam_parse: unknown option; %s",*argv);
134 pl->conf_file[sizeof(pl->conf_file) - 1] = '\0';
141 #ifdef DEFAULT_CONF_FILE
142 # define LIMITS_FILE DEFAULT_CONF_FILE
144 # define LIMITS_FILE "/etc/security/limits.conf"
147 #define LIMITED_OK 0 /* limit setting appeared to work */
148 #define LIMIT_ERR 1 /* error setting a limit */
149 #define LOGIN_ERR 2 /* too many logins err */
151 /* Counts the number of user logins and check against the limit*/
153 check_logins (pam_handle_t *pamh, const char *name, int limit, int ctrl,
154 struct pam_limit_s *pl)
159 if (ctrl & PAM_DEBUG_ARG) {
160 _pam_log(LOG_DEBUG, "checking logins for '%s' (maximum of %d)\n",
165 return 0; /* no limits imposed */
166 if (limit == 0) /* maximum 0 logins ? */ {
167 _pam_log(LOG_WARNING, "No logins allowed for '%s'\n", name);
173 /* Because there is no definition about when an application
174 actually adds a utmp entry, some applications bizarrely do the
175 utmp call before the have PAM authenticate them to the system:
176 you're logged it, sort of...? Anyway, you can use the
177 "utmp_early" module argument in your PAM config file to make
178 allowances for this sort of problem. (There should be a PAM
179 standard for this, since if a module wants to actually map a
180 username then any early utmp entry will be for the unmapped
183 if (ctrl & PAM_UTMP_EARLY) {
189 while((ut = getutent())) {
191 if (ut->ut_type != USER_PROCESS) {
195 if (ut->UT_USER[0] == '\0') {
198 if (!pl->flag_numsyslogins) {
199 if (((pl->login_limit_def == LIMITS_DEF_USER)
200 || (pl->login_limit_def == LIMITS_DEF_GROUP)
201 || (pl->login_limit_def == LIMITS_DEF_DEFAULT))
202 && strncmp(name, ut->UT_USER, sizeof(ut->UT_USER)) != 0) {
205 if ((pl->login_limit_def == LIMITS_DEF_ALLGROUP)
206 && !_pammodutil_user_in_group_nam_nam(pamh, ut->UT_USER, name)) {
210 if (++count > limit) {
217 _pam_log(LOG_WARNING, "Too many logins (max %d) for %s",
220 _pam_log(LOG_WARNING, "Too many system logins (max %d)", limit);
227 static int init_limits(struct pam_limit_s *pl)
230 int retval = PAM_SUCCESS;
234 for(i = 0; i < RLIM_NLIMITS; i++) {
235 int r = getrlimit(i, &pl->limits[i].limit);
237 if (errno == EINVAL) {
238 pl->supported[i] = 0;
240 retval = !PAM_SUCCESS;
243 pl->supported[i] = 1;
244 pl->limits[i].src_soft = LIMITS_DEF_NONE;
245 pl->limits[i].src_hard = LIMITS_DEF_NONE;
250 pl->login_limit = -2;
251 pl->login_limit_def = LIMITS_DEF_NONE;
256 static void process_limit(int source, const char *lim_type,
257 const char *lim_item, const char *lim_value,
258 int ctrl, struct pam_limit_s *pl)
264 const char *value_orig = lim_value;
266 if (ctrl & PAM_DEBUG_ARG)
267 _pam_log(LOG_DEBUG, "%s: processing %s %s %s for %s\n",
268 __FUNCTION__,lim_type,lim_item,lim_value,
269 limits_def_names[source]);
271 if (strcmp(lim_item, "cpu") == 0)
272 limit_item = RLIMIT_CPU;
273 else if (strcmp(lim_item, "fsize") == 0)
274 limit_item = RLIMIT_FSIZE;
275 else if (strcmp(lim_item, "data") == 0)
276 limit_item = RLIMIT_DATA;
277 else if (strcmp(lim_item, "stack") == 0)
278 limit_item = RLIMIT_STACK;
279 else if (strcmp(lim_item, "core") == 0)
280 limit_item = RLIMIT_CORE;
281 else if (strcmp(lim_item, "rss") == 0)
282 limit_item = RLIMIT_RSS;
283 else if (strcmp(lim_item, "nproc") == 0)
284 limit_item = RLIMIT_NPROC;
285 else if (strcmp(lim_item, "nofile") == 0)
286 limit_item = RLIMIT_NOFILE;
287 else if (strcmp(lim_item, "memlock") == 0)
288 limit_item = RLIMIT_MEMLOCK;
289 else if (strcmp(lim_item, "as") == 0)
290 limit_item = RLIMIT_AS;
292 else if (strcmp(lim_item, "locks") == 0)
293 limit_item = RLIMIT_LOCKS;
295 else if (strcmp(lim_item, "maxlogins") == 0) {
296 limit_item = LIMIT_LOGIN;
297 pl->flag_numsyslogins = 0;
298 } else if (strcmp(lim_item, "maxsyslogins") == 0) {
299 limit_item = LIMIT_NUMSYSLOGINS;
300 pl->flag_numsyslogins = 1;
301 } else if (strcmp(lim_item, "priority") == 0) {
302 limit_item = LIMIT_PRI;
304 _pam_log(LOG_DEBUG,"unknown limit item '%s'", lim_item);
308 if (strcmp(lim_type,"soft")==0)
309 limit_type=LIMIT_SOFT;
310 else if (strcmp(lim_type, "hard")==0)
311 limit_type=LIMIT_HARD;
312 else if (strcmp(lim_type,"-")==0)
313 limit_type=LIMIT_SOFT | LIMIT_HARD;
314 else if (limit_item != LIMIT_LOGIN && limit_item != LIMIT_NUMSYSLOGINS) {
315 _pam_log(LOG_DEBUG,"unknown limit type '%s'", lim_type);
319 limit_value = strtol (lim_value, &endptr, 10);
321 /* special case value when limiting logins */
322 if (limit_value == 0 && value_orig == endptr) { /* no chars read */
323 if (strcmp(lim_value,"-") != 0) {
324 _pam_log(LOG_DEBUG,"wrong limit value '%s'", lim_value);
327 if (limit_item != LIMIT_LOGIN) {
328 if (ctrl & PAM_DEBUG_ARG)
330 "'-' limit value valid for maxlogins type only");
336 /* one more special case when limiting logins */
337 if ((source == LIMITS_DEF_ALL || source == LIMITS_DEF_ALLGROUP)
338 && (limit_item != LIMIT_LOGIN)) {
339 if (ctrl & PAM_DEBUG_ARG)
341 "'%%' domain valid for maxlogins type only");
360 if ( (limit_item != LIMIT_LOGIN)
361 && (limit_item != LIMIT_NUMSYSLOGINS)
362 && (limit_item != LIMIT_PRI) ) {
363 if (limit_type & LIMIT_SOFT) {
364 if (pl->limits[limit_item].src_soft < source) {
367 pl->limits[limit_item].limit.rlim_cur = limit_value;
368 pl->limits[limit_item].src_soft = source;
371 if (limit_type & LIMIT_HARD) {
372 if (pl->limits[limit_item].src_hard < source) {
375 pl->limits[limit_item].limit.rlim_max = limit_value;
376 pl->limits[limit_item].src_hard = source;
380 /* recent kernels support negative priority limits (=raise priority) */
382 if (limit_item == LIMIT_PRI) {
383 pl->priority = limit_value;
385 if (pl->login_limit_def < source) {
388 pl->login_limit = limit_value;
389 pl->login_limit_def = source;
396 static int parse_config_file(pam_handle_t *pamh, const char *uname, int ctrl,
397 struct pam_limit_s *pl)
400 char buf[LINE_LENGTH];
402 #define CONF_FILE (pl->conf_file[0])?pl->conf_file:LIMITS_FILE
403 /* check for the LIMITS_FILE */
404 if (ctrl & PAM_DEBUG_ARG)
405 _pam_log(LOG_DEBUG,"reading settings from '%s'", CONF_FILE);
406 fil = fopen(CONF_FILE, "r");
408 _pam_log (LOG_WARNING, "can not read settings from %s", CONF_FILE);
409 return PAM_SERVICE_ERR;
414 memset(buf, 0, sizeof(buf));
416 while (fgets(buf, LINE_LENGTH, fil) != NULL) {
417 char domain[LINE_LENGTH];
418 char ltype[LINE_LENGTH];
419 char item[LINE_LENGTH];
420 char value[LINE_LENGTH];
425 /* skip the leading white space */
426 while (*tptr && isspace(*tptr))
428 strncpy(buf, tptr, sizeof(buf)-1);
429 buf[sizeof(buf)-1] = '\0';
431 /* Rip off the comments */
432 tptr = strchr(buf,'#');
435 /* Rip off the newline char */
436 tptr = strchr(buf,'\n');
439 /* Anything left ? */
441 memset(buf, 0, sizeof(buf));
445 memset(domain, 0, sizeof(domain));
446 memset(ltype, 0, sizeof(ltype));
447 memset(item, 0, sizeof(item));
448 memset(value, 0, sizeof(value));
450 i = sscanf(buf,"%s%s%s%s", domain, ltype, item, value);
451 D(("scanned line[%d]: domain[%s], ltype[%s], item[%s], value[%s]",
452 i, domain, ltype, item, value));
454 for(j=0; j < strlen(domain); j++)
455 domain[j]=tolower(domain[j]);
456 for(j=0; j < strlen(ltype); j++)
457 ltype[j]=tolower(ltype[j]);
458 for(j=0; j < strlen(item); j++)
459 item[j]=tolower(item[j]);
460 for(j=0; j < strlen(value); j++)
461 value[j]=tolower(value[j]);
463 if (i == 4) { /* a complete line */
464 if (strcmp(uname, domain) == 0) /* this user have a limit */
465 process_limit(LIMITS_DEF_USER, ltype, item, value, ctrl, pl);
466 else if (domain[0]=='@') {
467 if (ctrl & PAM_DEBUG_ARG) {
468 _pam_log(LOG_DEBUG, "checking if %s is in group %s",
471 if (_pammodutil_user_in_group_nam_nam(pamh, uname, domain+1))
472 process_limit(LIMITS_DEF_GROUP, ltype, item, value, ctrl,
474 } else if (domain[0]=='%') {
475 if (ctrl & PAM_DEBUG_ARG) {
476 _pam_log(LOG_DEBUG, "checking if %s is in group %s",
479 if (strcmp(domain,"%") == 0)
480 process_limit(LIMITS_DEF_ALL, ltype, item, value, ctrl,
482 else if (_pammodutil_user_in_group_nam_nam(pamh, uname, domain+1))
483 process_limit(LIMITS_DEF_ALLGROUP, ltype, item, value, ctrl,
485 } else if (strcmp(domain, "*") == 0)
486 process_limit(LIMITS_DEF_DEFAULT, ltype, item, value, ctrl,
488 } else if (i == 2 && ltype[0] == '-') { /* Probably a no-limit line */
489 if (strcmp(uname, domain) == 0) {
490 if (ctrl & PAM_DEBUG_ARG) {
491 _pam_log(LOG_DEBUG, "no limits for '%s'", uname);
495 } else if (domain[0] == '@' && _pammodutil_user_in_group_nam_nam(pamh, uname, domain+1)) {
496 if (ctrl & PAM_DEBUG_ARG) {
497 _pam_log(LOG_DEBUG, "no limits for '%s' in group '%s'",
504 _pam_log(LOG_DEBUG,"invalid line '%s' - skipped", buf);
511 static int setup_limits(pam_handle_t *pamh,
512 const char *uname, uid_t uid, int ctrl,
513 struct pam_limit_s *pl)
517 int retval = LIMITED_OK;
520 /* do not impose limits (+ve limits anyway) on the superuser */
521 if (pl->priority > 0) {
522 if (ctrl & PAM_DEBUG_ARG) {
523 _pam_log(LOG_DEBUG, "user '%s' has UID 0 - no limits imposed",
530 for (i=0, status=LIMITED_OK; i<RLIM_NLIMITS; i++) {
531 if (pl->limits[i].limit.rlim_cur > pl->limits[i].limit.rlim_max)
532 pl->limits[i].limit.rlim_cur = pl->limits[i].limit.rlim_max;
533 if (!pl->supported[i]) {
534 /* skip it if its not known to the system */
537 status |= setrlimit(i, &pl->limits[i].limit);
544 status = setpriority(PRIO_PROCESS, 0, pl->priority);
550 D(("skip login limit check for uid=0"));
551 } else if (pl->login_limit > 0) {
552 if (check_logins(pamh, uname, pl->login_limit, ctrl, pl) == LOGIN_ERR) {
555 } else if (pl->login_limit == 0) {
562 /* now the session stuff */
563 PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags,
564 int argc, const char **argv)
570 struct pam_limit_s pl;
574 memset(&pl, 0, sizeof(pl));
576 ctrl = _pam_parse(argc, argv, &pl);
577 retval = pam_get_item( pamh, PAM_USER, (void*) &user_name );
578 if ( user_name == NULL || retval != PAM_SUCCESS ) {
579 _pam_log(LOG_CRIT, "open_session - error recovering username");
580 return PAM_SESSION_ERR;
583 pwd = getpwnam(user_name);
585 if (ctrl & PAM_DEBUG_ARG)
586 _pam_log(LOG_WARNING, "open_session username '%s' does not exist",
588 return PAM_SESSION_ERR;
591 retval = init_limits(&pl);
592 if (retval != PAM_SUCCESS) {
593 _pam_log(LOG_WARNING, "cannot initialize");
597 retval = parse_config_file(pamh, pwd->pw_name, ctrl, &pl);
598 if (retval == PAM_IGNORE) {
599 D(("the configuration file has an applicable '<domain> -' entry"));
602 if (retval != PAM_SUCCESS) {
603 _pam_log(LOG_WARNING, "error parsing the configuration file");
607 if (ctrl & PAM_DO_SETREUID) {
608 setreuid(pwd->pw_uid, -1);
610 retval = setup_limits(pamh, pwd->pw_name, pwd->pw_uid, ctrl, &pl);
611 if (retval != LIMITED_OK) {
612 return PAM_PERM_DENIED;
618 PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags,
619 int argc, const char **argv)
627 /* static module data */
629 struct pam_module _pam_limits_modstruct = {
635 pam_sm_close_session,
641 * Copyright (c) Cristian Gafton, 1996-1997, <gafton@redhat.com>
642 * All rights reserved.
644 * Redistribution and use in source and binary forms, with or without
645 * modification, are permitted provided that the following conditions
647 * 1. Redistributions of source code must retain the above copyright
648 * notice, and the entire permission notice in its entirety,
649 * including the disclaimer of warranties.
650 * 2. Redistributions in binary form must reproduce the above copyright
651 * notice, this list of conditions and the following disclaimer in the
652 * documentation and/or other materials provided with the distribution.
653 * 3. The name of the author may not be used to endorse or promote
654 * products derived from this software without specific prior
655 * written permission.
657 * ALTERNATIVELY, this product may be distributed under the terms of
658 * the GNU Public License, in which case the provisions of the GPL are
659 * required INSTEAD OF the above restrictions. (This clause is
660 * necessary due to a potential bad interaction between the GPL and
661 * the restrictions contained in a BSD-style copyright.)
663 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
664 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
665 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
666 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
667 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
668 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
669 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
670 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
671 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
672 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
673 * OF THE POSSIBILITY OF SUCH DAMAGE.