diff options
Diffstat (limited to 'lualdap.c')
-rw-r--r-- | lualdap.c | 1056 |
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; +} |