/* * Copyright (C) 2006 Red Hat, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * Authors: * Mark McLoughlin */ /* * Module allows us to use "psearch" LDAP notifications, the notification * system which Fedora Directory Server implements, with a simple API: * * w = LDAPWatch ("ldap://mydirectory/", "dc=example,dc=com") * * for change in w.get_changes (): * (dn, type, prev_dn) = change * * Notes: * - you can pass a bind_dn and bind_passwd as the 3rd and 4th arguments * to the constructor * - get_changes() takes an optional timeout in seconds; if unspecified * the function will block until it gets a result, if zero it will * immediately return an empty list if no results are available * - type is one of TYPE_ADD, TYPE_DELETE, TYPE_MODIFY, TYPE_MODDN * - prev_dn is only valid if type == TYPE_MODDN * * See: * http://www.watersprings.org/pub/id/draft-smith-psearch-ldap-01.txt * http://www.watersprings.org/pub/id/draft-ietf-ldapext-c-api-psearch-00.txt * http://www.watersprings.org/pub/id/draft-ietf-ldapext-ldap-c-api-05.txt */ #include #include #include #include #include #include #define _LDAP_CONTROL_PERSISTENTSEARCH "2.16.840.1.113730.3.4.3" #define _LDAP_CONTROL_ENTRYCHANGE "2.16.840.1.113730.3.4.7" #define _LDAP_CHANGETYPE_ADD 1 #define _LDAP_CHANGETYPE_DELETE 2 #define _LDAP_CHANGETYPE_MODIFY 4 #define _LDAP_CHANGETYPE_MODDN 8 #define _LDAP_CHANGETYPE_ANY (1|2|4|8) typedef struct { PyObject_HEAD char *uri; char *base; char *bind_dn; char *bind_passwd; LDAP *cnx; int msgid; } PyLDAPWatch; static PyObject *module = NULL; static PyObject *LDAP_exception = NULL; static inline PyObject * lookup_item_type (int type) { const char *item_type_str; PyObject *retval; switch (type) { case _LDAP_CHANGETYPE_ADD: item_type_str = "TYPE_ADD"; break; case _LDAP_CHANGETYPE_DELETE: item_type_str = "TYPE_DELETE"; break; case _LDAP_CHANGETYPE_MODIFY: item_type_str = "TYPE_MODIFY"; break; case _LDAP_CHANGETYPE_MODDN: item_type_str = "TYPE_MODIFY"; break; default: item_type_str = "TYPE_INVALID"; break; } retval = PyDict_GetItemString (PyModule_GetDict (module), item_type_str); Py_INCREF (retval); return retval; } static int parse_entrychange_controls (LDAP *cnx, LDAPControl **controls, int *change_type_ret, int *change_num_ret, char **previous_dn_ret) { int i; ber_int_t change_type; ber_int_t change_num; char *previous_dn; if (change_type_ret != NULL) *change_type_ret = 0; if (change_num_ret != NULL) change_num_ret = 0; if (previous_dn_ret != NULL) *previous_dn_ret = NULL; change_type = 0; change_num = 0; previous_dn = NULL; for (i = 0; controls[i] != NULL; i++) { LDAPControl *control = controls[i]; BerElement *element; ber_len_t len; if (strcmp (control->ldctl_oid, _LDAP_CONTROL_ENTRYCHANGE) != 0) continue; element = ber_init (&control->ldctl_value); if (element == NULL) return LDAP_NO_MEMORY; if (ber_scanf (element, "{e", &change_type) == LBER_ERROR) { ber_free (element, 1); return LDAP_DECODING_ERROR; } previous_dn = NULL; switch (change_type) { case _LDAP_CHANGETYPE_ADD: break; case _LDAP_CHANGETYPE_DELETE: break; case _LDAP_CHANGETYPE_MODIFY: break; case _LDAP_CHANGETYPE_MODDN: if (ber_scanf (element, "a", &previous_dn) == LBER_ERROR) { ber_free (element, 1); return LDAP_DECODING_ERROR; } break; default: break; } if (ber_peek_tag (element, &len) == LBER_INTEGER) { if (ber_scanf (element, "i", &change_num) == LBER_ERROR) { ber_free (element, 1); return LDAP_DECODING_ERROR; } } } if (change_type_ret != NULL) *change_type_ret = change_type; if (change_num_ret != NULL) *change_num_ret = change_num; if (previous_dn_ret != NULL) *previous_dn_ret = previous_dn; return LDAP_SUCCESS; } static int get_received_results (LDAP *cnx, int msgid, int timeout, LDAPMessage **entries_ret) { LDAPMessage *entries; struct timeval timeout_tv; int ret; if (entries_ret != NULL) *entries_ret = NULL; timeout_tv.tv_sec = timeout; timeout_tv.tv_usec = 0; entries = NULL; ret = ldap_result (cnx, msgid, LDAP_MSG_ONE, timeout == -1 ? NULL : &timeout_tv, &entries); if (ret < 0) { int err; err = LDAP_SUCCESS; ldap_get_option (cnx, LDAP_OPT_RESULT_CODE, &err); return err; } if (ret == 0) /* timed out */ { ldap_msgfree (entries); return LDAP_SUCCESS; } if (entries_ret != NULL) *entries_ret = entries; else ldap_msgfree (entries); return LDAP_SUCCESS; } static int parse_result (LDAP *cnx, LDAPMessage *entry, char **dn_ret, int *type_ret, char **prev_dn_ret) { LDAPControl **ec_controls; int ret; int ch_type; char *prev_dn; if (dn_ret != NULL) *dn_ret = NULL; if (type_ret != NULL) *type_ret = 0; if (prev_dn_ret != NULL) *prev_dn_ret = 0; ec_controls = NULL; if ((ret = ldap_get_entry_controls (cnx, entry, &ec_controls)) != LDAP_SUCCESS) return ret; ch_type = 0; prev_dn = NULL; if ((ret = parse_entrychange_controls (cnx, ec_controls, &ch_type, NULL, &prev_dn)) != LDAP_SUCCESS) { ldap_controls_free (ec_controls); return ret; } if (dn_ret != NULL) *dn_ret = strdup (ldap_get_dn (cnx, entry)); if (type_ret != NULL) *type_ret = ch_type; if (prev_dn_ret != NULL && prev_dn != NULL) *prev_dn_ret = strdup (prev_dn); ldap_controls_free (ec_controls); return LDAP_SUCCESS; } static PyObject * pyldap_watch_get_changes (PyObject *self, PyObject *args) { PyLDAPWatch *watch; PyObject *retval; LDAPMessage *entries; LDAPMessage *entry; int timeout; int ret; timeout = -1; if (args != NULL) { if (!PyArg_ParseTuple (args, "|i:ldapwatch.LDAPWatch.get_changes_directory", &timeout)) return NULL; } watch = (PyLDAPWatch *) self; entries = NULL; ret = get_received_results (watch->cnx, watch->msgid, timeout, &entries); if (ret != LDAP_SUCCESS) { PyErr_SetString (LDAP_exception, ldap_err2string (ret)); return NULL; } if (entries == NULL) /* timed out */ return PyList_New (0); retval = PyList_New (0); entry = ldap_first_entry (watch->cnx, entries); while (entry != NULL) { PyObject *tuple; int type; char *dn; char *prev_dn; type = 0; dn = prev_dn = NULL; ret = parse_result (watch->cnx, entry, &dn, &type, &prev_dn); if (ret != LDAP_SUCCESS) { ldap_msgfree (entries); PyErr_SetString (LDAP_exception, ldap_err2string (ret)); Py_DECREF (retval); return NULL; } tuple = PyTuple_New (3); PyTuple_SET_ITEM (tuple, 0, PyString_FromString (dn)); PyTuple_SET_ITEM (tuple, 1, lookup_item_type (type)); if (prev_dn) { PyTuple_SET_ITEM (tuple, 2, PyString_FromString (prev_dn)); } else { Py_INCREF (Py_None); PyTuple_SET_ITEM (tuple, 2, Py_None); } PyList_Append (retval, tuple); Py_DECREF (tuple); free (dn); dn = NULL; type = 0; if (prev_dn != NULL) free (prev_dn); prev_dn = NULL; entry = ldap_next_entry (watch->cnx, entry); } ldap_msgfree (entries); return retval; } static int create_psearch_control (int change_types, int changes_only, int return_ecs, LDAPControl **control_ret) { LDAPControl *control; BerElement *element; ber_int_t ber_change_types; ber_int_t ber_changes_only; ber_int_t ber_return_ecs; int ret; if (control_ret != NULL) *control_ret = NULL; if ((element = ber_alloc_t (LBER_USE_DER)) == NULL) return LDAP_NO_MEMORY; ber_change_types = change_types; ber_changes_only = changes_only; ber_return_ecs = return_ecs; if (ber_printf (element, "{ibb}", ber_change_types, ber_changes_only, ber_return_ecs) < 0) { ber_free (element, 1); return LDAP_ENCODING_ERROR; } control = NULL; if ((ret = ldap_create_control (_LDAP_CONTROL_PERSISTENTSEARCH, element, 1, &control)) != LDAP_SUCCESS) { ber_free (element, 1); return ret; } ber_free (element, 1); if (control_ret != NULL) *control_ret = control; else ldap_control_free (control); return LDAP_SUCCESS; } static int start_persistent_search (LDAP *cnx, const char *base, int *msgid) { LDAPControl *controls[2]; int ret; if ((ret = create_psearch_control (_LDAP_CHANGETYPE_ANY, 1, 1, &controls[0])) != LDAP_SUCCESS) return ret; controls[1] = NULL; ret = ldap_search_ext (cnx, base, LDAP_SCOPE_SUBTREE, "(objectClass=*)", NULL, /* attrs */ 0, /* attrsonly */ controls, NULL, /* clientctrls */ NULL, /* timeout */ LDAP_NO_LIMIT, msgid); ldap_control_free (controls[0]); return ret; } static int initialize_ldap_connection (LDAP **cnxp, const char *uri, const char *bind_dn, const char *bind_passwd) { LDAP *cnx; int protocol; int ret; if (cnxp != NULL) *cnxp = NULL; cnx = NULL; if ((ret = ldap_initialize (&cnx, uri)) != LDAP_SUCCESS) return ret; protocol = LDAP_VERSION3; if ((ret = ldap_set_option (cnx, LDAP_OPT_PROTOCOL_VERSION, &protocol)) != LDAP_SUCCESS) { ldap_unbind_ext (cnx, NULL, NULL); return ret; } if (bind_dn != NULL && bind_passwd != NULL) { struct berval bind_cred; bind_cred.bv_val = (char *) bind_passwd; bind_cred.bv_len = strlen (bind_passwd); if ((ret = ldap_sasl_bind_s (cnx, bind_dn, LDAP_SASL_SIMPLE, &bind_cred, NULL, NULL, NULL)) != LDAP_SUCCESS) { ldap_unbind_ext (cnx, NULL, NULL); return ret; } } if (cnxp != NULL) *cnxp = cnx; return LDAP_SUCCESS; } static int pyldap_watch_init (PyLDAPWatch *self, PyObject *args, PyObject *kwargs) { char *uri; char *base; char *bind_dn; char *bind_passwd; int ret; uri = base = bind_dn = bind_passwd = NULL; if (!PyArg_ParseTuple (args, "ss|zz:ldapwatch.LDAPWatch.__init__", &uri, &base, &bind_dn, &bind_passwd)) return -1; self->uri = strdup (uri); self->base = strdup (base); if (bind_dn != NULL) self->bind_dn = strdup (base); else self->bind_dn = NULL; if (bind_passwd != NULL) self->bind_passwd = strdup (base); else self->bind_passwd = NULL; if (self->uri == NULL || self->base == NULL || (bind_dn != NULL && self->bind_dn == NULL) || (bind_passwd != NULL && self->bind_passwd == NULL)) { PyErr_SetString (PyExc_RuntimeError, "could not allocate memory"); return -1; } self->cnx = NULL; ret = initialize_ldap_connection (&self->cnx, self->uri, self->bind_dn, self->bind_passwd); if (ret != LDAP_SUCCESS) { PyErr_SetString (LDAP_exception, ldap_err2string (ret)); return -1; } self->msgid = 0; ret = start_persistent_search (self->cnx, self->base, &self->msgid); if (ret != LDAP_SUCCESS) { PyErr_SetString (LDAP_exception, ldap_err2string (ret)); return -1; } return 0; } static void pyldap_watch_dealloc (PyLDAPWatch *self) { if (self->cnx != NULL) ldap_unbind_ext (self->cnx, NULL, NULL); self->cnx = NULL; self->msgid = 0; if (self->uri != NULL) free (self->uri); self->uri = NULL; if (self->base != NULL) free (self->base); self->base = NULL; if (self->bind_dn != NULL) free (self->bind_dn); self->bind_dn = NULL; if (self->bind_passwd != NULL) free (self->bind_passwd); self->bind_passwd = NULL; PyObject_DEL (self); } static PyObject * pyldap_watch_getattro (PyLDAPWatch *self, PyObject *py_attr) { if (PyString_Check (py_attr)) { char *attr; attr = PyString_AsString (py_attr); if (!strcmp (attr, "__members__")) { return Py_BuildValue ("[]"); } } return PyObject_GenericGetAttr ((PyObject *) self, py_attr); } static struct PyMethodDef pyldap_watch_methods[] = { { "get_changes", pyldap_watch_get_changes, METH_VARARGS }, { NULL, NULL, 0 } }; static PyTypeObject PyLDAPWatch_Type = { PyObject_HEAD_INIT(NULL) 0, /* ob_size */ "ldapwatch.LDAPWatch", /* tp_name */ sizeof (PyLDAPWatch), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor) pyldap_watch_dealloc, /* tp_dealloc */ (printfunc)0, /* tp_print */ (getattrfunc)0, /* tp_getattr */ (setattrfunc)0, /* tp_setattr */ (cmpfunc)0, /* tp_compare */ (reprfunc)0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ (hashfunc)0, /* tp_hash */ (ternaryfunc)0, /* tp_call */ (reprfunc)0, /* tp_str */ (getattrofunc)pyldap_watch_getattro, /* tp_getattro */ (setattrofunc)0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ NULL, /* Documentation string */ (traverseproc)0, /* tp_traverse */ (inquiry)0, /* tp_clear */ (richcmpfunc)0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ (getiterfunc)0, /* tp_iter */ (iternextfunc)0, /* tp_iternext */ pyldap_watch_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ (PyTypeObject *)0, /* tp_base */ (PyObject *)0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)pyldap_watch_init, /* tp_init */ 0, /* tp_alloc */ PyType_GenericNew, /* tp_new */ 0, /* tp_free */ (inquiry)0, /* tp_is_gc */ (PyObject *)0, /* tp_bases */ }; static struct PyMethodDef ldapwatch_methods[] = { { NULL, NULL, 0 } }; void initldapwatch (void); DL_EXPORT (void) initldapwatch (void) { module = Py_InitModule4 ("ldapwatch", ldapwatch_methods, 0, 0, PYTHON_API_VERSION); PyLDAPWatch_Type.ob_type = &PyType_Type; PyType_Ready (&PyLDAPWatch_Type); PyModule_AddObject (module, "LDAPWatch", (PyObject *) &PyLDAPWatch_Type); LDAP_exception = PyErr_NewException ("ldapwatch.LDAPError", NULL, NULL); PyModule_AddObject (module, "LDAPError", LDAP_exception); PyModule_AddIntConstant (module, "TYPE_INVALID", 0); PyModule_AddIntConstant (module, "TYPE_ADD", _LDAP_CHANGETYPE_ADD); PyModule_AddIntConstant (module, "TYPE_DELETE", _LDAP_CHANGETYPE_DELETE); PyModule_AddIntConstant (module, "TYPE_MODIFY", _LDAP_CHANGETYPE_MODIFY); PyModule_AddIntConstant (module, "TYPE_MODDN", _LDAP_CHANGETYPE_MODDN); }