aboutsummaryrefslogtreecommitdiffstats
path: root/lualdap.c
diff options
context:
space:
mode:
Diffstat (limited to 'lualdap.c')
-rw-r--r--lualdap.c1056
1 files changed, 1056 insertions, 0 deletions
diff --git a/lualdap.c b/lualdap.c
new file mode 100644
index 0000000..a3f42a9
--- /dev/null
+++ b/lualdap.c
@@ -0,0 +1,1056 @@
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <ldap.h>
+#include <lua.h>
+#include <lauxlib.h>
+
+#define LUALDAP_PREFIX "LuaLDAP: "
+#define LUALDAP_TABLENAME "lualdap"
+#define LUALDAP_CONNECTION_METATABLE "LuaLDAP connection"
+#define LUALDAP_SEARCH_METATABLE "LuaLDAP search"
+
+#define LUALDAP_MOD_ADD (LDAP_MOD_ADD | LDAP_MOD_BVALUES)
+#define LUALDAP_MOD_DEL (LDAP_MOD_DELETE | LDAP_MOD_BVALUES)
+#define LUALDAP_MOD_REP (LDAP_MOD_REPLACE | LDAP_MOD_BVALUES)
+#define LUALDAP_NO_OP 0
+
+/* Maximum number of attributes manipulated in an operation */
+#ifndef LUALDAP_MAX_ATTRS
+#define LUALDAP_MAX_ATTRS 100
+#endif
+
+/* Size of buffer of NULL-terminated arrays of pointers to struct values */
+#ifndef LUALDAP_ARRAY_VALUES_SIZE
+#define LUALDAP_ARRAY_VALUES_SIZE (2 * LUALDAP_MAX_ATTRS)
+#endif
+
+/* Maximum number of values structures */
+#ifndef LUALDAP_MAX_VALUES
+#define LUALDAP_MAX_VALUES (LUALDAP_ARRAY_VALUES_SIZE / 2)
+#endif
+
+
+typedef struct {
+ LDAP *ld; /* LDAP connection */
+} conn_data;
+
+
+/* LDAP search context information */
+typedef struct {
+ int conn; /* conn_data reference */
+ int msgid;
+} search_data;
+
+
+/* LDAP attribute modification structure */
+typedef struct {
+ LDAPMod * attrs[LUALDAP_MAX_ATTRS + 1];
+ LDAPMod mods[LUALDAP_MAX_ATTRS];
+ int ai;
+ BerValue * values[LUALDAP_ARRAY_VALUES_SIZE];
+ int vi;
+ BerValue bvals[LUALDAP_MAX_VALUES];
+ int bi;
+} attrs_data;
+
+
+int luaopen_lualdap(lua_State *L);
+
+
+/*
+** Typical error situation.
+*/
+static int faildirect(lua_State *L, const char *errmsg)
+{
+ lua_pushnil(L);
+ lua_pushstring(L, errmsg);
+ return 2;
+}
+
+
+/*
+** Get a connection object from the first stack position.
+*/
+static conn_data *getconnection(lua_State *L)
+{
+ conn_data *conn = (conn_data *)luaL_checkudata(L, 1, LUALDAP_CONNECTION_METATABLE);
+ luaL_argcheck(L, conn != NULL, 1, LUALDAP_PREFIX "LDAP connection expected");
+ luaL_argcheck(L, conn->ld, 1, LUALDAP_PREFIX "LDAP connection is closed");
+ return conn;
+}
+
+
+/*
+** Get a search object from the first upvalue position.
+*/
+static search_data *getsearch(lua_State *L)
+{
+ /* don't need to check upvalue's integrity */
+ search_data *search = (search_data *)lua_touserdata(L, lua_upvalueindex(1));
+ luaL_argcheck (L, search->conn != LUA_NOREF, 1, LUALDAP_PREFIX "LDAP search is closed");
+ return search;
+}
+
+
+/*
+** Set metatable of userdata on top of the stack.
+*/
+static void lualdap_setmeta(lua_State *L, const char *name)
+{
+ luaL_getmetatable(L, name);
+ lua_setmetatable(L, -2);
+}
+
+
+/*
+** Error on option.
+*/
+static int option_error(lua_State *L, const char *name, const char *type)
+{
+ return luaL_error(L, LUALDAP_PREFIX "invalid value on option `%s': %s expected, got %s", name, type, lua_typename(L, lua_type(L, -1)));
+}
+
+
+/*
+** Get the field called name of the table at position 2.
+*/
+static void strgettable(lua_State *L, const char *name)
+{
+ lua_pushstring(L, name);
+ lua_gettable(L, 2);
+}
+
+
+/*
+** Get the field named name as a string.
+** The table MUST be at position 2.
+*/
+static const char *strtabparam(lua_State *L, const char *name, char *def)
+{
+ strgettable(L, name);
+ if (lua_isnil(L, -1))
+ return def;
+ else if (lua_isstring(L, -1))
+ return lua_tostring(L, -1);
+ else {
+ option_error(L, name, "string");
+ return NULL;
+ }
+}
+
+
+/*
+** Get the field named name as an integer.
+** The table MUST be at position 2.
+*/
+static long longtabparam(lua_State *L, const char *name, int def)
+{
+ strgettable(L, name);
+ if (lua_isnil(L, -1))
+ return def;
+ else if (lua_isnumber(L, -1))
+ return (long)lua_tonumber(L, -1);
+ else
+ return option_error(L, name, "number");
+}
+
+
+/*
+** Get the field named name as a double.
+** The table MUST be at position 2.
+*/
+static double numbertabparam(lua_State *L, const char *name, double def)
+{
+ strgettable(L, name);
+ if (lua_isnil(L, -1))
+ return def;
+ else if (lua_isnumber(L, -1))
+ return lua_tonumber(L, -1);
+ else
+ return option_error(L, name, "number");
+}
+
+
+/*
+** Get the field named name as a boolean.
+** The table MUST be at position 2.
+*/
+static int booltabparam(lua_State *L, const char *name, int def)
+{
+ strgettable(L, name);
+ if (lua_isnil(L, -1))
+ return def;
+ else if (lua_isboolean(L, -1))
+ return lua_toboolean(L, -1);
+ else
+ return option_error(L, name, "boolean");
+}
+
+
+/*
+** Error on attribute's value.
+*/
+static void value_error(lua_State *L, const char *name)
+{
+ luaL_error(L, LUALDAP_PREFIX "invalid value of attribute `%s' (%s)",
+ name, lua_typename(L, lua_type(L, -1)));
+}
+
+
+/*
+** Initialize attributes structure.
+*/
+static void A_init(attrs_data *attrs)
+{
+ attrs->ai = 0;
+ attrs->attrs[0] = NULL;
+ attrs->vi = 0;
+ attrs->values[0] = NULL;
+ attrs->bi = 0;
+}
+
+
+/*
+** Store the string on top of the stack on the attributes structure.
+** Increment the bvals counter.
+*/
+static BerValue *A_setbval(lua_State *L, attrs_data *a, const char *n)
+{
+ BerValue *ret = &(a->bvals[a->bi]);
+ if (a->bi >= LUALDAP_MAX_VALUES) {
+ luaL_error(L, LUALDAP_PREFIX "too many values");
+ return NULL;
+ } else if (!lua_isstring(L, -1)) {
+ value_error(L, n);
+ return NULL;
+ }
+ a->bvals[a->bi].bv_len = lua_strlen(L, -1);
+ a->bvals[a->bi].bv_val = (char *)lua_tostring(L, -1);
+ a->bi++;
+ return ret;
+}
+
+
+/*
+** Store a pointer to the value on top of the stack on the attributes structure.
+*/
+static BerValue **A_setval(lua_State *L, attrs_data *a, const char *n)
+{
+ BerValue **ret = &(a->values[a->vi]);
+ if (a->vi >= LUALDAP_ARRAY_VALUES_SIZE) {
+ luaL_error(L, LUALDAP_PREFIX "too many values");
+ return NULL;
+ }
+ a->values[a->vi] = A_setbval(L, a, n);
+ a->vi++;
+ return ret;
+}
+
+
+/*
+** Store a NULL pointer on the attributes structure.
+*/
+static BerValue **A_nullval(lua_State *L, attrs_data *a)
+{
+ BerValue **ret = &(a->values[a->vi]);
+ if (a->vi >= LUALDAP_ARRAY_VALUES_SIZE) {
+ luaL_error(L, LUALDAP_PREFIX "too many values");
+ return NULL;
+ }
+ a->values[a->vi] = NULL;
+ a->vi++;
+ return ret;
+}
+
+
+/*
+** Store the value of an attribute.
+** Valid values are:
+** true => no values;
+** string => one value; or
+** table of strings => many values.
+*/
+static BerValue **A_tab2val(lua_State *L, attrs_data *a, const char *name)
+{
+ int tab = lua_gettop(L);
+ BerValue **ret = &(a->values[a->vi]);
+ if (lua_isboolean(L, tab) && (lua_toboolean(L, tab) == 1)) /* true */
+ return NULL;
+ else if (lua_isstring(L, tab)) /* string */
+ A_setval(L, a, name);
+ else if (lua_istable(L, tab)) { /* list of strings */
+ int i;
+ int n = luaL_getn(L, tab);
+
+ for (i = 1; i <= n; i++) {
+ lua_rawgeti(L, tab, i); /* push table element */
+ A_setval(L, a, name);
+ }
+ lua_pop(L, n);
+ } else {
+ value_error(L, name);
+ return NULL;
+ }
+ A_nullval(L, a);
+ return ret;
+}
+
+
+/*
+** Set a modification value (which MUST be on top of the stack).
+*/
+static void A_setmod(lua_State *L, attrs_data *a, int op, const char *name)
+{
+ if (a->ai >= LUALDAP_MAX_ATTRS) {
+ luaL_error(L, LUALDAP_PREFIX "too many attributes");
+ return;
+ }
+ a->mods[a->ai].mod_op = op;
+ a->mods[a->ai].mod_type = (char *)name;
+ a->mods[a->ai].mod_bvalues = A_tab2val(L, a, name);
+ a->attrs[a->ai] = &a->mods[a->ai];
+ a->ai++;
+}
+
+
+/*
+** Convert a Lua table into an array of modifications.
+** An array of modifications is a NULL-terminated array of LDAPMod's.
+*/
+static void A_tab2mod(lua_State *L, attrs_data *a, int tab, int op)
+{
+ lua_pushnil(L); /* first key for lua_next */
+ while (lua_next(L, tab) != 0) {
+ /* attribute must be a string and not a number */
+ if ((!lua_isnumber(L, -2)) && (lua_isstring(L, -2)))
+ A_setmod(L, a, op, lua_tostring(L, -2));
+ /* pop value and leave last key on the stack as next key for lua_next */
+ lua_pop(L, 1);
+ }
+}
+
+
+/*
+** Terminate the array of attributes.
+*/
+static void A_lastattr(lua_State *L, attrs_data *a)
+{
+ if (a->ai >= LUALDAP_MAX_ATTRS) {
+ luaL_error(L, LUALDAP_PREFIX "too many attributes");
+ return;
+ }
+ a->attrs[a->ai] = NULL;
+ a->ai++;
+}
+
+
+/*
+** Copy a string or a table of strings from Lua to a NULL-terminated array
+** of C-strings.
+*/
+static int table2strarray(lua_State *L, int tab, char *array[], int limit)
+{
+ if (lua_isstring(L, tab)) {
+ if (limit < 2)
+ return luaL_error(L, LUALDAP_PREFIX "too many arguments");
+ array[0] = (char *)lua_tostring(L, tab);
+ array[1] = NULL;
+ } else if (lua_istable(L, tab)) {
+ int i;
+ int n = luaL_getn(L, tab);
+
+ if (limit < (n+1))
+ return luaL_error(L, LUALDAP_PREFIX "too many arguments");
+ for (i = 0; i < n; i++) {
+ lua_rawgeti(L, tab, i+1); /* push table element */
+ if (lua_isstring(L, -1))
+ array[i] = (char *)lua_tostring(L, -1);
+ else {
+ return luaL_error(L, LUALDAP_PREFIX "invalid value #%d", i+1);
+ }
+ }
+ array[n] = NULL;
+ } else
+ return luaL_error(L, LUALDAP_PREFIX "bad argument #%d (table or string expected, got %s)", tab, lua_typename(L, lua_type(L, tab)));
+ return 0;
+}
+
+
+/*
+** Get the result message of an operation.
+** #1 upvalue == connection
+** #2 upvalue == msgid
+** #3 upvalue == result code of the message (ADD, DEL etc.) to be received.
+*/
+static int result_message(lua_State *L)
+{
+ struct timeval *timeout = NULL; /* ??? function parameter ??? */
+ LDAPMessage *res;
+ int rc;
+ conn_data *conn = (conn_data *)lua_touserdata(L, lua_upvalueindex(1));
+ int msgid = (int)lua_tonumber(L, lua_upvalueindex(2));
+ luaL_argcheck(L, conn->ld, 1, LUALDAP_PREFIX "LDAP connection is closed");
+ rc = ldap_result(conn->ld, msgid, LDAP_MSG_ONE, timeout, &res);
+ if (rc == 0)
+ return faildirect(L, LUALDAP_PREFIX "result timeout expired");
+ else if (rc < 0) {
+ ldap_msgfree(res);
+ return faildirect(L, LUALDAP_PREFIX "result error");
+ } else {
+ int err, ret = 1;
+ char *mdn, *msg;
+
+ rc = ldap_parse_result(conn->ld, res, &err, &mdn, &msg, NULL, NULL, 1);
+ if (rc != LDAP_SUCCESS)
+ return faildirect(L, ldap_err2string(rc));
+ switch (err) {
+ case LDAP_SUCCESS:
+ case LDAP_COMPARE_TRUE:
+ lua_pushboolean(L, 1);
+ break;
+ case LDAP_COMPARE_FALSE:
+ lua_pushboolean(L, 0);
+ break;
+ default:
+ lua_pushnil(L);
+ lua_pushliteral(L, LUALDAP_PREFIX);
+ lua_pushstring(L, msg);
+ lua_pushliteral(L, " ");
+ lua_pushstring(L, ldap_err2string(err));
+ lua_concat(L, 4);
+ ret = 2;
+ }
+ ldap_memfree(mdn);
+ ldap_memfree(msg);
+ return ret;
+ }
+}
+
+
+/*
+** Push a function to process the LDAP result.
+*/
+static int create_future(lua_State *L, int rc, int conn, int msgid, int code)
+{
+ if (rc != LDAP_SUCCESS)
+ return faildirect(L, ldap_err2string(rc));
+ lua_pushvalue(L, conn); /* push connection as #1 upvalue */
+ lua_pushnumber(L, msgid); /* push msgid as #2 upvalue */
+ lua_pushnumber(L, code); /* push code as #3 upvalue */
+ lua_pushcclosure(L, result_message, 3);
+ return 1;
+}
+
+
+/*
+** Unbind from the directory.
+** @param #1 LDAP connection.
+** @return 1 in case of success; nothing when already closed.
+*/
+static int lualdap_close(lua_State *L)
+{
+ conn_data *conn = (conn_data *)luaL_checkudata(L, 1, LUALDAP_CONNECTION_METATABLE);
+ luaL_argcheck(L, conn != NULL, 1, LUALDAP_PREFIX "LDAP connection expected");
+ if (conn->ld == NULL) /* already closed */
+ return 0;
+ ldap_unbind_ext(conn->ld, NULL, NULL);
+ conn->ld = NULL;
+ lua_pushnumber(L, 1);
+ return 1;
+}
+
+
+/*
+** Add a new entry to the directory.
+** @param #1 LDAP connection.
+** @param #2 String with new entry's DN.
+** @param #3 Table with new entry's attributes and values.
+** @return Function to process the LDAP result.
+*/
+static int lualdap_add(lua_State *L)
+{
+ conn_data *conn = getconnection(L);
+ const char *dn = luaL_checkstring(L, 2);
+ attrs_data attrs;
+ int rc, msgid;
+ A_init(&attrs);
+ if (lua_istable(L, 3))
+ A_tab2mod(L, &attrs, 3, LUALDAP_MOD_ADD);
+ A_lastattr(L, &attrs);
+ rc = ldap_add_ext(conn->ld, dn, attrs.attrs, NULL, NULL, &msgid);
+ return create_future(L, rc, 1, msgid, LDAP_RES_ADD);
+}
+
+
+/*
+** Compare a value against an entry.
+** @param #1 LDAP connection.
+** @param #2 String with entry's DN.
+** @param #3 String with attribute's name.
+** @param #4 String with attribute's value.
+** @return Function to process the LDAP result.
+*/
+static int lualdap_compare(lua_State *L)
+{
+ conn_data *conn = getconnection(L);
+ const char *dn = luaL_checkstring(L, 2);
+ const char *attr = luaL_checkstring(L, 3);
+ BerValue bvalue;
+ int rc, msgid;
+ bvalue.bv_val = (char *)luaL_checkstring(L, 4);
+ bvalue.bv_len = lua_strlen(L, 4);
+ rc = ldap_compare_ext(conn->ld, dn, attr, &bvalue, NULL, NULL, &msgid);
+ return create_future(L, rc, 1, msgid, LDAP_RES_COMPARE);
+}
+
+
+/*
+** Delete an entry.
+** @param #1 LDAP connection.
+** @param #2 String with entry's DN.
+** @return Boolean.
+*/
+static int lualdap_delete(lua_State *L)
+{
+ conn_data *conn = getconnection(L);
+ const char *dn = luaL_checkstring(L, 2);
+ int rc, msgid;
+ rc = ldap_delete_ext(conn->ld, dn, NULL, NULL, &msgid);
+ return create_future(L, rc, 1, msgid, LDAP_RES_DELETE);
+}
+
+
+/*
+** Convert a string into an internal LDAP_MOD operation code.
+*/
+static int op2code(const char *s)
+{
+ if (!s)
+ return LUALDAP_NO_OP;
+ switch (*s) {
+ case '+':
+ return LUALDAP_MOD_ADD;
+ case '-':
+ return LUALDAP_MOD_DEL;
+ case '=':
+ return LUALDAP_MOD_REP;
+ default:
+ return LUALDAP_NO_OP;
+ }
+}
+
+
+/*
+** Modify an entry.
+** @param #1 LDAP connection.
+** @param #2 String with entry's DN.
+** @param #3, #4... Tables with modifications to apply.
+** @return True on success or nil, error message otherwise.
+*/
+static int lualdap_modify(lua_State *L)
+{
+ conn_data *conn = getconnection(L);
+ const char *dn = luaL_checkstring(L, 2);
+ attrs_data attrs;
+ int rc, msgid;
+ int param = 3;
+ A_init(&attrs);
+ while (lua_istable(L, param)) {
+ int op;
+ /* get operation ('+','-','=' operations allowed) */
+ lua_rawgeti(L, param, 1);
+ op = op2code(lua_tostring(L, -1));
+ if (op == LUALDAP_NO_OP)
+ return luaL_error(L, LUALDAP_PREFIX "forgotten operation on argument #%d", param);
+ /* get array of attributes and values */
+ A_tab2mod(L, &attrs, param, op);
+ param++;
+ }
+ A_lastattr(L, &attrs);
+ rc = ldap_modify_ext(conn->ld, dn, attrs.attrs, NULL, NULL, &msgid);
+ return create_future(L, rc, 1, msgid, LDAP_RES_MODIFY);
+}
+
+
+/*
+** Change the distinguished name of an entry.
+*/
+static int lualdap_rename(lua_State *L)
+{
+ conn_data *conn = getconnection(L);
+ const char *dn = luaL_checkstring(L, 2);
+ const char *rdn = luaL_checkstring(L, 3);
+ const char *par = luaL_optlstring(L, 4, NULL, NULL);
+ const int del = luaL_optnumber(L, 5, 0);
+ int msgid;
+ int rc = ldap_rename(conn->ld, dn, rdn, par, del, NULL, NULL, &msgid);
+ return create_future(L, rc, 1, msgid, LDAP_RES_MODDN);
+}
+
+
+/*
+** Push an attribute value (or a table of values) on top of the stack.
+** @param L lua_State.
+** @param ld LDAP Connection.
+** @param entry Current entry.
+** @param attr Name of entry's attribute to get values from.
+** @return 1 in case of success.
+*/
+static int push_values(lua_State *L, LDAP *ld, LDAPMessage *entry, char *attr)
+{
+ int i, n;
+ BerValue **vals = ldap_get_values_len(ld, entry, attr);
+ n = ldap_count_values_len(vals);
+ if (n == 0) /* no values */
+ lua_pushboolean(L, 1);
+ else if (n == 1) /* just one value */
+ lua_pushlstring(L, vals[0]->bv_val, vals[0]->bv_len);
+ else { /* Multiple values */
+ lua_newtable(L);
+ for (i = 0; i < n; i++) {
+ lua_pushlstring(L, vals[i]->bv_val, vals[i]->bv_len);
+ lua_rawseti(L, -2, i+1);
+ }
+ }
+ ldap_value_free_len(vals);
+ return 1;
+}
+
+
+/*
+** Store entry's attributes and values at the given table.
+** @param entry Current entry.
+** @param tab Absolute stack index of the table.
+*/
+static void set_attribs(lua_State *L, LDAP *ld, LDAPMessage *entry, int tab)
+{
+ char *attr;
+ BerElement *ber = NULL;
+ for (attr = ldap_first_attribute(ld, entry, &ber);
+ attr != NULL;
+ attr = ldap_next_attribute(ld, entry, ber)) {
+ lua_pushstring(L, attr);
+ push_values(L, ld, entry, attr);
+ lua_rawset(L, tab); /* tab[attr] = vals */
+ ldap_memfree(attr);
+ }
+ ber_free(ber, 0); /* don't need to test if (ber == NULL) */
+}
+
+
+/*
+** Get the distinguished name of the given entry and pushes it on the stack.
+*/
+static void push_dn(lua_State *L, LDAP *ld, LDAPMessage *entry)
+{
+ char *dn = ldap_get_dn(ld, entry);
+ lua_pushstring(L, dn);
+ ldap_memfree(dn);
+}
+
+
+/*
+** Release connection reference.
+*/
+static void search_close(lua_State *L, search_data *search)
+{
+ luaL_unref(L, LUA_REGISTRYINDEX, search->conn);
+ search->conn = LUA_NOREF;
+}
+
+
+/*
+** Retrieve next message...
+** @return #1 entry's distinguished name.
+** @return #2 table with entry's attributes and values.
+*/
+static int next_message(lua_State *L)
+{
+ search_data *search = getsearch(L);
+ conn_data *conn;
+ struct timeval *timeout = NULL; /* ??? function parameter ??? */
+ LDAPMessage *res;
+ int rc;
+ int ret;
+ lua_rawgeti(L, LUA_REGISTRYINDEX, search->conn);
+ conn = (conn_data *)lua_touserdata(L, -1); /* get connection */
+ rc = ldap_result(conn->ld, search->msgid, LDAP_MSG_ONE, timeout, &res);
+ if (rc == 0)
+ return faildirect(L, LUALDAP_PREFIX "result timeout expired");
+ else if (rc == -1)
+ return faildirect(L, LUALDAP_PREFIX "result error");
+ else if (rc == LDAP_RES_SEARCH_RESULT) { /* last message => nil */
+ /* close search object to avoid reuse */
+ search_close(L, search);
+ ret = 0;
+ } else {
+ LDAPMessage *msg = ldap_first_message(conn->ld, res);
+ switch (ldap_msgtype(msg)) {
+ case LDAP_RES_SEARCH_ENTRY: {
+ LDAPMessage *entry = ldap_first_entry(conn->ld, msg);
+ push_dn(L, conn->ld, entry);
+ lua_newtable(L);
+ set_attribs(L, conn->ld, entry, lua_gettop(L));
+ ret = 2; /* two return values */
+ break;
+ }
+ case LDAP_RES_SEARCH_REFERENCE: {
+ LDAPMessage *ref = ldap_first_reference(conn->ld, msg);
+ push_dn(L, conn->ld, ref); /* is this supposed to work? */
+ lua_pushnil(L);
+ ret = 2; /* two return values */
+ break;
+ }
+ case LDAP_RES_SEARCH_RESULT:
+ /* close search object to avoid reuse */
+ search_close(L, search);
+ ret = 0;
+ break;
+ default:
+ ldap_msgfree(res);
+ return luaL_error(L, LUALDAP_PREFIX "error on search result chain");
+ }
+ }
+ ldap_msgfree(res);
+ return ret;
+}
+
+
+/*
+** Convert a string to one of the possible scopes of the search.
+*/
+static int string2scope(lua_State *L, const char *s)
+{
+ if ((s == NULL) || (*s == '\0'))
+ return LDAP_SCOPE_DEFAULT;
+ switch (*s) {
+ case 'b':
+ return LDAP_SCOPE_BASE;
+ case 'o':
+ return LDAP_SCOPE_ONELEVEL;
+ case 's':
+ return LDAP_SCOPE_SUBTREE;
+ default:
+ return luaL_error(L, LUALDAP_PREFIX "invalid search scope `%s'", s);
+ }
+}
+
+
+/*
+** Close the search object.
+*/
+static int lualdap_search_close(lua_State *L)
+{
+ search_data *search = (search_data *)luaL_checkudata(L, 1, LUALDAP_SEARCH_METATABLE);
+ luaL_argcheck (L, search != NULL, 1, LUALDAP_PREFIX "LDAP search expected");
+ if (search->conn == LUA_NOREF)
+ return 0;
+ search_close(L, search);
+ lua_pushnumber(L, 1);
+ return 1;
+}
+
+
+/*
+** Create a search object and leaves it on top of the stack.
+*/
+static void create_search(lua_State *L, int conn_index, int msgid)
+{
+ search_data *search = (search_data *)lua_newuserdata(L, sizeof(search_data));
+ lualdap_setmeta(L, LUALDAP_SEARCH_METATABLE);
+ search->conn = LUA_NOREF;
+ search->msgid = msgid;
+ lua_pushvalue(L, conn_index);
+ search->conn = luaL_ref(L, LUA_REGISTRYINDEX);
+}
+
+
+/*
+** Fill in the attrs array, according to the attrs parameter.
+*/
+static int get_attrs_param(lua_State *L, char *attrs[])
+{
+ lua_pushstring(L, "attrs");
+ lua_gettable(L, 2);
+ if (lua_isstring(L, -1)) {
+ attrs[0] = (char *)lua_tostring(L, -1);
+ attrs[1] = NULL;
+ } else if (!lua_istable(L, -1))
+ attrs[0] = NULL;
+ else if (table2strarray(L, lua_gettop(L), attrs, LUALDAP_MAX_ATTRS))
+ return 0;
+ return 1;
+}
+
+
+/*
+** Fill in the struct timeval, according to the timeout parameter.
+*/
+static struct timeval *get_timeout_param(lua_State *L, struct timeval *st)
+{
+ double t = numbertabparam(L, "timeout", 0);
+ st->tv_sec = (long)t;
+ st->tv_usec = (long)(1000000 * (t - st->tv_sec));
+ if (st->tv_sec == 0 && st->tv_usec == 0)
+ return NULL;
+ else
+ return st;
+}
+
+
+/*
+** Perform a search operation.
+** @return #1 Function to iterate over the result entries.
+** @return #2 nil.
+** @return #3 nil as first entry.
+** The search result is defined as an upvalue of the iterator.
+*/
+static int lualdap_search(lua_State *L)
+{
+ conn_data *conn = getconnection(L);
+ const char *base;
+ const char *filter;
+ char *attrs[LUALDAP_MAX_ATTRS];
+ int scope, attrsonly, msgid, rc, sizelimit;
+ struct timeval st, *timeout;
+ if (!lua_istable(L, 2))
+ return luaL_error(L, LUALDAP_PREFIX "no search specification");
+ if (!get_attrs_param(L, attrs))
+ return 2;
+ /* get other parameters */
+ attrsonly = booltabparam(L, "attrsonly", 0);
+ base = strtabparam(L, "base", NULL);
+ filter = strtabparam(L, "filter", NULL);
+ scope = string2scope(L, strtabparam(L, "scope", NULL));
+ sizelimit = longtabparam(L, "sizelimit", LDAP_NO_LIMIT);
+ timeout = get_timeout_param(L, &st);
+ rc = ldap_search_ext(conn->ld, base, scope, filter, attrs, attrsonly,
+ NULL, NULL, timeout, sizelimit, &msgid);
+ if (rc != LDAP_SUCCESS)
+ return luaL_error(L, LUALDAP_PREFIX "%s", ldap_err2string(rc));
+
+ create_search(L, 1, msgid);
+ lua_pushcclosure(L, next_message, 1);
+ return 1;
+}
+
+
+/*
+** Return the name of the object's metatable.
+** This function is used by `tostring'.
+*/
+static int lualdap_conn_tostring(lua_State *L)
+{
+ char buff[100];
+ conn_data *conn = (conn_data *)lua_touserdata(L, 1);
+ if (conn->ld == NULL)
+ strcpy(buff, "closed");
+ else
+ sprintf(buff, "%p", conn);
+ lua_pushfstring(L, "%s (%s)", LUALDAP_CONNECTION_METATABLE, buff);
+ return 1;
+}
+
+
+/*
+** Return the name of the object's metatable.
+** This function is used by `tostring'.
+*/
+static int lualdap_search_tostring(lua_State *L)
+{
+ char buff[100];
+ search_data *search = (search_data *)lua_touserdata(L, 1);
+ luaL_argcheck (L, search->conn != LUA_NOREF, 1, LUALDAP_PREFIX "LDAP search is closed");
+ if (search->conn == LUA_NOREF)
+ strcpy(buff, "closed");
+ else
+ snprintf(buff, 100, "%p", search);
+ lua_pushfstring(L, "%s (%s)", LUALDAP_SEARCH_METATABLE, buff);
+ return 1;
+}
+
+
+/*
+** Create a metatable.
+*/
+static int lualdap_createmeta(lua_State *L)
+{
+ const luaL_reg methods[] = {
+ {"close", lualdap_close},
+ {"add", lualdap_add},
+ {"compare", lualdap_compare},
+ {"delete", lualdap_delete},
+ {"modify", lualdap_modify},
+ {"rename", lualdap_rename},
+ {"search", lualdap_search},
+ {NULL, NULL}
+ };
+
+ if (!luaL_newmetatable(L, LUALDAP_CONNECTION_METATABLE))
+ return 0;
+
+ /* define methods */
+ luaL_register(L, NULL, methods);
+
+ /* define metamethods */
+ lua_pushliteral(L, "__gc");
+ lua_pushcfunction(L, lualdap_close);
+ lua_settable(L, -3);
+
+ lua_pushliteral(L, "__index");
+ lua_pushvalue(L, -2);
+ lua_settable(L, -3);
+
+ lua_pushliteral(L, "__tostring");
+ lua_pushcfunction(L, lualdap_conn_tostring);
+ lua_settable(L, -3);
+
+ lua_pushliteral(L, "__metatable");
+ lua_pushliteral(L, LUALDAP_PREFIX "you're not allowed to get this metatable");
+ lua_settable(L, -3);
+
+ if (!luaL_newmetatable(L, LUALDAP_SEARCH_METATABLE))
+ return 0;
+
+ lua_pushliteral(L, "__gc");
+ lua_pushcfunction(L, lualdap_search_close);
+ lua_settable(L, -3);
+
+ lua_pushliteral(L, "__tostring");
+ lua_pushcclosure(L, lualdap_search_tostring, 1);
+ lua_settable(L, -3);
+
+ lua_pushliteral(L, "__metatable");
+ lua_pushliteral(L, LUALDAP_PREFIX "you're not allowed to get this metatable");
+ lua_settable(L, -3);
+
+ return 0;
+}
+
+
+/*
+** Open and initialize a connection to a server.
+** @param #1 table of connection options { uri, who, password, starttls, certfile, keyfile, cacertfile, cacertdir }
+** @return #1 Userdata with connection structure.
+*/
+static int lualdap_open_simple(lua_State *L)
+{
+ char *uri = NULL;
+ const char *who = NULL;
+ const char *password = NULL;
+ int starttls = 0;
+ const char *certfile = NULL;
+ const char *keyfile = NULL;
+ const char *cacertfile = NULL;
+ const char *cacertdir = NULL;
+
+ if (lua_istable(L, 1)) {
+ /* New API */
+ lua_settop(L, 1);
+ luaL_checktype(L, 1, LUA_TTABLE);
+ lua_getfield(L, 1, "uri");
+ lua_getfield(L, 1, "who");
+ lua_getfield(L, 1, "password");
+ lua_getfield(L, 1, "starttls");
+ lua_getfield(L, 1, "certfile");
+ lua_getfield(L, 1, "keyfile");
+ lua_getfield(L, 1, "cacertfile");
+ lua_getfield(L, 1, "cacertdir");
+
+ uri = (char *)luaL_checkstring(L, -8);
+ who = luaL_optstring(L, -7, NULL);
+ password = luaL_optstring(L, -6, NULL);
+ starttls = lua_toboolean(L, -5);
+ certfile = luaL_optstring(L, -4, NULL);
+ keyfile = luaL_optstring(L, -3, NULL);
+ cacertfile = luaL_optstring(L, -2, NULL);
+ cacertdir = luaL_optstring(L, -1, NULL);
+
+ /* Does popping here cause a use-after-free for those strings? */
+ lua_pop(L, 8);
+ } else {
+ /* Old API */
+ const char *hostname = luaL_checkstring(L, 1);
+ size_t hostname_len = strlen(hostname);
+
+ if (hostname_len > 248)
+ return faildirect(L, LUALDAP_PREFIX "Hostname is too large");
+ uri = alloca(hostname_len + 8);
+ strcpy(uri, "ldap://");
+ strcat(uri, hostname);
+
+ who = luaL_optstring(L, 2, NULL);
+ password = luaL_optstring(L, 3, NULL);
+ starttls = lua_toboolean(L, 4);
+ }
+
+ conn_data *conn = (conn_data *)lua_newuserdata(L, sizeof(conn_data));
+ struct berval cred = { password ? strlen(password) : 0, (char *)password };
+ int err, val;
+
+ lualdap_setmeta(L, LUALDAP_CONNECTION_METATABLE);
+ err = ldap_initialize(&conn->ld, uri);
+ if (err != LDAP_SUCCESS)
+ return faildirect(L, LUALDAP_PREFIX "Error connecting to server");
+
+ val = LDAP_VERSION3;
+ if (ldap_set_option(conn->ld, LDAP_OPT_PROTOCOL_VERSION, &val) != LDAP_OPT_SUCCESS)
+ return faildirect(L, LUALDAP_PREFIX "Error setting LDAP version");
+
+ if (certfile)
+ ldap_set_option(conn->ld, LDAP_OPT_X_TLS_CERTFILE, certfile);
+ if (keyfile)
+ ldap_set_option(conn->ld, LDAP_OPT_X_TLS_KEYFILE, keyfile);
+ if (cacertfile)
+ ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTFILE, cacertfile); /* The ca cert must be set globally */
+ if (cacertdir)
+ ldap_set_option(NULL, LDAP_OPT_X_TLS_CACERTDIR, cacertfile); /* The ca cert must be set globally */
+
+ if (starttls) {
+ err = ldap_start_tls_s(conn->ld, NULL, NULL);
+ if (err != LDAP_SUCCESS)
+ return faildirect(L, ldap_err2string(err));
+ }
+
+ err = ldap_sasl_bind_s(conn->ld, who, LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL);
+ if (err != LDAP_SUCCESS)
+ return faildirect(L, ldap_err2string(err));
+
+ return 1;
+}
+
+
+/*
+** Assumes the table is on top of the stack.
+*/
+static void set_info(lua_State *L)
+{
+ lua_pushliteral(L, "_COPYRIGHT");
+ lua_pushliteral(L, "Copyright (C) 2003-2007 Kepler Project, 2015 Jason A. Donenfeld <Jason@zx2c4.com>");
+ lua_settable(L, -3);
+ lua_pushliteral(L, "_DESCRIPTION");
+ lua_pushliteral(L, "LuaLDAP is a simple interface from Lua to an LDAP client");
+ lua_settable(L, -3);
+ lua_pushliteral(L, "_VERSION");
+ lua_pushliteral(L, "LuaLDAP 1.2.0");
+ lua_settable(L, -3);
+}
+
+
+/*
+** Create ldap table and register the open method.
+*/
+int luaopen_lualdap(lua_State *L)
+{
+ struct luaL_reg lualdap[] = {
+ {"open_simple", lualdap_open_simple},
+ {NULL, NULL},
+ };
+
+ lualdap_createmeta(L);
+ luaL_register(L, LUALDAP_TABLENAME, lualdap);
+ set_info(L);
+
+ return 1;
+}