/*
 * 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"

#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
        },
        { "usercheck",
                (getter)pwqsettings_getint, (setter)pwqsettings_setint,
                "Perform the user name check",
                (void *)PWQ_SETTING_USER_CHECK
        },
        { "enforcing",
                (getter)pwqsettings_getint, (setter)pwqsettings_setint,
                "Whether the check is supposed to be just warning or enforced (informational setting)",
                (void *)PWQ_SETTING_ENFORCING
        },
        { "retry",
                (getter)pwqsettings_getint, (setter)pwqsettings_setint,
                "How many retries for the password change should be allowed (informational setting)",
                (void *)PWQ_SETTING_RETRY_TIMES
        },
        { "enforce_for_root",
                (getter)pwqsettings_getint, (setter)pwqsettings_setint,
                "Whether the check is supposed to be just warning or enforced (informational setting)",
                (void *)PWQ_SETTING_ENFORCE_ROOT
        },
        { "local_users_only",
                (getter)pwqsettings_getint, (setter)pwqsettings_setint,
                "Whether the check is supposed to be applied just to local users (informational setting)",
                (void *)PWQ_SETTING_LOCAL_USERS
        },
        { "usersubstr",
                (getter)pwqsettings_getint, (setter)pwqsettings_setint,
                "Check for substrings of the username of a given length",
                (void *)PWQ_SETTING_USER_SUBSTR
        },
        { "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;

        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.
 */
