/* * $Id$ * * Copyright information at end of file. */ /* * here is the string to inform the user that the new passwords they * typed were not the same. */ #define MISTYPED_PASS "Sorry, passwords do not match" /* type definition for the control options */ typedef struct { const char *token; unsigned int mask; /* shall assume 32 bits of flags */ unsigned int flag; } UNIX_Ctrls; /* * macro to determine if a given flag is on */ #define on(x,ctrl) (unix_args[x].flag & ctrl) /* * macro to determine that a given flag is NOT on */ #define off(x,ctrl) (!on(x,ctrl)) /* * macro to turn on/off a ctrl flag manually */ #define set(x,ctrl) (ctrl = ((ctrl)&unix_args[x].mask)|unix_args[x].flag) #define unset(x,ctrl) (ctrl &= ~(unix_args[x].flag)) /* the generic mask */ #define _ALL_ON_ (~0U) /* end of macro definitions definitions for the control flags */ /* ****************************************************************** * * ctrl flags proper.. */ /* * here are the various options recognized by the unix module. They * are enumerated here and then defined below. Internal arguments are * given NULL tokens. */ #define UNIX__OLD_PASSWD 0 /* internal */ #define UNIX__VERIFY_PASSWD 1 /* internal */ #define UNIX__IAMROOT 2 /* internal */ #define UNIX_AUDIT 3 /* print more things than debug.. some information may be sensitive */ #define UNIX_USE_FIRST_PASS 4 #define UNIX_TRY_FIRST_PASS 5 #define UNIX_NOT_SET_PASS 6 /* don't set the AUTHTOK items */ #define UNIX__PRELIM 7 /* internal */ #define UNIX__UPDATE 8 /* internal */ #define UNIX__NONULL 9 /* internal */ #define UNIX__QUIET 10 /* internal */ #define UNIX_USE_AUTHTOK 11 /* insist on reading PAM_AUTHTOK */ #define UNIX_SHADOW 12 /* signal shadow on */ #define UNIX_MD5_PASS 13 /* force the use of MD5 passwords */ #define UNIX__NULLOK 14 /* Null token ok */ #define UNIX_RADIUS 15 /* wish to use RADIUS for password */ #define UNIX__SET_DB 16 /* internal - signals redirect to db */ #define UNIX_DEBUG 17 /* send more info to syslog(3) */ #define UNIX_NODELAY 18 /* admin does not want a fail-delay */ #define UNIX_UNIX 19 /* wish to use /etc/passwd for pwd */ #define UNIX_BIGCRYPT 20 /* use DEC-C2 crypt()^x function */ #define UNIX_LIKE_AUTH 21 /* need to auth for setcred to work */ /* -------------- */ #define UNIX_CTRLS_ 22 /* number of ctrl arguments defined */ static const UNIX_Ctrls unix_args[UNIX_CTRLS_] = { /* symbol token name ctrl mask ctrl * * ------------------ ------------------ -------------- ---------- */ /* UNIX__OLD_PASSWD */ { NULL, _ALL_ON_, 01 }, /* UNIX__VERIFY_PASSWD */ { NULL, _ALL_ON_, 02 }, /* UNIX__IAMROOT */ { NULL, _ALL_ON_, 04 }, /* UNIX_AUDIT */ { "audit", _ALL_ON_, 010 }, /* UNIX_USE_FIRST_PASS */ { "use_first_pass", _ALL_ON_^(060), 020 }, /* UNIX_TRY_FIRST_PASS */ { "try_first_pass", _ALL_ON_^(060), 040 }, /* UNIX_NOT_SET_PASS */ { "not_set_pass", _ALL_ON_, 0100 }, /* UNIX__PRELIM */ { NULL, _ALL_ON_^(0600), 0200 }, /* UNIX__UPDATE */ { NULL, _ALL_ON_^(0600), 0400 }, /* UNIX__NONULL */ { NULL, _ALL_ON_, 01000 }, /* UNIX__QUIET */ { NULL, _ALL_ON_, 02000 }, /* UNIX_USE_AUTHTOK */ { "use_authtok", _ALL_ON_, 04000 }, /* UNIX_SHADOW */ { "shadow", _ALL_ON_^(0140000), 010000 }, /* UNIX_MD5_PASS */ { "md5", _ALL_ON_^(02000000), 020000 }, /* UNIX__NULLOK */ { "nullok", _ALL_ON_^(01000), 0 }, /* UNIX_RADIUS */ { "radius", _ALL_ON_^(0110000), 040000 }, /* UNIX__SET_DB */ { NULL, _ALL_ON_, 0100000 }, /* UNIX_DEBUG */ { "debug", _ALL_ON_, 0200000 }, /* UNIX_NODELAY */ { "nodelay", _ALL_ON_, 0400000 }, /* UNIX_UNIX */ { "unix", _ALL_ON_^(050000), 01000000 }, /* UNIX_BIGCRYPT */ { "bigcrypt", _ALL_ON_^(020000), 02000000 }, /* UNIX_LIKE_AUTH */ { "likeauth", _ALL_ON_, 04000000 }, }; #define UNIX_DEFAULTS (unix_args[UNIX__NONULL].flag) /* syslogging function for errors and other information */ static void _log_err(int err, const char *format, ...) { va_list args; va_start(args, format); openlog("PAM_pwdb", LOG_CONS|LOG_PID, LOG_AUTH); vsyslog(err, format, args); va_end(args); closelog(); } /* this is a front-end for module-application conversations */ static int converse(pam_handle_t *pamh, int ctrl, int nargs , struct pam_message **message , struct pam_response **response) { int retval; struct pam_conv *conv; D(("begin to converse")); retval = pam_get_item( pamh, PAM_CONV, (const void **) &conv ) ; if ( retval == PAM_SUCCESS ) { retval = conv->conv(nargs, ( const struct pam_message ** ) message , response, conv->appdata_ptr); D(("returned from application's conversation function")); if (retval != PAM_SUCCESS && on(UNIX_DEBUG,ctrl) ) { _log_err(LOG_DEBUG, "conversation failure [%s]" , pam_strerror(pamh, retval)); } } else if (retval != PAM_CONV_AGAIN) { _log_err(LOG_ERR, "couldn't obtain coversation function [%s]" , pam_strerror(pamh, retval)); } D(("ready to return from module conversation")); return retval; /* propagate error status */ } static int make_remark(pam_handle_t *pamh, unsigned int ctrl , int type, const char *text) { int retval=PAM_SUCCESS; if ( off(UNIX__QUIET, ctrl) ) { struct pam_message *pmsg[1], msg[1]; struct pam_response *resp; pmsg[0] = &msg[0]; msg[0].msg = text; msg[0].msg_style = type; resp = NULL; retval = converse(pamh, ctrl, 1, pmsg, &resp); if (resp) { _pam_drop_reply(resp, 1); } } return retval; } /* * set the control flags for the UNIX module. */ static int set_ctrl(int flags, int argc, const char **argv) { unsigned int ctrl; D(("called.")); ctrl = UNIX_DEFAULTS; /* the default selection of options */ /* set some flags manually */ if ( getuid() == 0 && !(flags & PAM_CHANGE_EXPIRED_AUTHTOK) ) { set(UNIX__IAMROOT, ctrl); } if ( flags & PAM_UPDATE_AUTHTOK ) { set(UNIX__UPDATE, ctrl); } if ( flags & PAM_PRELIM_CHECK ) { set(UNIX__PRELIM, ctrl); } if ( flags & PAM_DISALLOW_NULL_AUTHTOK ) { set(UNIX__NONULL, ctrl); } if ( flags & PAM_SILENT ) { set(UNIX__QUIET, ctrl); } /* now parse the arguments to this module */ while (argc-- > 0) { int j; D(("pam_pwdb arg: %s",*argv)); for (j=0; j= UNIX_CTRLS_ ) { _log_err(LOG_ERR, "unrecognized option [%s]",*argv); } else { ctrl &= unix_args[j].mask; /* for turning things off */ ctrl |= unix_args[j].flag; /* for turning things on */ } ++argv; /* step to next argument */ } /* these are used for updating passwords in specific places */ if (on(UNIX_SHADOW,ctrl) || on(UNIX_RADIUS,ctrl) || on(UNIX_UNIX,ctrl)) { set(UNIX__SET_DB, ctrl); } /* auditing is a more sensitive version of debug */ if ( on(UNIX_AUDIT,ctrl) ) { set(UNIX_DEBUG, ctrl); } /* return the set of flags */ D(("done.")); return ctrl; } /* use this to free strings. ESPECIALLY password strings */ static char *_pam_delete(register char *xx) { _pam_overwrite(xx); _pam_drop(xx); return NULL; } static void _cleanup(pam_handle_t *pamh, void *x, int error_status) { x = _pam_delete( (char *) x ); } /* ************************************************************** * * Useful non-trivial functions * * ************************************************************** */ #include "pam_unix_md.-c" /* * the following is used to keep track of the number of times a user fails * to authenticate themself. */ #define FAIL_PREFIX "-UN*X-FAIL-" #define UNIX_MAX_RETRIES 3 struct _pam_failed_auth { char *user; /* user that's failed to be authenticated */ char *name; /* attempt from user with name */ int id; /* uid of name'd user */ int count; /* number of failures so far */ }; #ifndef PAM_DATA_REPLACE #error "Need to get an updated libpam 0.52 or better" #endif static void _cleanup_failures(pam_handle_t *pamh, void *fl, int err) { int quiet; const char *service=NULL; struct _pam_failed_auth *failure; D(("called")); quiet = err & PAM_DATA_SILENT; /* should we log something? */ err &= PAM_DATA_REPLACE; /* are we just replacing data? */ failure = (struct _pam_failed_auth *) fl; if ( failure != NULL ) { if ( !quiet && !err ) { /* under advisement from Sun,may go away */ /* log the number of authentication failures */ if ( failure->count > 1 ) { (void) pam_get_item(pamh, PAM_SERVICE , (const void **)&service); _log_err(LOG_NOTICE , "%d more authentication failure%s; %s(uid=%d) -> " "%s for %s service" , failure->count-1, failure->count==2 ? "":"s" , failure->name , failure->id , failure->user , service == NULL ? "**unknown**":service ); if ( failure->count > UNIX_MAX_RETRIES ) { _log_err(LOG_ALERT , "service(%s) ignoring max retries; %d > %d" , service == NULL ? "**unknown**":service , failure->count , UNIX_MAX_RETRIES ); } } } failure->user = _pam_delete(failure->user); /* tidy up */ failure->name = _pam_delete(failure->name); /* tidy up */ free(failure); } } /* * verify the password of a user */ #include #include static int pwdb_run_helper_binary(pam_handle_t *pamh, const char *passwd, const char *user) { int retval, child, fds[2]; D(("called.")); /* create a pipe for the password */ if (pipe(fds) != 0) { D(("could not make pipe")); return PAM_AUTH_ERR; } /* fork */ child = fork(); if (child == 0) { static char *args[] = { NULL, NULL, NULL }; static char *envp[] = { NULL }; /* XXX - should really tidy up PAM here too */ while (pwdb_end() == PWDB_SUCCESS); /* reopen stdin as pipe */ close(fds[1]); dup2(fds[0], STDIN_FILENO); /* exec binary helper */ args[0] = x_strdup(CHKPWD_HELPER); args[1] = x_strdup(user); execve(CHKPWD_HELPER, args, envp); /* should not get here: exit with error */ D(("helper binary is not available")); exit(PWDB_SUCCESS+1); } else if (child > 0) { /* wait for child */ if (passwd != NULL) { /* send the password to the child */ write(fds[1], passwd, strlen(passwd)+1); passwd = NULL; } else { write(fds[1], "", 1); /* blank password */ } close(fds[0]); /* we close this after the write because we want to avoid a possible SIGPIPE. */ close(fds[1]); (void) waitpid(child, &retval, 0); /* wait for helper to complete */ retval = (retval == PWDB_SUCCESS) ? PAM_SUCCESS:PAM_AUTH_ERR; } else { D(("fork failed")); retval = PAM_AUTH_ERR; } D(("returning %d", retval)); return retval; } static int _unix_verify_password(pam_handle_t *pamh, const char *name, const char *p, unsigned int ctrl) { const struct pwdb *pw=NULL; const struct pwdb_entry *pwe=NULL; const char *salt; char *pp; char *data_name; int retval; int verify_result; D(("called")); #ifdef HAVE_PAM_FAIL_DELAY if ( off(UNIX_NODELAY, ctrl) ) { D(("setting delay")); (void) pam_fail_delay(pamh, 1000000); /* 1 sec delay for on failure */ } #endif /* locate the entry for this user */ D(("locating user's record")); retval = pwdb_locate("user", PWDB_DEFAULT, name, PWDB_ID_UNKNOWN, &pw); if (retval == PWDB_PASS_PHRASE_REQD) { /* * give the password to the pwdb library. It may be needed to * access the database */ retval = pwdb_set_entry( pw, "pass_phrase", p, 1+strlen(p) , NULL, NULL, 0); if (retval != PWDB_SUCCESS) { _log_err(LOG_ALERT, "find pass; %s", pwdb_strerror(retval)); (void) pwdb_delete(&pw); p = NULL; return PAM_CRED_INSUFFICIENT; } retval = pwdb_locate("user", pw->source, name, PWDB_ID_UNKNOWN, &pw); } if (retval != PWDB_SUCCESS) { D(("user's record unavailable")); if ( on(UNIX_AUDIT, ctrl) ) { /* this might be a typo and the user has given a password instead of a username. Careful with this. */ _log_err(LOG_ALERT, "check pass; user (%s) unknown", name); } else { _log_err(LOG_ALERT, "check pass; user unknown"); } (void) pwdb_delete(&pw); p = NULL; return PAM_USER_UNKNOWN; } /* * courtesy of PWDB the password for the user is stored in * encrypted form in the "passwd" entry of pw. */ retval = pwdb_get_entry(pw, "passwd", &pwe); if (retval != PWDB_SUCCESS) { if (geteuid()) { /* we are not root perhaps this is the reason? Run helper */ D(("running helper binary")); retval = pwdb_run_helper_binary(pamh, p, name); } else { retval = PAM_AUTHINFO_UNAVAIL; _log_err(LOG_ALERT, "get passwd; %s", pwdb_strerror(retval)); } (void) pwdb_delete(&pw); p = NULL; return retval; } salt = (const char *) pwe->value; /* * XXX: Cristian, the above is not the case for RADIUS(?) Some * lines should be added for RADIUS to verify the password in * clear text... */ data_name = (char *) malloc(sizeof(FAIL_PREFIX)+strlen(name)); if ( data_name == NULL ) { _log_err(LOG_CRIT, "no memory for data-name"); } strcpy(data_name, FAIL_PREFIX); strcpy(data_name + sizeof(FAIL_PREFIX)-1, name); if ( !( (salt && *salt) || (p && *p) ) ) { D(("two null passwords to compare")); /* the stored password is NULL */ pp = NULL; if ( off(UNIX__NONULL, ctrl ) ) { /* this means we've succeeded */ verify_result = PAM_SUCCESS; } else { verify_result = PAM_AUTH_ERR; } } else if ( !( salt && p ) ) { D(("one of the two to compare are NULL")); pp = NULL; verify_result = PAM_AUTH_ERR; } else { /* there is no way that p can be NULL (one can be "") */ pp = _pam_md(p, salt); /* the moment of truth -- do we agree with the password? */ D(("comparing state of pp[%s] and salt[%s]", pp, salt)); if ( strcmp( pp, salt ) == 0 ) { verify_result = PAM_SUCCESS; } else { _pam_delete(pp); pp = _pam_md_compat(p, salt); if ( strcmp( pp, salt ) == 0 ) { verify_result = PAM_SUCCESS; } else { verify_result = PAM_AUTH_ERR; } } p = NULL; /* no longer needed here */ } if ( verify_result == PAM_SUCCESS ) { retval = PAM_SUCCESS; if (data_name) { /* reset failures */ pam_set_data(pamh, data_name, NULL, _cleanup_failures); } } else { retval = PAM_AUTH_ERR; if (data_name != NULL) { struct _pam_failed_auth *new=NULL; const struct _pam_failed_auth *old=NULL; /* get a failure recorder */ new = (struct _pam_failed_auth *) malloc(sizeof(struct _pam_failed_auth)); if (new != NULL) { new->user = x_strdup(name); new->id = getuid(); new->name = x_strdup(getlogin() ? getlogin():"" ); /* any previous failures for this user ? */ pam_get_data(pamh, data_name, (const void **)&old ); if (old != NULL) { new->count = old->count +1; if (new->count >= UNIX_MAX_RETRIES) { retval = PAM_MAXTRIES; } } else { const char *service=NULL; (void) pam_get_item(pamh, PAM_SERVICE , (const void **)&service); _log_err(LOG_NOTICE , "authentication failure; %s(uid=%d) -> " "%s for %s service" , new->name , new->id , new->user , service == NULL ? "**unknown**":service ); new->count = 1; } pam_set_data(pamh, data_name, new, _cleanup_failures); } else { _log_err(LOG_CRIT, "no memory for failure recorder"); } } } (void) pwdb_entry_delete(&pwe); (void) pwdb_delete(&pw); salt = NULL; _pam_delete(data_name); _pam_delete(pp); D(("done [%d].", retval)); return retval; } /* * this function obtains the name of the current user and ensures * that the PAM_USER item is set to this value */ static int _unix_get_user(pam_handle_t *pamh, unsigned int ctrl , const char *prompt, const char **user) { int retval; D(("called")); retval = pam_get_user(pamh, user, prompt); if (retval != PAM_SUCCESS) { D(("trouble reading username")); return retval; } /* * Various libraries at various times have had bugs related to * '+' or '-' as the first character of a user name. Don't take * any chances here. Require that the username starts with an * alphanumeric character. */ if (*user == NULL || !isalnum(**user)) { D(("bad username")); if (on(UNIX_DEBUG,ctrl)) { _log_err(LOG_ERR, "bad username [%s]", *user); } return PAM_USER_UNKNOWN; } if (retval == PAM_SUCCESS && on(UNIX_DEBUG,ctrl)) { _log_err(LOG_DEBUG, "username [%s] obtained", *user); } return retval; } /* * _unix_blankpasswd() is a quick check for a blank password * * returns TRUE if user does not have a password * - to avoid prompting for one in such cases (CG) */ static int _unix_blankpasswd(unsigned int ctrl, const char *name) { const struct pwdb *pw=NULL; const struct pwdb_entry *pwe=NULL; int retval; D(("called")); /* * This function does not have to be too smart if something goes * wrong, return FALSE and let this case to be treated somewhere * else (CG) */ if ( on(UNIX__NONULL, ctrl) ) return 0; /* will fail but don't let on yet */ /* find the user's database entry */ retval = pwdb_locate("user", PWDB_DEFAULT, name, PWDB_ID_UNKNOWN, &pw); if (retval != PWDB_SUCCESS || pw == NULL ) { retval = 0; } else { /* Does this user have a password? */ retval = pwdb_get_entry(pw, "passwd", &pwe); if ( retval != PWDB_SUCCESS || pwe == NULL ) retval = 0; else if ( pwe->value == NULL || ((char *)pwe->value)[0] == '\0' ) retval = 1; else retval = 0; } /* tidy up */ if ( pw ) { (void) pwdb_delete(&pw); if ( pwe ) (void) pwdb_entry_delete(&pwe); } return retval; } /* * obtain a password from the user */ static int _unix_read_password( pam_handle_t *pamh , unsigned int ctrl , const char *comment , const char *prompt1 , const char *prompt2 , const char *data_name , const char **pass ) { int authtok_flag; int retval; const char *item; char *token; D(("called")); /* * make sure nothing inappropriate gets returned */ *pass = token = NULL; /* * which authentication token are we getting? */ authtok_flag = on(UNIX__OLD_PASSWD,ctrl) ? PAM_OLDAUTHTOK:PAM_AUTHTOK ; /* * should we obtain the password from a PAM item ? */ if ( on(UNIX_TRY_FIRST_PASS,ctrl) || on(UNIX_USE_FIRST_PASS,ctrl) ) { retval = pam_get_item(pamh, authtok_flag, (const void **) &item); if (retval != PAM_SUCCESS ) { /* very strange. */ _log_err(LOG_ALERT , "pam_get_item returned error to unix-read-password" ); return retval; } else if (item != NULL) { /* we have a password! */ *pass = item; item = NULL; return PAM_SUCCESS; } else if (on(UNIX_USE_FIRST_PASS,ctrl)) { return PAM_AUTHTOK_RECOVER_ERR; /* didn't work */ } else if (on(UNIX_USE_AUTHTOK, ctrl) && off(UNIX__OLD_PASSWD, ctrl)) { return PAM_AUTHTOK_RECOVER_ERR; } } /* * getting here implies we will have to get the password from the * user directly. */ { struct pam_message msg[3],*pmsg[3]; struct pam_response *resp; int i, replies; /* prepare to converse */ if ( comment != NULL && off(UNIX__QUIET, ctrl) ) { pmsg[0] = &msg[0]; msg[0].msg_style = PAM_TEXT_INFO; msg[0].msg = comment; i = 1; } else { i = 0; } pmsg[i] = &msg[i]; msg[i].msg_style = PAM_PROMPT_ECHO_OFF; msg[i++].msg = prompt1; replies = 1; if ( prompt2 != NULL ) { pmsg[i] = &msg[i]; msg[i].msg_style = PAM_PROMPT_ECHO_OFF; msg[i++].msg = prompt2; ++replies; } /* so call the conversation expecting i responses */ resp = NULL; retval = converse(pamh, ctrl, i, pmsg, &resp); if (resp != NULL) { /* interpret the response */ if (retval == PAM_SUCCESS) { /* a good conversation */ token = x_strdup(resp[i-replies].resp); if (token != NULL) { if (replies == 2) { /* verify that password entered correctly */ if (!resp[i-1].resp || strcmp(token,resp[i-1].resp)) { token = _pam_delete(token); /* mistyped */ retval = PAM_AUTHTOK_RECOVER_ERR; make_remark(pamh, ctrl , PAM_ERROR_MSG, MISTYPED_PASS); } } } else { _log_err(LOG_NOTICE , "could not recover authentication token"); } } /* * tidy up the conversation (resp_retcode) is ignored * -- what is it for anyway? AGM */ _pam_drop_reply(resp, i); } else { retval = (retval == PAM_SUCCESS) ? PAM_AUTHTOK_RECOVER_ERR:retval ; } } if (retval != PAM_SUCCESS) { if ( on(UNIX_DEBUG,ctrl) ) _log_err(LOG_DEBUG,"unable to obtain a password"); return retval; } /* 'token' is the entered password */ if ( off(UNIX_NOT_SET_PASS, ctrl) ) { /* we store this password as an item */ retval = pam_set_item(pamh, authtok_flag, token); token = _pam_delete(token); /* clean it up */ if ( retval != PAM_SUCCESS || (retval = pam_get_item(pamh, authtok_flag , (const void **)&item)) != PAM_SUCCESS ) { _log_err(LOG_CRIT, "error manipulating password"); return retval; } } else { /* * then store it as data specific to this module. pam_end() * will arrange to clean it up. */ retval = pam_set_data(pamh, data_name, (void *) token, _cleanup); if (retval != PAM_SUCCESS) { _log_err(LOG_CRIT, "error manipulating password data [%s]" , pam_strerror(pamh, retval) ); token = _pam_delete(token); return retval; } item = token; token = NULL; /* break link to password */ } *pass = item; item = NULL; /* break link to password */ return PAM_SUCCESS; } static int _pam_unix_approve_pass(pam_handle_t *pamh , unsigned int ctrl , const char *pass_old , const char *pass_new) { D(("&new=%p, &old=%p",pass_old,pass_new)); D(("new=[%s]",pass_new)); D(("old=[%s]",pass_old)); if (pass_new == NULL || (pass_old && !strcmp(pass_old,pass_new))) { if ( on(UNIX_DEBUG, ctrl) ) { _log_err(LOG_DEBUG, "bad authentication token"); } make_remark(pamh, ctrl, PAM_ERROR_MSG, pass_new == NULL ? "No password supplied":"Password unchanged" ); return PAM_AUTHTOK_ERR; } /* * if one wanted to hardwire authentication token strength * checking this would be the place - AGM */ return PAM_SUCCESS; } /* ****************************************************************** * * Copyright (c) Andrew G. Morgan 1996-8. * Copyright (c) Alex O. Yuriev, 1996. * Copyright (c) Cristian Gafton 1996. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, and the entire permission notice in its entirety, * including the disclaimer of warranties. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote * products derived from this software without specific prior * written permission. * * ALTERNATIVELY, this product may be distributed under the terms of * the GNU Public License, in which case the provisions of the GPL are * required INSTEAD OF the above restrictions. (This clause is * necessary due to a potential bad interaction between the GPL and * the restrictions contained in a BSD-style copyright.) * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */