2 * Copyright information at end of file.
21 #include <sys/resource.h>
22 #include <rpcsvc/ypclnt.h>
24 #include <security/_pam_macros.h>
25 #include <security/pam_modules.h>
26 #include <security/pam_ext.h>
27 #include <security/pam_modutil.h>
33 #include <selinux/selinux.h>
34 #define SELINUX_ENABLED is_selinux_enabled()>0
36 #define SELINUX_ENABLED 0
39 /* this is a front-end for module-application conversations */
41 int _make_remark(pam_handle_t * pamh, unsigned int ctrl,
42 int type, const char *text)
44 int retval = PAM_SUCCESS;
46 if (off(UNIX__QUIET, ctrl)) {
47 retval = pam_prompt(pamh, type, NULL, "%s", text);
53 * set the control flags for the UNIX module.
56 int _set_ctrl(pam_handle_t *pamh, int flags, int *remember, int argc,
63 ctrl = UNIX_DEFAULTS; /* the default selection of options */
65 /* set some flags manually */
67 if (getuid() == 0 && !(flags & PAM_CHANGE_EXPIRED_AUTHTOK)) {
69 set(UNIX__IAMROOT, ctrl);
71 if (flags & PAM_UPDATE_AUTHTOK) {
72 D(("UPDATE_AUTHTOK"));
73 set(UNIX__UPDATE, ctrl);
75 if (flags & PAM_PRELIM_CHECK) {
77 set(UNIX__PRELIM, ctrl);
79 if (flags & PAM_SILENT) {
81 set(UNIX__QUIET, ctrl);
83 /* now parse the arguments to this module */
88 D(("pam_unix arg: %s", *argv));
90 for (j = 0; j < UNIX_CTRLS_; ++j) {
91 if (unix_args[j].token
92 && !strncmp(*argv, unix_args[j].token, strlen(unix_args[j].token))) {
97 if (j >= UNIX_CTRLS_) {
98 pam_syslog(pamh, LOG_ERR,
99 "unrecognized option [%s]", *argv);
101 ctrl &= unix_args[j].mask; /* for turning things off */
102 ctrl |= unix_args[j].flag; /* for turning things on */
104 if (remember != NULL) {
105 if (j == UNIX_REMEMBER_PASSWD) {
106 *remember = strtol(*argv + 9, NULL, 10);
107 if ((*remember == INT_MIN) || (*remember == INT_MAX))
115 ++argv; /* step to next argument */
118 if (flags & PAM_DISALLOW_NULL_AUTHTOK) {
119 D(("DISALLOW_NULL_AUTHTOK"));
120 set(UNIX__NONULL, ctrl);
123 /* auditing is a more sensitive version of debug */
125 if (on(UNIX_AUDIT, ctrl)) {
126 set(UNIX_DEBUG, ctrl);
128 /* return the set of flags */
134 static void _cleanup(pam_handle_t * pamh UNUSED, void *x, int error_status UNUSED)
139 /* ************************************************************** *
140 * Useful non-trivial functions *
141 * ************************************************************** */
144 * the following is used to keep track of the number of times a user fails
145 * to authenticate themself.
148 #define FAIL_PREFIX "-UN*X-FAIL-"
149 #define UNIX_MAX_RETRIES 3
151 struct _pam_failed_auth {
152 char *user; /* user that's failed to be authenticated */
153 char *name; /* attempt from user with name */
154 int uid; /* uid of calling user */
155 int euid; /* euid of calling process */
156 int count; /* number of failures so far */
159 #ifndef PAM_DATA_REPLACE
160 #error "Need to get an updated libpam 0.52 or better"
163 static void _cleanup_failures(pam_handle_t * pamh, void *fl, int err)
166 const void *service = NULL;
167 const void *ruser = NULL;
168 const void *rhost = NULL;
169 const void *tty = NULL;
170 struct _pam_failed_auth *failure;
174 quiet = err & PAM_DATA_SILENT; /* should we log something? */
175 err &= PAM_DATA_REPLACE; /* are we just replacing data? */
176 failure = (struct _pam_failed_auth *) fl;
178 if (failure != NULL) {
180 if (!quiet && !err) { /* under advisement from Sun,may go away */
182 /* log the number of authentication failures */
183 if (failure->count > 1) {
184 (void) pam_get_item(pamh, PAM_SERVICE,
186 (void) pam_get_item(pamh, PAM_RUSER,
188 (void) pam_get_item(pamh, PAM_RHOST,
190 (void) pam_get_item(pamh, PAM_TTY,
192 pam_syslog(pamh, LOG_NOTICE,
193 "%d more authentication failure%s; "
194 "logname=%s uid=%d euid=%d "
195 "tty=%s ruser=%s rhost=%s "
197 failure->count - 1, failure->count == 2 ? "" : "s",
198 failure->name, failure->uid, failure->euid,
199 tty ? (const char *)tty : "", ruser ? (const char *)ruser : "",
200 rhost ? (const char *)rhost : "",
201 (failure->user && failure->user[0] != '\0')
202 ? " user=" : "", failure->user
205 if (failure->count > UNIX_MAX_RETRIES) {
206 pam_syslog(pamh, LOG_ALERT,
207 "service(%s) ignoring max retries; %d > %d",
208 service == NULL ? "**unknown**" : (const char *)service,
214 _pam_delete(failure->user); /* tidy up */
215 _pam_delete(failure->name); /* tidy up */
221 * _unix_getpwnam() searches only /etc/passwd and NIS to find user information
223 static void _unix_cleanup(pam_handle_t *pamh UNUSED, void *data, int error_status UNUSED)
228 int _unix_getpwnam(pam_handle_t *pamh, const char *name,
229 int files, int nis, struct passwd **ret)
233 int matched = 0, buflen;
234 char *slogin, *spasswd, *suid, *sgid, *sgecos, *shome, *sshell, *p;
236 memset(buf, 0, sizeof(buf));
238 if (!matched && files) {
239 int userlen = strlen(name);
240 passwd = fopen("/etc/passwd", "r");
241 if (passwd != NULL) {
242 while (fgets(buf, sizeof(buf), passwd) != NULL) {
243 if ((buf[userlen] == ':') &&
244 (strncmp(name, buf, userlen) == 0)) {
245 p = buf + strlen(buf) - 1;
246 while (isspace(*p) && (p >= buf)) {
257 if (!matched && nis) {
258 char *userinfo = NULL, *domain = NULL;
260 len = yp_get_default_domain(&domain);
261 if (len == YPERR_SUCCESS) {
262 len = yp_bind(domain);
264 if (len == YPERR_SUCCESS) {
265 i = yp_match(domain, "passwd.byname", name,
266 strlen(name), &userinfo, &len);
268 if ((i == YPERR_SUCCESS) && ((size_t)len < sizeof(buf))) {
269 strncpy(buf, userinfo, sizeof(buf) - 1);
270 buf[sizeof(buf) - 1] = '\0';
276 if (matched && (ret != NULL)) {
281 spasswd = strchr(slogin, ':');
282 if (spasswd == NULL) {
287 suid = strchr(spasswd, ':');
293 sgid = strchr(suid, ':');
299 sgecos = strchr(sgid, ':');
300 if (sgecos == NULL) {
305 shome = strchr(sgecos, ':');
311 sshell = strchr(shome, ':');
312 if (sshell == NULL) {
317 buflen = sizeof(struct passwd) +
319 strlen(spasswd) + 1 +
325 *ret = malloc(buflen);
329 memset(*ret, '\0', buflen);
331 (*ret)->pw_uid = strtol(suid, &p, 10);
332 if ((strlen(suid) == 0) || (*p != '\0')) {
338 (*ret)->pw_gid = strtol(sgid, &p, 10);
339 if ((strlen(sgid) == 0) || (*p != '\0')) {
345 p = ((char*)(*ret)) + sizeof(struct passwd);
346 (*ret)->pw_name = strcpy(p, slogin);
348 (*ret)->pw_passwd = strcpy(p, spasswd);
350 (*ret)->pw_gecos = strcpy(p, sgecos);
352 (*ret)->pw_dir = strcpy(p, shome);
354 (*ret)->pw_shell = strcpy(p, sshell);
356 snprintf(buf, sizeof(buf), "_pam_unix_getpwnam_%s", name);
358 if (pam_set_data(pamh, buf,
359 *ret, _unix_cleanup) != PAM_SUCCESS) {
369 * _unix_comsefromsource() is a quick check to see if information about a given
370 * user comes from a particular source (just files and nis for now)
373 int _unix_comesfromsource(pam_handle_t *pamh,
374 const char *name, int files, int nis)
376 return _unix_getpwnam(pamh, name, files, nis, NULL);
380 * _unix_blankpasswd() is a quick check for a blank password
382 * returns TRUE if user does not have a password
383 * - to avoid prompting for one in such cases (CG)
387 _unix_blankpasswd (pam_handle_t *pamh, unsigned int ctrl, const char *name)
389 struct passwd *pwd = NULL;
390 struct spwd *spwdent = NULL;
397 * This function does not have to be too smart if something goes
398 * wrong, return FALSE and let this case to be treated somewhere
402 if (on(UNIX__NONULL, ctrl))
403 return 0; /* will fail but don't let on yet */
405 /* UNIX passwords area */
407 /* Get password file entry... */
408 pwd = pam_modutil_getpwnam (pamh, name);
411 if (strcmp( pwd->pw_passwd, "*NP*" ) == 0)
413 uid_t save_euid, save_uid;
415 save_euid = geteuid();
417 if (save_uid == pwd->pw_uid)
418 setreuid( save_euid, save_uid );
421 if (setreuid( -1, pwd->pw_uid ) == -1) {
424 if(setreuid( -1, pwd->pw_uid ) == -1)
425 /* Will fail elsewhere. */
430 spwdent = pam_modutil_getspnam (pamh, name);
431 if (save_uid == pwd->pw_uid)
432 setreuid( save_uid, save_euid );
434 if (setreuid( -1, 0 ) == -1)
435 setreuid( save_uid, -1 );
436 setreuid( -1, save_euid );
438 } else if (_unix_shadowed(pwd)) {
440 * ...and shadow password file entry for this user,
441 * if shadowing is enabled
443 spwdent = pam_modutil_getspnam(pamh, name);
446 salt = x_strdup(spwdent->sp_pwdp);
448 salt = x_strdup(pwd->pw_passwd);
450 /* Does this user have a password? */
454 if (strlen(salt) == 0)
469 * verify the password of a user
472 #include <sys/types.h>
473 #include <sys/wait.h>
475 static int _unix_run_helper_binary(pam_handle_t *pamh, const char *passwd,
476 unsigned int ctrl, const char *user)
478 int retval, child, fds[2];
479 void (*sighandler)(int) = NULL;
482 /* create a pipe for the password */
483 if (pipe(fds) != 0) {
484 D(("could not make pipe"));
488 if (off(UNIX_NOREAP, ctrl)) {
490 * This code arranges that the demise of the child does not cause
491 * the application to receive a signal it is not expecting - which
492 * may kill the application or worse.
494 * The "noreap" module argument is provided so that the admin can
495 * override this behavior.
497 sighandler = signal(SIGCHLD, SIG_DFL);
505 static char *envp[] = { NULL };
506 char *args[] = { NULL, NULL, NULL, NULL };
508 /* XXX - should really tidy up PAM here too */
511 /* reopen stdin as pipe */
513 dup2(fds[0], STDIN_FILENO);
515 if (getrlimit(RLIMIT_NOFILE,&rlim)==0) {
516 for (i=2; i < (int)rlim.rlim_max; i++) {
522 if (SELINUX_ENABLED && geteuid() == 0) {
523 /* must set the real uid to 0 so the helper will not error
524 out if pam is called from setuid binary (su, sudo...) */
528 /* exec binary helper */
529 args[0] = strdup(CHKPWD_HELPER);
530 args[1] = x_strdup(user);
531 if (off(UNIX__NONULL, ctrl)) { /* this means we've succeeded */
532 args[2]=strdup("nullok");
534 args[2]=strdup("nonull");
537 execve(CHKPWD_HELPER, args, envp);
539 /* should not get here: exit with error */
540 D(("helper binary is not available"));
541 exit(PAM_AUTHINFO_UNAVAIL);
542 } else if (child > 0) {
544 /* if the stored password is NULL */
546 if (passwd != NULL) { /* send the password to the child */
547 write(fds[1], passwd, strlen(passwd)+1);
550 write(fds[1], "", 1); /* blank password */
552 close(fds[0]); /* close here to avoid possible SIGPIPE above */
554 rc=waitpid(child, &retval, 0); /* wait for helper to complete */
556 pam_syslog(pamh, LOG_ERR, "unix_chkpwd waitpid returned %d: %m", rc);
557 retval = PAM_AUTH_ERR;
559 retval = WEXITSTATUS(retval);
565 retval = PAM_AUTH_ERR;
568 if (sighandler != SIG_ERR) {
569 (void) signal(SIGCHLD, sighandler); /* restore old signal handler */
572 D(("returning %d", retval));
576 int _unix_verify_password(pam_handle_t * pamh, const char *name
577 ,const char *p, unsigned int ctrl)
579 struct passwd *pwd = NULL;
580 struct spwd *spwdent = NULL;
589 #ifdef HAVE_PAM_FAIL_DELAY
590 if (off(UNIX_NODELAY, ctrl)) {
591 D(("setting delay"));
592 (void) pam_fail_delay(pamh, 2000000); /* 2 sec delay for on failure */
596 /* locate the entry for this user */
598 D(("locating user's record"));
600 /* UNIX passwords area */
601 pwd = pam_modutil_getpwnam (pamh, name); /* Get password file entry... */
604 if (strcmp( pwd->pw_passwd, "*NP*" ) == 0)
606 uid_t save_euid, save_uid;
608 save_euid = geteuid();
610 if (save_uid == pwd->pw_uid)
611 setreuid( save_euid, save_uid );
614 if (setreuid( -1, pwd->pw_uid ) == -1) {
617 if(setreuid( -1, pwd->pw_uid ) == -1)
618 return PAM_CRED_INSUFFICIENT;
622 spwdent = pam_modutil_getspnam (pamh, name);
623 if (save_uid == pwd->pw_uid)
624 setreuid( save_uid, save_euid );
626 if (setreuid( -1, 0 ) == -1)
627 setreuid( save_uid, -1 );
628 setreuid( -1, save_euid );
630 } else if (_unix_shadowed(pwd)) {
632 * ...and shadow password file entry for this user,
633 * if shadowing is enabled
635 spwdent = pam_modutil_getspnam (pamh, name);
638 salt = x_strdup(spwdent->sp_pwdp);
640 salt = x_strdup(pwd->pw_passwd);
643 data_name = (char *) malloc(sizeof(FAIL_PREFIX) + strlen(name));
644 if (data_name == NULL) {
645 pam_syslog(pamh, LOG_CRIT, "no memory for data-name");
647 strcpy(data_name, FAIL_PREFIX);
648 strcpy(data_name + sizeof(FAIL_PREFIX) - 1, name);
651 retval = PAM_SUCCESS;
652 if (pwd == NULL || salt == NULL || !strcmp(salt, "x") || ((salt[0] == '#') && (salt[1] == '#') && !strcmp(salt + 2, name))) {
654 if (pwd != NULL && (geteuid() || SELINUX_ENABLED)) {
655 /* we are not root perhaps this is the reason? Run helper */
656 D(("running helper binary"));
657 retval = _unix_run_helper_binary(pamh, p, ctrl, name);
659 D(("user's record unavailable"));
662 retval = PAM_USER_UNKNOWN;
664 retval = PAM_AUTHINFO_UNAVAIL;
665 if (on(UNIX_AUDIT, ctrl)) {
666 /* this might be a typo and the user has given a password
667 instead of a username. Careful with this. */
668 pam_syslog(pamh, LOG_ALERT,
669 "check pass; user (%s) unknown", name);
672 if (on(UNIX_DEBUG, ctrl) || pwd == NULL) {
673 pam_syslog(pamh, LOG_ALERT,
674 "check pass; user unknown");
676 /* don't log failure as another pam module can succeed */
682 int salt_len = strlen(salt);
684 /* the stored password is NULL */
685 if (off(UNIX__NONULL, ctrl)) {/* this means we've succeeded */
686 D(("user has empty password - access granted"));
687 retval = PAM_SUCCESS;
689 D(("user has empty password - access denied"));
690 retval = PAM_AUTH_ERR;
692 } else if (!p || (*salt == '*')) {
693 retval = PAM_AUTH_ERR;
695 if (!strncmp(salt, "$1$", 3)) {
697 pp = Goodcrypt_md5(p, salt);
698 if (strcmp(pp, salt) != 0) {
700 pp = Brokencrypt_md5(p, salt);
702 } else if (*salt == '$') {
704 * Ok, we don't know the crypt algorithm, but maybe
705 * libcrypt nows about it? We should try it.
708 pp = x_strdup (crypt(p, salt));
710 pp = bigcrypt(p, salt);
712 p = NULL; /* no longer needed here */
714 /* the moment of truth -- do we agree with the password? */
715 D(("comparing state of pp[%s] and salt[%s]", pp, salt));
718 * Note, we are comparing the bigcrypt of the password with
719 * the contents of the password field. If the latter was
720 * encrypted with regular crypt (and not bigcrypt) it will
721 * have been truncated for storage relative to the output
722 * of bigcrypt here. As such we need to compare only the
723 * stored string with the subset of bigcrypt's result.
724 * Bug 521314: The strncmp comparison is for legacy support.
726 if ((!salt_len && strcmp(pp, salt) == 0) ||
727 (salt_len && strncmp(pp, salt, salt_len) == 0)) {
728 retval = PAM_SUCCESS;
730 retval = PAM_AUTH_ERR;
735 if (retval == PAM_SUCCESS) {
736 if (data_name) /* reset failures */
737 pam_set_data(pamh, data_name, NULL, _cleanup_failures);
739 if (data_name != NULL) {
740 struct _pam_failed_auth *new = NULL;
741 const struct _pam_failed_auth *old = NULL;
743 /* get a failure recorder */
745 new = (struct _pam_failed_auth *)
746 malloc(sizeof(struct _pam_failed_auth));
750 const char *login_name;
751 const void *void_old;
754 login_name = pam_modutil_getlogin(pamh);
755 if (login_name == NULL) {
759 new->user = x_strdup(name ? name : "");
761 new->euid = geteuid();
762 new->name = x_strdup(login_name);
764 /* any previous failures for this user ? */
765 if (pam_get_data(pamh, data_name, &void_old)
772 new->count = old->count + 1;
773 if (new->count >= UNIX_MAX_RETRIES) {
774 retval = PAM_MAXTRIES;
777 const void *service=NULL;
778 const void *ruser=NULL;
779 const void *rhost=NULL;
780 const void *tty=NULL;
782 (void) pam_get_item(pamh, PAM_SERVICE,
784 (void) pam_get_item(pamh, PAM_RUSER,
786 (void) pam_get_item(pamh, PAM_RHOST,
788 (void) pam_get_item(pamh, PAM_TTY,
791 pam_syslog(pamh, LOG_NOTICE,
792 "authentication failure; "
793 "logname=%s uid=%d euid=%d "
794 "tty=%s ruser=%s rhost=%s "
796 new->name, new->uid, new->euid,
797 tty ? (const char *)tty : "",
798 ruser ? (const char *)ruser : "",
799 rhost ? (const char *)rhost : "",
800 (new->user && new->user[0] != '\0')
807 pam_set_data(pamh, data_name, new, _cleanup_failures);
810 pam_syslog(pamh, LOG_CRIT,
811 "no memory for failure recorder");
818 _pam_delete(data_name);
824 D(("done [%d].", retval));
830 * obtain a password from the user
833 int _unix_read_password(pam_handle_t * pamh
838 ,const char *data_name
842 int retval = PAM_SUCCESS;
848 * make sure nothing inappropriate gets returned
851 *pass = token = NULL;
854 * which authentication token are we getting?
857 authtok_flag = on(UNIX__OLD_PASSWD, ctrl) ? PAM_OLDAUTHTOK : PAM_AUTHTOK;
860 * should we obtain the password from a PAM item ?
863 if (on(UNIX_TRY_FIRST_PASS, ctrl) || on(UNIX_USE_FIRST_PASS, ctrl)) {
864 retval = pam_get_item(pamh, authtok_flag, pass);
865 if (retval != PAM_SUCCESS) {
867 pam_syslog(pamh, LOG_ALERT,
868 "pam_get_item returned error to unix-read-password"
871 } else if (*pass != NULL) { /* we have a password! */
873 } else if (on(UNIX_USE_FIRST_PASS, ctrl)) {
874 return PAM_AUTHTOK_RECOVERY_ERR; /* didn't work */
875 } else if (on(UNIX_USE_AUTHTOK, ctrl)
876 && off(UNIX__OLD_PASSWD, ctrl)) {
877 return PAM_AUTHTOK_ERR;
881 * getting here implies we will have to get the password from the
887 char *resp[2] = { NULL, NULL };
889 if (comment != NULL && off(UNIX__QUIET, ctrl)) {
890 retval = pam_info(pamh, "%s", comment);
893 if (retval == PAM_SUCCESS) {
894 retval = pam_prompt(pamh, PAM_PROMPT_ECHO_OFF,
895 &resp[0], "%s", prompt1);
897 if (retval == PAM_SUCCESS && prompt2 != NULL) {
898 retval = pam_prompt(pamh, PAM_PROMPT_ECHO_OFF,
899 &resp[1], "%s", prompt2);
904 if (resp[0] != NULL && resp[replies-1] != NULL) {
905 /* interpret the response */
907 if (retval == PAM_SUCCESS) { /* a good conversation */
912 /* verify that password entered correctly */
913 if (strcmp(token, resp[replies - 1])) {
915 retval = PAM_AUTHTOK_RECOVERY_ERR;
916 _make_remark(pamh, ctrl,
917 PAM_ERROR_MSG, MISTYPED_PASS);
921 pam_syslog(pamh, LOG_NOTICE,
922 "could not recover authentication token");
928 retval = (retval == PAM_SUCCESS)
929 ? PAM_AUTHTOK_RECOVERY_ERR : retval;
934 _pam_delete(resp[1]);
937 if (retval != PAM_SUCCESS) {
940 if (on(UNIX_DEBUG, ctrl))
941 pam_syslog(pamh, LOG_DEBUG,
942 "unable to obtain a password");
945 /* 'token' is the entered password */
947 if (off(UNIX_NOT_SET_PASS, ctrl)) {
949 /* we store this password as an item */
951 retval = pam_set_item(pamh, authtok_flag, token);
952 _pam_delete(token); /* clean it up */
953 if (retval != PAM_SUCCESS
954 || (retval = pam_get_item(pamh, authtok_flag, pass))
958 pam_syslog(pamh, LOG_CRIT, "error manipulating password");
964 * then store it as data specific to this module. pam_end()
965 * will arrange to clean it up.
968 retval = pam_set_data(pamh, data_name, (void *) token, _cleanup);
969 if (retval != PAM_SUCCESS) {
970 pam_syslog(pamh, LOG_CRIT,
971 "error manipulating password data [%s]",
972 pam_strerror(pamh, retval));
977 token = NULL; /* break link to password */
983 int _unix_shadowed(const struct passwd *pwd)
986 if (strcmp(pwd->pw_passwd, "x") == 0) {
989 if ((pwd->pw_passwd[0] == '#') &&
990 (pwd->pw_passwd[1] == '#') &&
991 (strcmp(pwd->pw_name, pwd->pw_passwd + 2) == 0)) {
998 /* ****************************************************************** *
999 * Copyright (c) Jan Rêkorajski 1999.
1000 * Copyright (c) Andrew G. Morgan 1996-8.
1001 * Copyright (c) Alex O. Yuriev, 1996.
1002 * Copyright (c) Cristian Gafton 1996.
1004 * Redistribution and use in source and binary forms, with or without
1005 * modification, are permitted provided that the following conditions
1007 * 1. Redistributions of source code must retain the above copyright
1008 * notice, and the entire permission notice in its entirety,
1009 * including the disclaimer of warranties.
1010 * 2. Redistributions in binary form must reproduce the above copyright
1011 * notice, this list of conditions and the following disclaimer in the
1012 * documentation and/or other materials provided with the distribution.
1013 * 3. The name of the author may not be used to endorse or promote
1014 * products derived from this software without specific prior
1015 * written permission.
1017 * ALTERNATIVELY, this product may be distributed under the terms of
1018 * the GNU Public License, in which case the provisions of the GPL are
1019 * required INSTEAD OF the above restrictions. (This clause is
1020 * necessary due to a potential bad interaction between the GPL and
1021 * the restrictions contained in a BSD-style copyright.)
1023 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
1024 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1025 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
1026 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
1027 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
1028 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
1029 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
1030 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
1031 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
1032 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
1033 * OF THE POSSIBILITY OF SUCH DAMAGE.