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