/*
 * x509name.c
 *
 * Copyright (C) AB Strakt
 * Copyright (C) Jean-Paul Calderone
 * See LICENSE for details.
 *
 * X.509 Name handling, mostly thin wrapping.
 * See the file RATIONALE for a short explanation of why this module was written.
 *
 * Reviewed 2001-07-23
 */
#include <Python.h>
#define crypto_MODULE
#include "crypto.h"

static PyMethodDef crypto_X509Name_methods[4];

/*
 * Constructor for X509Name, never called by Python code directly
 *
 * Arguments: name    - A "real" X509_NAME object
 *            dealloc - Boolean value to specify whether the destructor should
 *                      free the "real" X509_NAME object
 * Returns:   The newly created X509Name object
 */
crypto_X509NameObj *
crypto_X509Name_New(X509_NAME *name, int dealloc)
{
    crypto_X509NameObj *self;

    self = PyObject_GC_New(crypto_X509NameObj, &crypto_X509Name_Type);

    if (self == NULL)
        return NULL;

    self->x509_name = name;
    self->dealloc = dealloc;
    self->parent_cert = NULL;

    PyObject_GC_Track(self);
    return self;
}


static char crypto_X509Name_doc[] = "\n\
X509Name(name) -> New X509Name object\n\
\n\
Create a new X509Name, copying the given X509Name instance.\n\
\n\
@param name: An X509Name object to copy\n\
@return: The X509Name object\n\
";

static PyObject *
crypto_X509Name_new(PyTypeObject *subtype, PyObject *args, PyObject *kwargs)
{
    crypto_X509NameObj *name;

    if (!PyArg_ParseTuple(args, "O!:X509Name", &crypto_X509Name_Type, &name)) {
        return NULL;
    }

    return (PyObject *)crypto_X509Name_New(X509_NAME_dup(name->x509_name), 1);
}


/*
 * Return a name string given a X509_NAME object and a name identifier. Used
 * by the getattr function.
 *
 * Arguments: name - The X509_NAME object
 *            nid  - The name identifier
 * Returns:   The name as a Python string object
 */
static int
get_name_by_nid(X509_NAME *name, int nid, char **utf8string)
{
    int entry_idx;
    X509_NAME_ENTRY *entry;
    ASN1_STRING *data;
    int len;

    if ((entry_idx = X509_NAME_get_index_by_NID(name, nid, -1)) == -1)
    {
        return 0;
    }
    entry = X509_NAME_get_entry(name, entry_idx);
    data = X509_NAME_ENTRY_get_data(entry);
    if ((len = ASN1_STRING_to_UTF8((unsigned char **)utf8string, data)) < 0)
    {
        exception_from_error_queue(crypto_Error);
        return -1;
    }

    return len;
}

/*
 * Given a X509_NAME object and a name identifier, set the corresponding
 * attribute to the given string. Used by the setattr function.
 *
 * Arguments: name  - The X509_NAME object
 *            nid   - The name identifier
 *            value - The string to set
 * Returns:   0 for success, -1 on failure
 */
static int
set_name_by_nid(X509_NAME *name, int nid, char *utf8string)
{
    X509_NAME_ENTRY *ne;
    int i, entry_count, temp_nid;

    /* If there's an old entry for this NID, remove it */
    entry_count = X509_NAME_entry_count(name);
    for (i = 0; i < entry_count; i++)
    {
        ne = X509_NAME_get_entry(name, i);
        temp_nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(ne));
        if (temp_nid == nid)
        {
            ne = X509_NAME_delete_entry(name, i);
            X509_NAME_ENTRY_free(ne);
            break;
        }
    }

    /* Add the new entry */
    if (!X509_NAME_add_entry_by_NID(name, nid, MBSTRING_UTF8, 
				    (unsigned char *)utf8string,
				    -1, -1, 0))
    {
        exception_from_error_queue(crypto_Error);
        return -1;
    }
    return 0;
}


/*
 * Find attribute. An X509Name object has the following attributes:
 * countryName (alias C), stateOrProvince (alias ST), locality (alias L),
 * organization (alias O), organizationalUnit (alias OU), commonName (alias
 * CN) and more...
 *
 * Arguments: self - The X509Name object
 *            name - The attribute name
 * Returns:   A Python object for the attribute, or NULL if something went
 *            wrong
 */
static PyObject *
crypto_X509Name_getattro(crypto_X509NameObj *self, PyObject *nameobj)
{
    int nid, len;
    char *utf8string;
    char *name;
#ifdef PY3
    name = PyBytes_AsString(PyUnicode_AsASCIIString(nameobj));
#else
    name = PyBytes_AsString(nameobj);
#endif

    if ((nid = OBJ_txt2nid(name)) == NID_undef) {
        /*
         * This is a bit weird.  OBJ_txt2nid indicated failure, but it seems
         * a lower level function, a2d_ASN1_OBJECT, also feels the need to
         * push something onto the error queue.  If we don't clean that up
         * now, someone else will bump into it later and be quite confused. 
         * See lp#314814.
         */
        flush_error_queue();
        return PyObject_GenericGetAttr((PyObject*)self, nameobj);
    }

    len = get_name_by_nid(self->x509_name, nid, &utf8string);
    if (len < 0)
        return NULL;
    else if (len == 0)
    {
        Py_INCREF(Py_None);
        return Py_None;
    }
    else {
	    PyObject* result = PyUnicode_Decode(utf8string, len, "utf-8", NULL);
	    OPENSSL_free(utf8string);
	    return result;
    }
}

/*
 * Set attribute
 *
 * Arguments: self  - The X509Name object
 *            name  - The attribute name
 *            value - The value to set
 */
static int
crypto_X509Name_setattro(crypto_X509NameObj *self, PyObject *nameobj, PyObject *value)
{
    int nid;
    int result;
    char *buffer;
    char *name;

    if (!PyBytes_CheckExact(nameobj) && !PyUnicode_CheckExact(nameobj)) {
        PyErr_Format(PyExc_TypeError,
                     "attribute name must be string, not '%.200s'",
                     Py_TYPE(nameobj)->tp_name);
        return -1;
    }

#ifdef PY3
    name = PyBytes_AsString(PyUnicode_AsASCIIString(nameobj));
#else
    name = PyBytes_AsString(nameobj);
#endif

    if ((nid = OBJ_txt2nid(name)) == NID_undef)
    {
        /* Just like the case in the getattr function */
        flush_error_queue();
        PyErr_SetString(PyExc_AttributeError, "No such attribute");
        return -1;
    }

    /* Something of a hack to get nice unicode behaviour */
    if (!PyArg_Parse(value, "es:setattr", "utf-8", &buffer))
        return -1;

    result = set_name_by_nid(self->x509_name, nid, buffer);
    PyMem_Free(buffer);
    return result;
}

/*
 * Compare two X509Name structures.
 *
 * Arguments: n - The first X509Name
 *            m - The second X509Name
 * Returns:   <0 if n < m, 0 if n == m and >0 if n > m
 */
static PyObject *
crypto_X509Name_richcompare(PyObject *n, PyObject *m, int op) {
    int result;

    if (!crypto_X509Name_Check(n) || !crypto_X509Name_Check(m)) {
        Py_INCREF(Py_NotImplemented);
        return Py_NotImplemented;
    }

    result = X509_NAME_cmp(
        ((crypto_X509NameObj*)n)->x509_name,
        ((crypto_X509NameObj*)m)->x509_name);

    switch (op) {
    case Py_EQ:
        result = (result == 0);
        break;

    case Py_NE:
        result = (result != 0);
        break;

    case Py_LT:
        result = (result < 0);
        break;

    case Py_LE:
        result = (result <= 0);
        break;

    case Py_GT:
        result = (result > 0);
        break;

    case Py_GE:
        result = (result >= 0);
        break;

    default:
        /* Should be impossible */
        Py_INCREF(Py_NotImplemented);
        return Py_NotImplemented;
    }

    if (result) {
        Py_INCREF(Py_True);
        return Py_True;
    } else {
        Py_INCREF(Py_False);
        return Py_False;
    }
}

/*
 * String representation of an X509Name
 *
 * Arguments: self - The X509Name object
 * Returns:   A string representation of the object
 */
static PyObject *
crypto_X509Name_repr(crypto_X509NameObj *self)
{
    char tmpbuf[512] = "";
    char realbuf[512+64];

    if (X509_NAME_oneline(self->x509_name, tmpbuf, 512) == NULL)
    {
        exception_from_error_queue(crypto_Error);
        return NULL;
    }
    else
    {
        /* This is safe because tmpbuf is max 512 characters */
        sprintf(realbuf, "<X509Name object '%s'>", tmpbuf);
        return PyText_FromString(realbuf);
    }
}

static char crypto_X509Name_hash_doc[] = "\n\
Return the hash value of this name\n\
\n\
@return: None\n\
";

/*
 * First four bytes of the MD5 digest of the DER form of an X509Name.
 *
 * Arguments: self - The X509Name object
 * Returns:   An integer giving the hash.
 */
static PyObject *
crypto_X509Name_hash(crypto_X509NameObj *self, PyObject* args)
{
    unsigned long hash;

    if (!PyArg_ParseTuple(args, ":hash")) {
        return NULL;
    }
    hash = X509_NAME_hash(self->x509_name);
    return PyLong_FromLong(hash);
}

static char crypto_X509Name_der_doc[] = "\n\
Return the DER encoding of this name\n\
\n\
@return: None\n\
";

/*
 * Arguments: self - The X509Name object
 * Returns:   The DER form of an X509Name.
 */
static PyObject *
crypto_X509Name_der(crypto_X509NameObj *self, PyObject *args)
{
    if (!PyArg_ParseTuple(args, ":der")) {
	return NULL;
    }

    i2d_X509_NAME(self->x509_name, 0);
    return PyBytes_FromStringAndSize(self->x509_name->bytes->data,
                                     self->x509_name->bytes->length);
}


static char crypto_X509Name_get_components_doc[] = "\n\
Returns the split-up components of this name.\n\
\n\
@return: List of tuples (name, value).\n\
";

static PyObject *
crypto_X509Name_get_components(crypto_X509NameObj *self, PyObject *args)
{
    int n, i;
    X509_NAME *name = self->x509_name;
    PyObject *list;

    if (!PyArg_ParseTuple(args, ":get_components"))
	return NULL;

    n = X509_NAME_entry_count(name);
    list = PyList_New(n);
    for (i = 0; i < n; i++)
    {
	X509_NAME_ENTRY *ent;
	ASN1_OBJECT *fname;
	ASN1_STRING *fval;
	int nid;
	int l;
	unsigned char *str;
	PyObject *tuple;

	ent = X509_NAME_get_entry(name, i);

	fname = X509_NAME_ENTRY_get_object(ent);
	fval = X509_NAME_ENTRY_get_data(ent);

	l = ASN1_STRING_length(fval);
	str = ASN1_STRING_data(fval);

	nid = OBJ_obj2nid(fname);

	/* printf("fname is %s len=%d str=%s\n", OBJ_nid2sn(nid), l, str); */

	tuple = PyTuple_New(2);
	PyTuple_SetItem(tuple, 0, PyBytes_FromString(OBJ_nid2sn(nid)));
	PyTuple_SetItem(tuple, 1, PyBytes_FromStringAndSize((char *)str, l));

	PyList_SetItem(list, i, tuple);
    }

    return list;
}


/*
 * Call the visitproc on all contained objects.
 *
 * Arguments: self - The Connection object
 *            visit - Function to call
 *            arg - Extra argument to visit
 * Returns:   0 if all goes well, otherwise the return code from the first
 *            call that gave non-zero result.
 */
