]> granicus.if.org Git - linux-pam/blob - modules/pam_limits/pam_limits.c
Relevant BUGIDs: 436061
[linux-pam] / modules / pam_limits / pam_limits.c
1 /*
2  * pam_limits - impose resource limits when opening a user session
3  *
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
12  *
13  * See end for Copyright information
14  */
15
16 #if !(defined(linux))
17 #error THIS CODE IS KNOWN TO WORK ONLY ON LINUX !!!
18 #endif 
19
20 #include <security/_pam_aconf.h>
21
22 #include <stdio.h>
23 #include <unistd.h>
24 #include <string.h>
25 #include <ctype.h>
26 #include <stdlib.h>
27 #include <errno.h>
28 #include <syslog.h>
29 #include <stdarg.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <sys/resource.h>
33
34 #include <utmp.h>
35 #ifndef UT_USER  /* some systems have ut_name instead of ut_user */
36 #define UT_USER ut_user
37 #endif
38
39 #include <grp.h>
40 #include <pwd.h>
41
42 /* Module defines */
43 #define LINE_LENGTH 1024
44
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
50 static const char *limits_def_names[] = {
51        "USER",
52        "GROUP",
53        "DEFAULT",
54        "NONE",
55        NULL,
56 };
57
58 struct user_limits_struct {
59     int src_soft;
60     int src_hard;
61     struct rlimit limit;
62 };
63
64 /* internal data */
65 struct pam_limit_s {
66     int login_limit;     /* the max logins limit */
67     int login_limit_def; /* which entry set the login limit */
68     int flag_numsyslogins; /* whether to limit logins only for a
69                               specific user or to count all logins */
70     int priority;        /* the priority to run user process with */
71     int supported[RLIM_NLIMITS];
72     struct user_limits_struct limits[RLIM_NLIMITS];
73     char conf_file[BUFSIZ];
74 };
75
76 #define LIMIT_LOGIN RLIM_NLIMITS+1
77 #define LIMIT_NUMSYSLOGINS RLIM_NLIMITS+2
78
79 #define LIMIT_PRI RLIM_NLIMITS+3
80
81 #define LIMIT_SOFT  1
82 #define LIMIT_HARD  2
83
84 #define PAM_SM_SESSION
85
86 #include <security/pam_modules.h>
87 #include <security/_pam_macros.h>
88
89 /* logging */
90 static void _pam_log(int err, const char *format, ...)
91 {
92     va_list args;
93
94     va_start(args, format);
95     openlog("pam_limits", LOG_CONS|LOG_PID, LOG_AUTH);
96     vsyslog(err, format, args);
97     va_end(args);
98     closelog();
99 }
100
101 /* argument parsing */
102
103 #define PAM_DEBUG_ARG       0x0001
104 #define PAM_DO_SETREUID     0x0002
105
106 static int _pam_parse(int argc, const char **argv, struct pam_limit_s *pl)
107 {
108      int ctrl=0;
109
110      /* step through arguments */
111      for (ctrl=0; argc-- > 0; ++argv) {
112
113           /* generic options */
114
115           if (!strcmp(*argv,"debug"))
116                ctrl |= PAM_DEBUG_ARG;
117           else if (!strncmp(*argv,"conf=",5))
118                 strncpy(pl->conf_file,*argv+5,sizeof(pl->conf_file)-1);
119           else if (!strncmp(*argv,"change_uid",10))
120                 ctrl |= PAM_DO_SETREUID;
121           else {
122                _pam_log(LOG_ERR,"pam_parse: unknown option; %s",*argv);
123           }
124      }
125      pl->conf_file[sizeof(pl->conf_file) - 1] = '\0';
126
127      return ctrl;
128 }
129
130
131 /* limits stuff */
132 #ifdef DEFAULT_CONF_FILE
133 # define LIMITS_FILE DEFAULT_CONF_FILE
134 #else
135 # define LIMITS_FILE "/etc/security/limits.conf"
136 #endif
137
138 #define LIMIT_ERR 1 /* error setting a limit */
139 #define LOGIN_ERR 2 /* too many logins err */
140
141 /* checks if a user is on a list of members of the GID 0 group */
142 static int is_on_list(char * const *list, const char *member)
143 {
144     while (*list) {
145         if (strcmp(*list, member) == 0)
146             return 1;
147         list++;
148     }
149     return 0;
150 }
151
152 /*
153  * Checks if a user is a member of a group - return non-zero if
154  * the user is in the group.
155  */
156 static int is_in_group(const char *user_name, const char *group_name)
157 {
158     struct passwd *pwd;
159     struct group *grp, *pgrp;
160     char uname[LINE_LENGTH], gname[LINE_LENGTH];
161     
162     if (!user_name || !strlen(user_name))
163         return 0;
164     if (!group_name || !strlen(group_name))
165         return 0;
166     memset(uname, 0, sizeof(uname));
167     strncpy(uname, user_name, sizeof(uname)-1);
168     memset(gname, 0, sizeof(gname));
169     strncpy(gname, group_name, sizeof(gname)-1);
170         
171     pwd = getpwnam(uname);
172     if (!pwd)
173         return 0;
174
175     /* the info about this group */
176     grp = getgrnam(gname);
177     if (!grp)
178         return 0;
179     
180     /* first check: is a member of the group_name group ? */
181     if (is_on_list(grp->gr_mem, uname))
182         return 1;
183
184     /* next check: user primary group is group_name ? */
185     pgrp = getgrgid(pwd->pw_gid);
186     if (!pgrp)
187         return 0;
188     if (!strcmp(pgrp->gr_name, gname))
189         return 1;
190         
191     return 0;
192 }
193     
194 /* Counts the number of user logins and check against the limit*/
195 static int check_logins(const char *name, int limit, int ctrl,
196                         struct pam_limit_s *pl)
197 {
198     struct utmp *ut;
199     unsigned int count;
200
201     if (ctrl & PAM_DEBUG_ARG) {
202         _pam_log(LOG_DEBUG, "checking logins for '%s' (maximum of %d)\n",
203                  name, limit);
204     }
205
206     if (limit < 0)
207         return 0; /* no limits imposed */
208     if (limit == 0) /* maximum 0 logins ? */ {
209         _pam_log(LOG_WARNING, "No logins allowed for '%s'\n", name);
210         return LOGIN_ERR;
211     }
212
213     setutent();
214     count = 0;
215     while((ut = getutent())) {
216 #ifdef USER_PROCESS
217         if (ut->ut_type != USER_PROCESS)
218             continue;
219 #endif
220         if (ut->UT_USER[0] == '\0')
221             continue;
222         if (!pl->flag_numsyslogins) {
223             if ((pl->login_limit_def == LIMITS_DEF_USER)
224                 && strncmp(name, ut->UT_USER, sizeof(ut->UT_USER)) != 0)
225                 continue;
226             if ((pl->login_limit_def == LIMITS_DEF_GROUP)
227                 && !is_in_group(ut->UT_USER, name))
228                 continue;
229         }
230         if (++count > limit)
231             break;
232     }
233     endutent();
234     if (count > limit) {
235         if (name) {
236             _pam_log(LOG_WARNING, "Too many logins (max %d) for %s",
237                      limit, name);
238         } else {
239             _pam_log(LOG_WARNING, "Too many system logins (max %d)", limit);
240         }
241         return LOGIN_ERR;
242     }
243     return 0;
244 }
245
246 static int init_limits(struct pam_limit_s *pl)
247 {
248     int i;
249     int retval = PAM_SUCCESS;
250
251     D(("called."));
252
253     for(i = 0; i < RLIM_NLIMITS; i++) {
254         int r = getrlimit(i, &pl->limits[i].limit);
255         if (r == -1) {
256             if (errno == EINVAL) {
257                 pl->supported[i] = 0;
258             } else {
259                 retval = !PAM_SUCCESS;
260             }
261         } else {
262             pl->supported[i] = 1;
263             pl->limits[i].src_soft = LIMITS_DEF_NONE;
264             pl->limits[i].src_hard = LIMITS_DEF_NONE;
265         }
266     }
267
268     pl->priority = 0;
269     pl->login_limit = -2;
270     pl->login_limit_def = LIMITS_DEF_NONE;
271
272     return retval;
273 }    
274
275 static void process_limit(int source, const char *lim_type,
276                           const char *lim_item, const char *lim_value,
277                           int ctrl, struct pam_limit_s *pl)
278 {
279     int limit_item;
280     int limit_type = 0;
281     long limit_value;
282     const char **endptr = &lim_value;
283     const char *value_orig = lim_value;
284         
285     if (ctrl & PAM_DEBUG_ARG)
286          _pam_log(LOG_DEBUG, "%s: processing %s %s %s for %s\n",
287                   __FUNCTION__,lim_type,lim_item,lim_value,
288                   limits_def_names[source]);
289
290     if (strcmp(lim_item, "cpu") == 0)
291         limit_item = RLIMIT_CPU;
292     else if (strcmp(lim_item, "fsize") == 0)
293         limit_item = RLIMIT_FSIZE;
294     else if (strcmp(lim_item, "data") == 0)
295         limit_item = RLIMIT_DATA;
296     else if (strcmp(lim_item, "stack") == 0)
297         limit_item = RLIMIT_STACK;
298     else if (strcmp(lim_item, "core") == 0)
299         limit_item = RLIMIT_CORE;
300     else if (strcmp(lim_item, "rss") == 0)
301         limit_item = RLIMIT_RSS;
302     else if (strcmp(lim_item, "nproc") == 0)
303         limit_item = RLIMIT_NPROC;
304     else if (strcmp(lim_item, "nofile") == 0)
305         limit_item = RLIMIT_NOFILE;
306     else if (strcmp(lim_item, "memlock") == 0)
307         limit_item = RLIMIT_MEMLOCK;
308     else if (strcmp(lim_item, "as") == 0)
309         limit_item = RLIMIT_AS;
310 #ifdef RLIMIT_LOCKS
311     else if (strcmp(lim_item, "locks") == 0)
312         limit_item = RLIMIT_LOCKS;
313 #endif
314     else if (strcmp(lim_item, "maxlogins") == 0) {
315         limit_item = LIMIT_LOGIN;
316         pl->flag_numsyslogins = 0;
317     } else if (strcmp(lim_item, "maxsyslogins") == 0) {
318         limit_item = LIMIT_NUMSYSLOGINS;
319         pl->flag_numsyslogins = 1;
320     } else if (strcmp(lim_item, "priority") == 0) {
321         limit_item = LIMIT_PRI;
322     } else {
323         _pam_log(LOG_DEBUG,"unknown limit item '%s'", lim_item);
324         return;
325     }
326
327     if (strcmp(lim_type,"soft")==0)
328         limit_type=LIMIT_SOFT;
329     else if (strcmp(lim_type, "hard")==0)
330         limit_type=LIMIT_HARD;
331     else if (strcmp(lim_type,"-")==0)
332         limit_type=LIMIT_SOFT | LIMIT_HARD;
333     else if (limit_item != LIMIT_LOGIN && limit_item != LIMIT_NUMSYSLOGINS) {
334         _pam_log(LOG_DEBUG,"unknown limit type '%s'", lim_type);
335         return;
336     }
337
338     /*
339      * there is a warning here because the library prototype for this
340      * function is incorrect.
341      */
342     limit_value = strtol(lim_value, endptr, 10);
343
344     if (limit_value == 0 && value_orig == *endptr) { /* no chars read */
345         if (strcmp(lim_value,"-") != 0) {
346             _pam_log(LOG_DEBUG,"wrong limit value '%s'", lim_value);
347             return;
348         } else
349             if (limit_item != LIMIT_LOGIN) {
350                 if (ctrl & PAM_DEBUG_ARG)
351                     _pam_log(LOG_DEBUG,
352                             "'-' limit value valid for maxlogins type only");
353                 return;
354             } else
355                 limit_value = -1;
356     }
357     
358     switch(limit_item) {
359         case RLIMIT_CPU:
360             limit_value *= 60;
361             break;
362         case RLIMIT_FSIZE:
363         case RLIMIT_DATA:
364         case RLIMIT_STACK:
365         case RLIMIT_CORE:
366         case RLIMIT_RSS:
367         case RLIMIT_MEMLOCK:
368         case RLIMIT_AS:
369             limit_value *= 1024;
370             break;
371     }
372
373     if ( (limit_item != LIMIT_LOGIN)
374          && (limit_item != LIMIT_NUMSYSLOGINS)
375          && (limit_item != LIMIT_PRI) ) {
376         if (limit_type & LIMIT_SOFT) {
377             if (pl->limits[limit_item].src_soft < source) {
378                 return;
379             } else {
380                 pl->limits[limit_item].limit.rlim_cur = limit_value;
381                 pl->limits[limit_item].src_soft = source;
382             }
383         }
384         if (limit_type & LIMIT_HARD) {
385             if (pl->limits[limit_item].src_hard < source) {
386                 return;
387             } else {
388                 pl->limits[limit_item].limit.rlim_max = limit_value;
389                 pl->limits[limit_item].src_hard = source;
390             }
391         }
392     } else {
393         if (limit_item == LIMIT_PRI) {
394                 /* additional check */
395                 pl->priority = ((limit_value>0)?limit_value:0);
396         } else {
397                 if (pl->login_limit_def < source) {
398                     return;
399                 } else {
400                     pl->login_limit = limit_value;
401                     pl->login_limit_def = source;
402                 }
403         }
404     }
405     return;
406 }
407
408 static int parse_config_file(const char *uname, int ctrl,
409                              struct pam_limit_s *pl)
410 {
411     FILE *fil;
412     char buf[LINE_LENGTH];
413     
414 #define CONF_FILE (pl->conf_file[0])?pl->conf_file:LIMITS_FILE
415     /* check for the LIMITS_FILE */
416     if (ctrl & PAM_DEBUG_ARG)
417         _pam_log(LOG_DEBUG,"reading settings from '%s'", CONF_FILE);
418     fil = fopen(CONF_FILE, "r");
419     if (fil == NULL) {
420         _pam_log (LOG_WARNING, "can not read settings from %s", CONF_FILE);
421         return PAM_SERVICE_ERR;
422     }
423 #undef CONF_FILE
424     
425     /* init things */
426     memset(buf, 0, sizeof(buf));
427     /* start the show */
428     while (fgets(buf, LINE_LENGTH, fil) != NULL) {
429         char domain[LINE_LENGTH];
430         char ltype[LINE_LENGTH];
431         char item[LINE_LENGTH];
432         char value[LINE_LENGTH];
433         int i,j;
434         char *tptr;
435         
436         tptr = buf;
437         /* skip the leading white space */
438         while (*tptr && isspace(*tptr))
439             tptr++;
440         strncpy(buf, tptr, sizeof(buf)-1);
441         buf[sizeof(buf)-1] = '\0';
442                                 
443         /* Rip off the comments */
444         tptr = strchr(buf,'#');
445         if (tptr)
446             *tptr = '\0';
447         /* Rip off the newline char */
448         tptr = strchr(buf,'\n');
449         if (tptr)
450             *tptr = '\0';
451         /* Anything left ? */
452         if (!strlen(buf)) {
453             memset(buf, 0, sizeof(buf));
454             continue;
455         }
456
457         memset(domain, 0, sizeof(domain));
458         memset(ltype, 0, sizeof(ltype));
459         memset(item, 0, sizeof(item));
460         memset(value, 0, sizeof(value));
461         
462         i = sscanf(buf,"%s%s%s%s", domain, ltype, item, value);
463         D(("scanned line[%d]: domain[%s], ltype[%s], item[%s], value[%s]",
464            i, domain, ltype, item, value));
465
466         for(j=0; j < strlen(domain); j++)
467             domain[j]=tolower(domain[j]);
468         for(j=0; j < strlen(ltype); j++)
469             ltype[j]=tolower(ltype[j]);
470         for(j=0; j < strlen(item); j++)
471             item[j]=tolower(item[j]);
472         for(j=0; j < strlen(value); j++)
473             value[j]=tolower(value[j]);
474
475         if (i == 4) { /* a complete line */
476             if (strcmp(uname, domain) == 0) /* this user have a limit */
477                 process_limit(LIMITS_DEF_USER, ltype, item, value, ctrl, pl);
478             else if (domain[0]=='@') {
479                 _pam_log(LOG_DEBUG, "checking if %s is in group %s",
480                          uname, domain + 1);
481                 if (is_in_group(uname, domain+1))
482                     process_limit(LIMITS_DEF_GROUP, ltype, item, value, ctrl,
483                                   pl);
484             } else if (strcmp(domain, "*") == 0)
485                 process_limit(LIMITS_DEF_DEFAULT, ltype, item, value, ctrl,
486                               pl);
487         } else if (i == 2 && ltype[0] == '-') { /* Probably a no-limit line */
488             if (strcmp(uname, domain) == 0) {
489                 _pam_log(LOG_DEBUG, "no limits for '%s'", uname);
490                 fclose(fil);
491                 return PAM_IGNORE;
492             } else if (domain[0] == '@' && is_in_group(uname, domain+1)) {
493                 _pam_log(LOG_DEBUG, "no limits for '%s' in group '%s'",
494                          uname, domain+1);
495                 fclose(fil);
496                 return PAM_IGNORE;
497             }
498         } else {
499             _pam_log(LOG_DEBUG,"invalid line '%s' - skipped", buf);
500         }
501     }
502     fclose(fil);
503     return PAM_SUCCESS;    
504 }
505
506 static int setup_limits(const char * uname, int ctrl, struct pam_limit_s *pl)
507 {
508     int i;
509     int retval = PAM_SUCCESS;
510     
511     for (i=0; i<RLIM_NLIMITS; i++) {
512         if (pl->limits[i].limit.rlim_cur > pl->limits[i].limit.rlim_max)
513             pl->limits[i].limit.rlim_cur = pl->limits[i].limit.rlim_max;
514         if (!pl->supported[i]) {
515             /* skip it if its not known to the system */
516             continue;
517         }
518         retval |= setrlimit(i, &pl->limits[i].limit);
519     }
520     
521     if (retval != PAM_SUCCESS)
522         retval = LIMIT_ERR;
523
524     retval=setpriority(PRIO_PROCESS, 0, pl->priority);
525     
526     if (retval != PAM_SUCCESS)
527         retval = LIMIT_ERR;
528
529     if (pl->login_limit > 0) {
530         if (check_logins(uname, pl->login_limit, ctrl, pl) == LOGIN_ERR)
531             retval |= LOGIN_ERR;
532     } else if (pl->login_limit == 0)
533         retval |= LOGIN_ERR;
534     return retval;
535 }
536             
537 /* now the session stuff */
538 PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags,
539                                    int argc, const char **argv)
540 {
541     int retval;
542     char *user_name;
543     struct passwd *pwd;
544     int ctrl;
545     struct pam_limit_s pl;
546
547     D(("called."));
548
549     memset(&pl, 0, sizeof(pl));
550
551     ctrl = _pam_parse(argc, argv, &pl);
552     retval = pam_get_item( pamh, PAM_USER, (void*) &user_name );
553     if ( user_name == NULL || retval != PAM_SUCCESS ) {
554         _pam_log(LOG_CRIT, "open_session - error recovering username");
555         return PAM_SESSION_ERR;
556      }
557         
558     pwd = getpwnam(user_name);
559     if (!pwd) {
560         if (ctrl & PAM_DEBUG_ARG)
561             _pam_log(LOG_WARNING, "open_session username '%s' does not exist",
562                                    user_name);
563         return PAM_SESSION_ERR;
564     }
565                      
566     /* do not impose limits on UID 0 accounts */
567     if (!pwd->pw_uid) {
568         if (ctrl & PAM_DEBUG_ARG)
569             _pam_log(LOG_DEBUG, "user '%s' have UID 0 - no limits imposed",
570                                 user_name);
571         return PAM_SUCCESS;
572     }
573         
574     retval = init_limits(&pl);
575     if (retval != PAM_SUCCESS) {
576         _pam_log(LOG_WARNING, "cannot initialize");
577         return PAM_IGNORE;
578     }
579
580     retval = parse_config_file(pwd->pw_name, ctrl, &pl);
581     if (retval == PAM_IGNORE) {
582         D(("the configuration file has an applicable '<domain> -' entry"));
583         return PAM_SUCCESS;
584     }
585     if (retval != PAM_SUCCESS) {
586         _pam_log(LOG_WARNING, "error parsing the configuration file");
587         return PAM_IGNORE;
588     }
589
590     if (ctrl & PAM_DO_SETREUID)
591         setreuid(pwd->pw_uid, -1);
592     retval = setup_limits(pwd->pw_name, ctrl, &pl);
593     if (retval & LOGIN_ERR) {
594         return PAM_PERM_DENIED;
595     }
596
597     return PAM_SUCCESS;
598 }
599
600 PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags,
601                                     int argc, const char **argv)
602 {
603      /* nothing to do */
604      return PAM_SUCCESS;
605 }
606
607 #ifdef PAM_STATIC
608
609 /* static module data */
610
611 struct pam_module _pam_limits_modstruct = {
612      "pam_limits",
613      NULL,
614      NULL,
615      NULL,
616      pam_sm_open_session,
617      pam_sm_close_session,
618      NULL
619 };
620 #endif
621
622 /*
623  * Copyright (c) Cristian Gafton, 1996-1997, <gafton@redhat.com>
624  *                                              All rights reserved.
625  *
626  * Redistribution and use in source and binary forms, with or without
627  * modification, are permitted provided that the following conditions
628  * are met:
629  * 1. Redistributions of source code must retain the above copyright
630  *    notice, and the entire permission notice in its entirety,
631  *    including the disclaimer of warranties.
632  * 2. Redistributions in binary form must reproduce the above copyright
633  *    notice, this list of conditions and the following disclaimer in the
634  *    documentation and/or other materials provided with the distribution.
635  * 3. The name of the author may not be used to endorse or promote
636  *    products derived from this software without specific prior
637  *    written permission.
638  * 
639  * ALTERNATIVELY, this product may be distributed under the terms of
640  * the GNU Public License, in which case the provisions of the GPL are
641  * required INSTEAD OF the above restrictions.  (This clause is
642  * necessary due to a potential bad interaction between the GPL and
643  * the restrictions contained in a BSD-style copyright.)
644  * 
645  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
646  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
647  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
648  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
649  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
650  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
651  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
652  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
653  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
654  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
655  * OF THE POSSIBILITY OF SUCH DAMAGE.
656  */