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