static int
crypto_X509Name_traverse(crypto_X509NameObj *self, visitproc visit, void *arg)
{
    int ret = 0;

    if (ret == 0 && self->parent_cert != NULL)
        ret = visit(self->parent_cert, arg);
    return ret;
}

/*
 * Decref all contained objects and zero the pointers.
 *
 * Arguments: self - The Connection object
 * Returns:   Always 0.
 */
static int
crypto_X509Name_clear(crypto_X509NameObj *self)
{
    Py_XDECREF(self->parent_cert);
    self->parent_cert = NULL;
    return 0;
}

/*
 * Deallocate the memory used by the X509Name object
 *
 * Arguments: self - The X509Name object
 * Returns:   None
 */
static void
crypto_X509Name_dealloc(crypto_X509NameObj *self)
{
    PyObject_GC_UnTrack(self);
    /* Sometimes we don't have to dealloc this */
    if (self->dealloc)
        X509_NAME_free(self->x509_name);

    crypto_X509Name_clear(self);

    PyObject_GC_Del(self);
}

/*
 * ADD_METHOD(name) expands to a correct PyMethodDef declaration
 *   {  'name', (PyCFunction)crypto_X509_name, METH_VARARGS }
 * for convenience
 */
#define ADD_METHOD(name)        \
    { #name, (PyCFunction)crypto_X509Name_##name, METH_VARARGS, crypto_X509Name_##name##_doc }
static PyMethodDef crypto_X509Name_methods[] =
{
    ADD_METHOD(hash),
    ADD_METHOD(der),
    ADD_METHOD(get_components),
    { NULL, NULL }
};
#undef ADD_METHOD

PyTypeObject crypto_X509Name_Type = {
    PyOpenSSL_HEAD_INIT(&PyType_Type, 0)
    "X509Name",
    sizeof(crypto_X509NameObj),
    0,
    (destructor)crypto_X509Name_dealloc,
    NULL, /* print */
    NULL, /* getattr */
    NULL, /* setattr */
    NULL, /* reserved */
    (reprfunc)crypto_X509Name_repr,
    NULL, /* as_number */
    NULL, /* as_sequence */
    NULL, /* as_mapping */
    NULL, /* hash */
    NULL, /* call */
    NULL, /* str */
    (getattrofunc)crypto_X509Name_getattro, /* getattro */
    (setattrofunc)crypto_X509Name_setattro, /* setattro */
    NULL, /* as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */
    crypto_X509Name_doc, /* tp_doc */
    (traverseproc)crypto_X509Name_traverse, /* tp_traverse */
    (inquiry)crypto_X509Name_clear, /* tp_clear */
    crypto_X509Name_richcompare, /* tp_richcompare */
    0, /* tp_weaklistoffset */
    NULL, /* tp_iter */
    NULL, /* tp_iternext */
    crypto_X509Name_methods, /* tp_methods */
    NULL, /* tp_members */
    NULL, /* tp_getset */
    NULL, /* tp_base */
    NULL, /* tp_dict */
    NULL, /* tp_descr_get */
    NULL, /* tp_descr_set */
    0, /* tp_dictoffset */
    NULL, /* tp_init */
    NULL, /* tp_alloc */
    crypto_X509Name_new, /* tp_new */
};

/*
 * Initialize the X509Name part of the crypto module
 *
 * Arguments: module - The crypto module
 * Returns:   None
 */
int
init_crypto_x509name(PyObject *module)
{
    if (PyType_Ready(&crypto_X509Name_Type) < 0) {
        return 0;
    }

    /* PyModule_AddObject steals a reference.
     */
    Py_INCREF((PyObject *)&crypto_X509Name_Type);
    if (PyModule_AddObject(module, "X509Name", (PyObject *)&crypto_X509Name_Type) != 0) {
        return 0;
    }

    /* PyModule_AddObject steals a reference.
     */
    Py_INCREF((PyObject *)&crypto_X509Name_Type);
    if (PyModule_AddObject(module, "X509NameType", (PyObject *)&crypto_X509Name_Type) != 0) {
        return 0;
    }

    return 1;
}
