Commit message: adding KDC verification support (enabled by default)
CVE: CVE-2015-3206
CWE: CWE-287
Vulnerability description: The checkPassword function in python-kerberos does not authenticate the KDC it attempts to communicate with, which allows remote attackers to cause a denial of service (bad response), or have other unspecified impact by performing a man-in-the-middle attack.

Project: pykerberos

static PyObject *checkPassword(PyObject *self, PyObject *args)
{
    const char *user = NULL;
    const char *pswd = NULL;
    const char *service = NULL;
    const char *default_realm = NULL;
    const int verify = 1;
    int result = 0;

    if (!PyArg_ParseTuple(args, "ssssb", &user, &pswd, &service, &default_realm, &verify))
        return NULL;

    result = authenticate_user_krb5pwd(user, pswd, service, default_realm, verify);

    if (result)
        return Py_INCREF(Py_True), Py_True;
    else
        return NULL;
}

Project: pykerberos

static PyObject* authGSSClientInit(PyObject* self, PyObject* args, PyObject* keywds)
{
    const char *service = NULL;
    const char *principal = NULL;
    gss_client_state *state;
    PyObject *pystate;
    static char *kwlist[] = {"service", "principal", "gssflags", NULL};
    long int gss_flags = GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG;
    int result = 0;

    if (!PyArg_ParseTupleAndKeywords(args, keywds, "s|zl", kwlist, &service, &principal, &gss_flags))
        return NULL;

    state = (gss_client_state *) malloc(sizeof(gss_client_state));
#if PY_MAJOR_VERSION >= 3
    pystate = PyCapsule_New(state, NULL, NULL);
#else
    pystate = PyCObject_FromVoidPtr(state, NULL);
#endif

    result = authenticate_gss_client_init(service, principal, gss_flags, state);
    if (result == AUTH_GSS_ERROR)
        return NULL;

    return Py_BuildValue("(iO)", result, pystate);
}

Project: pykerberos

static PyObject *changePassword(PyObject *self, PyObject *args)
{
    const char *newpswd = NULL;
    const char *oldpswd = NULL;
    const char *user = NULL;
    int result = 0;

    if (!PyArg_ParseTuple(args, "sss", &user, &oldpswd, &newpswd))
        return NULL;

    result = change_user_krb5pwd(user, oldpswd, newpswd);

    if (result)
	return Py_INCREF(Py_True), Py_True;
    else
	return NULL;
}

Project: pykerberos

int authenticate_user_krb5pwd(const char *user, const char *pswd, const char *service, const char *default_realm, unsigned char verify)
{
    krb5_context    kcontext = NULL;
    krb5_error_code code;
    krb5_principal  client = NULL;
    krb5_principal  server = NULL;
    int             ret = 0;
    char            *name = NULL;
    char            *p = NULL;

    // create kerberos context
    code = krb5_init_context(&kcontext);
    if (code) {
        PyErr_SetObject(BasicAuthException_class, Py_BuildValue("((s:i))", "Cannot initialize Kerberos5 context", code));
        return 0;
    }

    // parse service name to get the server principal
    ret = krb5_parse_name(kcontext, service, &server);
    if (ret) {
        set_basicauth_error(kcontext, ret);
        ret = 0;
        goto end;
    }

    // unparse server pinrcipal again to get cannonical string representation
    code = krb5_unparse_name(kcontext, server, &name);
    if (code) {
        set_basicauth_error(kcontext, code);
        ret = 0;
        goto end;
    }

#ifdef PRINTFS
    printf("Using %s as server principal for password verification\n", name);
#endif

    // free cannonical server principal
    free(name);
    name = NULL;

    // construct user principal string from username and default realm
    name = (char *)malloc(256);
    p = strchr(user, '@');
    if (p == NULL) {
        snprintf(name, 256, "%s@%s", user, default_realm);
    } else {
        snprintf(name, 256, "%s", user);
    }

    // parse it into principal structure
    code = krb5_parse_name(kcontext, name, &client);
    if (code) {
        set_basicauth_error(kcontext, code);
        ret = 0;
        goto end;
    }

    code = verify_krb5_user(kcontext, client, pswd, server, verify);
    if (code) {
        ret = 0;
        goto end;
    }

    ret = 1;

end:
#ifdef PRINTFS
    printf("kerb_authenticate_user_krb5pwd ret=%d user=%s authtype=%s\n", ret, user, "Basic");
#endif

    if (name)
        free(name);
    if (client)
        krb5_free_principal(kcontext, client);
    if (server)
        krb5_free_principal(kcontext, server);
    krb5_free_context(kcontext);

    return ret;
}

Project: pykerberos

static PyObject *getServerPrincipalDetails(PyObject *self, PyObject *args)
{
    const char *service = NULL;
    const char *hostname = NULL;
    char* result;

    if (!PyArg_ParseTuple(args, "ss", &service, &hostname))
        return NULL;

    result = server_principal_details(service, hostname);

    if (result != NULL)
    {
        PyObject* pyresult = Py_BuildValue("s", result);
        free(result);
        return pyresult;
    }
    else
        return NULL;
}

Project: pykerberos

static krb5_error_code verify_krb5_user(krb5_context context, krb5_principal principal, const char *password, krb5_principal server, unsigned char verify)
{
    krb5_creds creds;
    krb5_get_init_creds_opt *gic_options;
    krb5_verify_init_creds_opt vic_options;
    krb5_error_code ret;
    char *name = NULL;

    memset(&creds, 0, sizeof(creds));

    // verify passed in client principal
    ret = krb5_unparse_name(context, principal, &name);
    if (ret == 0) {
#ifdef PRINTFS
        printf("Trying to get TGT for user %s\n", name);
#endif
        free(name);
    }

    // verify passed in server principal if needed
    if (verify) {
        ret = krb5_unparse_name(context, server, &name);
        if (ret == 0) {
#ifdef PRINTFS
            printf("Trying to get TGT for service %s\n", name);
#endif
            free(name);
        }
    }

    // verify password
    krb5_get_init_creds_opt_alloc(context, &gic_options);
    ret = krb5_get_init_creds_password(context, &creds, principal, (char *)password, NULL, NULL, 0, NULL, gic_options);
    if (ret) {
        set_basicauth_error(context, ret);
        goto end;
    }

    // verify response authenticity
    if (verify) {
        krb5_verify_init_creds_opt_init(&vic_options);
        krb5_verify_init_creds_opt_set_ap_req_nofail(&vic_options, 1);
        ret = krb5_verify_init_creds(context, &creds, server, NULL, NULL, &vic_options);
        if (ret) {
            set_basicauth_error(context, ret);
        }
    }

end:
    // clean up
    krb5_free_cred_contents(context, &creds);

    if (gic_options)
        krb5_get_init_creds_opt_free(context, gic_options);

    return ret;
}

Project: pykerberos

static void set_basicauth_error(krb5_context context, krb5_error_code code)
{
    PyErr_SetObject(BasicAuthException_class, Py_BuildValue("(s:i)", krb5_get_err_text(context, code), code));
}