2022-08-22 16:15:05 +08:00
|
|
|
/*
|
|
|
|
* libpwquality Python bindings
|
|
|
|
*
|
|
|
|
* Copyright (c) Red Hat, Inc, 2011,2014
|
|
|
|
* Copyright (c) Tomas Mraz <tm@t8m.info>, 2011,2014
|
|
|
|
*
|
|
|
|
* See the end of the file for the License Information
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <Python.h>
|
|
|
|
#include "pwquality.h"
|
|
|
|
|
2022-08-22 16:15:06 +08:00
|
|
|
#include <locale.h>
|
|
|
|
#include <libintl.h>
|
|
|
|
|
2022-08-22 16:15:05 +08:00
|
|
|
#if PY_MAJOR_VERSION >= 3
|
|
|
|
#define IS_PY3K
|
|
|
|
#define PWQLong_FromLong PyLong_FromLong
|
|
|
|
#define PWQLong_AsLong PyLong_AsLong
|
|
|
|
#else
|
|
|
|
#define PWQLong_FromLong PyInt_FromLong
|
|
|
|
#define PWQLong_AsLong PyInt_AsLong
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static PyObject *PWQError;
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
PyObject_HEAD
|
|
|
|
pwquality_settings_t *pwq;
|
|
|
|
} PWQSettings;
|
|
|
|
|
|
|
|
static PyObject *
|
|
|
|
pwqsettings_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
|
|
|
|
static void
|
|
|
|
pwqsettings_dealloc(PWQSettings *self);
|
|
|
|
static PyObject *
|
|
|
|
pwqsettings_getint(PWQSettings *self, void *setting);
|
|
|
|
static int
|
|
|
|
pwqsettings_setint(PWQSettings *self, PyObject *value, void *setting);
|
|
|
|
static PyObject *
|
|
|
|
pwqsettings_getstr(PWQSettings *self, void *setting);
|
|
|
|
static int
|
|
|
|
pwqsettings_setstr(PWQSettings *self, PyObject *value, void *setting);
|
|
|
|
static PyObject *
|
|
|
|
read_config(PWQSettings *self, PyObject *args);
|
|
|
|
static PyObject *
|
|
|
|
set_option(PWQSettings *self, PyObject *args);
|
|
|
|
static PyObject *
|
|
|
|
generate(PWQSettings *self, PyObject *args);
|
|
|
|
static PyObject *
|
|
|
|
check(PWQSettings *self, PyObject *args);
|
|
|
|
|
|
|
|
static PyMethodDef pwqsettings_methods[] = {
|
|
|
|
{ "read_config", (PyCFunction)read_config, METH_VARARGS,
|
|
|
|
"Read the settings from configuration file\n\nParameters:\n"
|
|
|
|
" cfgfilename - path to the configuration file (optional)"
|
|
|
|
},
|
|
|
|
{ "set_option", (PyCFunction)set_option, METH_VARARGS,
|
|
|
|
"Set option from name=value pair\n\nParameters:\n"
|
|
|
|
" option - string with the name=value pair"
|
|
|
|
},
|
|
|
|
{ "generate", (PyCFunction)generate, METH_VARARGS,
|
|
|
|
"Generate password with requested entropy\n\nParameters:\n"
|
|
|
|
" entropy - integer entropy bits used to generate the password"
|
|
|
|
},
|
|
|
|
{ "check", (PyCFunction)check, METH_VARARGS,
|
|
|
|
"Check whether the password conforms to the requirements and return password strength score"
|
|
|
|
"\n\nParameters:\n"
|
|
|
|
" password - password string to be checked\n"
|
|
|
|
" oldpassword - old password string (or None) for additional checks (optional)\n"
|
|
|
|
" username - user name (or None) for additional checks (optional)"
|
|
|
|
},
|
|
|
|
{ NULL } /* Sentinel */
|
|
|
|
};
|
|
|
|
|
|
|
|
static PyGetSetDef pwqsettings_getseters[] = {
|
|
|
|
{ "difok",
|
|
|
|
(getter)pwqsettings_getint, (setter)pwqsettings_setint,
|
|
|
|
"Minimum difference from the old password",
|
|
|
|
(void *)PWQ_SETTING_DIFF_OK
|
|
|
|
},
|
|
|
|
{ "minlen",
|
|
|
|
(getter)pwqsettings_getint, (setter)pwqsettings_setint,
|
|
|
|
"Minimum length of the new password",
|
|
|
|
(void *)PWQ_SETTING_MIN_LENGTH
|
|
|
|
},
|
|
|
|
{ "dcredit",
|
|
|
|
(getter)pwqsettings_getint, (setter)pwqsettings_setint,
|
|
|
|
"Credit for or minimum of digits",
|
|
|
|
(void *)PWQ_SETTING_DIG_CREDIT
|
|
|
|
},
|
|
|
|
{ "ucredit",
|
|
|
|
(getter)pwqsettings_getint, (setter)pwqsettings_setint,
|
|
|
|
"Credit for or minimum of uppercase characters",
|
|
|
|
(void *)PWQ_SETTING_UP_CREDIT
|
|
|
|
},
|
|
|
|
{ "lcredit",
|
|
|
|
(getter)pwqsettings_getint, (setter)pwqsettings_setint,
|
|
|
|
"Credit for or minimum of lowercase characters",
|
|
|
|
(void *)PWQ_SETTING_LOW_CREDIT
|
|
|
|
},
|
|
|
|
{ "ocredit",
|
|
|
|
(getter)pwqsettings_getint, (setter)pwqsettings_setint,
|
|
|
|
"Credit for or minimum of other characters",
|
|
|
|
(void *)PWQ_SETTING_OTH_CREDIT
|
|
|
|
},
|
|
|
|
{ "minclass",
|
|
|
|
(getter)pwqsettings_getint, (setter)pwqsettings_setint,
|
|
|
|
"Minimum number of character classes",
|
|
|
|
(void *)PWQ_SETTING_MIN_CLASS
|
|
|
|
},
|
|
|
|
{ "maxrepeat",
|
|
|
|
(getter)pwqsettings_getint, (setter)pwqsettings_setint,
|
|
|
|
"Maximum repeated consecutive characters",
|
|
|
|
(void *)PWQ_SETTING_MAX_REPEAT
|
|
|
|
},
|
|
|
|
{ "maxclassrepeat",
|
|
|
|
(getter)pwqsettings_getint, (setter)pwqsettings_setint,
|
|
|
|
"Maximum consecutive characters of the same class",
|
|
|
|
(void *)PWQ_SETTING_MAX_CLASS_REPEAT
|
|
|
|
},
|
|
|
|
{ "maxsequence",
|
|
|
|
(getter)pwqsettings_getint, (setter)pwqsettings_setint,
|
|
|
|
"Maximum length of a monotonic character sequence",
|
|
|
|
(void *)PWQ_SETTING_MAX_SEQUENCE
|
|
|
|
},
|
|
|
|
{ "gecoscheck",
|
|
|
|
(getter)pwqsettings_getint, (setter)pwqsettings_setint,
|
|
|
|
"Match words from the passwd GECOS field if available",
|
|
|
|
(void *)PWQ_SETTING_GECOS_CHECK
|
|
|
|
},
|
|
|
|
{ "dictcheck",
|
|
|
|
(getter)pwqsettings_getint, (setter)pwqsettings_setint,
|
|
|
|
"Perform the dictionary check",
|
|
|
|
(void *)PWQ_SETTING_DICT_CHECK
|
|
|
|
},
|
|
|
|
{ "badwords",
|
|
|
|
(getter)pwqsettings_getstr, (setter)pwqsettings_setstr,
|
|
|
|
"List of words more than 3 characters long that are forbidden",
|
|
|
|
(void *)PWQ_SETTING_BAD_WORDS
|
|
|
|
},
|
|
|
|
{ "dictpath",
|
|
|
|
(getter)pwqsettings_getstr, (setter)pwqsettings_setstr,
|
|
|
|
"Path to the cracklib dictionary",
|
|
|
|
(void *)PWQ_SETTING_DICT_PATH
|
|
|
|
},
|
|
|
|
{ NULL } /* Sentinel */
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static PyTypeObject pwqsettings_type = {
|
|
|
|
PyVarObject_HEAD_INIT(NULL, 0)
|
|
|
|
"pwquality.PWQSettings", /* tp_name */
|
|
|
|
sizeof(PWQSettings), /* tp_basicsize */
|
|
|
|
0, /* tp_itemsize */
|
|
|
|
(destructor)pwqsettings_dealloc, /* tp_dealloc */
|
|
|
|
0, /* tp_print */
|
|
|
|
0, /* tp_getattr */
|
|
|
|
0, /* tp_setattr */
|
|
|
|
0, /* tp_compare */
|
|
|
|
0, /* tp_repr */
|
|
|
|
0, /* tp_as_number */
|
|
|
|
0, /* tp_as_sequence */
|
|
|
|
0, /* tp_as_mapping */
|
|
|
|
0, /* tp_hash */
|
|
|
|
0, /* tp_call */
|
|
|
|
0, /* tp_str */
|
|
|
|
0, /* tp_getattro */
|
|
|
|
0, /* tp_setattro */
|
|
|
|
0, /* tp_as_buffer */
|
|
|
|
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
|
|
|
|
"PWQSettings objects - libpwquality functionality wrapper", /* tp_doc */
|
|
|
|
0, /* tp_traverse */
|
|
|
|
0, /* tp_clear */
|
|
|
|
0, /* tp_richcompare */
|
|
|
|
0, /* tp_weaklistoffset */
|
|
|
|
0, /* tp_iter */
|
|
|
|
0, /* tp_iternext */
|
|
|
|
pwqsettings_methods, /* tp_methods */
|
|
|
|
0, /* tp_members */
|
|
|
|
pwqsettings_getseters, /* tp_getset */
|
|
|
|
0, /* tp_base */
|
|
|
|
0, /* tp_dict */
|
|
|
|
0, /* tp_descr_get */
|
|
|
|
0, /* tp_descr_set */
|
|
|
|
0, /* tp_dictoffset */
|
|
|
|
0, /* tp_init */
|
|
|
|
0, /* tp_alloc */
|
|
|
|
pwqsettings_new, /* tp_new */
|
|
|
|
};
|
|
|
|
|
|
|
|
static PyMethodDef pwquality_methods[] = {
|
|
|
|
{ NULL } /* Sentinel */
|
|
|
|
};
|
|
|
|
|
|
|
|
static PyObject *
|
|
|
|
pwqerror(int rc, void *auxerror)
|
|
|
|
{
|
|
|
|
char buf[PWQ_MAX_ERROR_MESSAGE_LEN];
|
|
|
|
PyObject *py_errvalue;
|
|
|
|
const char *msg;
|
|
|
|
|
2022-08-22 16:15:06 +08:00
|
|
|
setlocale(LC_ALL, "");
|
|
|
|
bindtextdomain("libpwquality", "/usr/share/locale");
|
|
|
|
textdomain("libpwquality");
|
|
|
|
|
2022-08-22 16:15:05 +08:00
|
|
|
msg = pwquality_strerror(buf, sizeof(buf), rc, auxerror);
|
|
|
|
|
|
|
|
if (rc == PWQ_ERROR_MEM_ALLOC)
|
|
|
|
return PyErr_NoMemory();
|
|
|
|
|
|
|
|
py_errvalue = Py_BuildValue("is", rc, msg);
|
|
|
|
if (py_errvalue == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (rc == PWQ_ERROR_UNKNOWN_SETTING || rc == PWQ_ERROR_NON_INT_SETTING
|
|
|
|
|| rc == PWQ_ERROR_NON_STR_SETTING) {
|
|
|
|
PyErr_SetObject(PyExc_AttributeError, py_errvalue);
|
|
|
|
} else {
|
|
|
|
PyErr_SetObject(PWQError, py_errvalue);
|
|
|
|
}
|
|
|
|
Py_DECREF(py_errvalue);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static PyObject *
|
|
|
|
pwqsettings_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
|
|
|
{
|
|
|
|
PWQSettings *self;
|
|
|
|
|
|
|
|
self = (PWQSettings *)type->tp_alloc(type, 0);
|
|
|
|
if (self) {
|
|
|
|
self->pwq = pwquality_default_settings();
|
|
|
|
if (self->pwq == NULL) {
|
|
|
|
Py_DECREF(self);
|
|
|
|
return PyErr_NoMemory();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return (PyObject *)self;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
pwqsettings_dealloc(PWQSettings *self)
|
|
|
|
{
|
|
|
|
pwquality_free_settings(self->pwq);
|
|
|
|
Py_TYPE(self)->tp_free((PyObject *)self);
|
|
|
|
}
|
|
|
|
|
|
|
|
static PyObject *
|
|
|
|
pwqsettings_getint(PWQSettings *self, void *setting)
|
|
|
|
{
|
|
|
|
int value;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if ((rc = pwquality_get_int_value(self->pwq, (int)(ssize_t)setting, &value)) < 0) {
|
|
|
|
return pwqerror(rc, NULL);
|
|
|
|
}
|
|
|
|
return PWQLong_FromLong((long)value);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
pwqsettings_setint(PWQSettings *self, PyObject *value, void *setting)
|
|
|
|
{
|
|
|
|
long l;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
l = PWQLong_AsLong(value);
|
|
|
|
if (PyErr_Occurred() == NULL) {
|
|
|
|
if ((rc = pwquality_set_int_value(self->pwq,
|
|
|
|
(int)(ssize_t)setting, (int)l)) < 0) {
|
|
|
|
pwqerror(rc, NULL);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static PyObject *
|
|
|
|
pwqsettings_getstr(PWQSettings *self, void *setting)
|
|
|
|
{
|
|
|
|
const char *value;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if ((rc = pwquality_get_str_value(self->pwq, (int)(ssize_t)setting, &value)) < 0) {
|
|
|
|
return pwqerror(rc, NULL);
|
|
|
|
}
|
|
|
|
if (value == NULL) {
|
|
|
|
Py_INCREF(Py_None);
|
|
|
|
return Py_None;
|
|
|
|
}
|
|
|
|
#ifdef IS_PY3K
|
|
|
|
return PyUnicode_FromString(value);
|
|
|
|
#else
|
|
|
|
return PyString_FromString(value);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
pwqsettings_setstr(PWQSettings *self, PyObject *value, void *setting)
|
|
|
|
{
|
|
|
|
const char *s = NULL;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (value != (PyObject *)Py_None) {
|
|
|
|
#ifdef IS_PY3K
|
|
|
|
if (PyUnicode_Check(value)) {
|
|
|
|
PyObject *value_as_bytes = PyUnicode_AsUTF8String(value);
|
|
|
|
if (!value_as_bytes)
|
|
|
|
return -1;
|
|
|
|
s = PyBytes_AsString(value_as_bytes);
|
|
|
|
Py_DECREF(value_as_bytes);
|
|
|
|
if (!s)
|
|
|
|
return -1;
|
|
|
|
} else {
|
|
|
|
PyErr_SetString(PyExc_TypeError, "expected unicode string");
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
s = PyString_AsString(value);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
if (PyErr_Occurred() == NULL) {
|
|
|
|
if ((rc = pwquality_set_str_value(self->pwq,
|
|
|
|
(int)(ssize_t)setting, s)) < 0) {
|
|
|
|
pwqerror(rc, NULL);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static PyObject *
|
|
|
|
read_config(PWQSettings *self, PyObject *args)
|
|
|
|
{
|
|
|
|
char *cfgfile = NULL;
|
|
|
|
void *auxerror;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (!PyArg_ParseTuple(args, "|s", &cfgfile))
|
|
|
|
return NULL;
|
|
|
|
if ((rc = pwquality_read_config(self->pwq, cfgfile, &auxerror)) < 0) {
|
|
|
|
return pwqerror(rc, auxerror);
|
|
|
|
}
|
|
|
|
Py_INCREF(Py_None);
|
|
|
|
return Py_None;
|
|
|
|
}
|
|
|
|
|
|
|
|
static PyObject *
|
|
|
|
set_option(PWQSettings *self, PyObject *args)
|
|
|
|
{
|
|
|
|
char *option;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (!PyArg_ParseTuple(args, "s", &option))
|
|
|
|
return NULL;
|
|
|
|
if ((rc = pwquality_set_option(self->pwq, option)) < 0) {
|
|
|
|
return pwqerror(rc, NULL);
|
|
|
|
}
|
|
|
|
Py_INCREF(Py_None);
|
|
|
|
return Py_None;
|
|
|
|
}
|
|
|
|
|
|
|
|
static PyObject *
|
|
|
|
generate(PWQSettings *self, PyObject *args)
|
|
|
|
{
|
|
|
|
int entropy_bits;
|
|
|
|
char *password;
|
|
|
|
PyObject *passobj;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (!PyArg_ParseTuple(args, "i", &entropy_bits))
|
|
|
|
return NULL;
|
|
|
|
if ((rc = pwquality_generate(self->pwq, entropy_bits, &password)) < 0) {
|
|
|
|
return pwqerror(rc, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef IS_PY3K
|
|
|
|
passobj = PyUnicode_FromString(password);
|
|
|
|
#else
|
|
|
|
passobj = PyString_FromString(password);
|
|
|
|
#endif
|
|
|
|
free(password);
|
|
|
|
return passobj;
|
|
|
|
}
|
|
|
|
|
|
|
|
static PyObject *
|
|
|
|
check(PWQSettings *self, PyObject *args)
|
|
|
|
{
|
|
|
|
char *password;
|
|
|
|
char *oldpassword = NULL;
|
|
|
|
char *username = NULL;
|
|
|
|
void *auxerror;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (!PyArg_ParseTuple(args, "s|zz", &password, &oldpassword, &username))
|
|
|
|
return NULL;
|
|
|
|
if ((rc = pwquality_check(self->pwq, password, oldpassword,
|
|
|
|
username, &auxerror)) < 0) {
|
|
|
|
return pwqerror(rc, auxerror);
|
|
|
|
}
|
|
|
|
|
|
|
|
return PWQLong_FromLong((long)rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef IS_PY3K
|
|
|
|
static struct PyModuleDef pwqualitydef = {
|
|
|
|
PyModuleDef_HEAD_INIT,
|
|
|
|
"pwquality",
|
|
|
|
"Libpwquality wrapper module",
|
|
|
|
-1,
|
|
|
|
pwquality_methods,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
#define INITERROR return NULL
|
|
|
|
|
|
|
|
PyObject *
|
|
|
|
PyInit_pwquality(void)
|
|
|
|
#else
|
|
|
|
#define INITERROR return
|
|
|
|
|
|
|
|
PyMODINIT_FUNC
|
|
|
|
initpwquality(void)
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
PyObject *module;
|
|
|
|
|
|
|
|
if (PyType_Ready(&pwqsettings_type) < 0)
|
|
|
|
INITERROR;
|
|
|
|
|
|
|
|
#ifdef IS_PY3K
|
|
|
|
module = PyModule_Create(&pwqualitydef);
|
|
|
|
#else
|
|
|
|
module = Py_InitModule3("pwquality", pwquality_methods,
|
|
|
|
"Libpwquality wrapper module");
|
|
|
|
#endif
|
|
|
|
if (module == NULL)
|
|
|
|
INITERROR;
|
|
|
|
|
|
|
|
PWQError = PyErr_NewExceptionWithDoc("pwquality.PWQError",
|
|
|
|
"Standard exception thrown from PWQSettings method calls\n\n"
|
|
|
|
"The exception value is always integer error code and string description",
|
|
|
|
NULL, NULL);
|
|
|
|
if (PWQError == NULL) {
|
|
|
|
Py_DECREF(module);
|
|
|
|
INITERROR;
|
|
|
|
}
|
|
|
|
Py_INCREF(PWQError);
|
|
|
|
PyModule_AddObject(module, "PWQError", PWQError);
|
|
|
|
|
|
|
|
Py_INCREF(&pwqsettings_type);
|
|
|
|
PyModule_AddObject(module, "PWQSettings", (PyObject *)&pwqsettings_type);
|
|
|
|
|
|
|
|
#include "constants.c"
|
|
|
|
#ifdef IS_PY3K
|
|
|
|
return module;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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 General Public License version 2 or later, in which case the
|
|
|
|
* provisions of the GPL are required INSTEAD OF the above restrictions.
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|