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