bsw@1071: /* bsw@1071: * minimalistic Lua LDAP interface library bsw@1071: * bsw@1071: * The library does not set any global variable itself and must thus be bsw@1071: * loaded with bsw@1071: * bsw@1071: * mldap = require("mldap") bsw@1071: * bsw@1071: * or a similar statement. bsw@1071: * bsw@1071: * The library provides two functions, conn = bind{...} and unbind(conn) bsw@1071: * to connect to and disconnect from the LDAP server respectively. The bsw@1071: * unbind function is also provided as a method of the connection userdata bsw@1071: * object (see below). bsw@1071: * bsw@1071: * The arguments to the bind{...} function are documented in the source code bsw@1071: * (see C function mldap_bind). In case of success, the bind function returns bsw@1071: * a userdata object that provides two methods: query{...} and unbind(). In bsw@1071: * case of error, the bind function returns nil as first return value, an bsw@1071: * error string as second return value, and a numeric error code as third bsw@1071: * return value. A positive error code is an LDAP resultCode, a negative bsw@1071: * error code is an OpenLDAP API error code: bsw@1071: * bsw@1071: * connection, error_string, numeric_error_code = mldap.bind{...} bsw@1071: * bsw@1071: * For translating numeric error codes to an identifying (machine readable) bsw@1071: * string identifier, the library provides in addition to the two functions bsw@1071: * a table named 'errorcodes', for example: bsw@1071: * bsw@1071: * 49 == mldap.errorcodes["invalid_credentials"] bsw@1071: * bsw@1071: * and bsw@1071: * bsw@1071: * mldap.errorcodes[49] == "invalid_credentials" bsw@1071: * bsw@1071: * The arguments and return value of the query{...} method of the connection bsw@1071: * userdata object are also documented in the source code below (see bsw@1071: * C function mldap_query). Error conditions are reported the same way as the bsw@1071: * bind{...} function does. bsw@1071: * bsw@1071: * To close the connection, either the unbind() function of the library or bsw@1071: * the unbind() method can be called; it is allowed to call them multiple bsw@1071: * times, and they are also invoked by the garbage collector. bsw@1071: * bsw@1071: */ bsw@1071: bsw@1071: // Lua header inclusions: bsw@1071: #include bsw@1071: #include bsw@1071: bsw@1071: // OpenLDAP header inclusions: bsw@1071: #include bsw@1071: bsw@1071: // Standard C inclusions: bsw@1071: #include bsw@1071: #include bsw@1071: #include bsw@1071: bsw@1071: // Error code translation is included from separate C file: bsw@1071: #include "mldap_errorcodes.c" bsw@1071: bsw@1071: // Provide compatibility with Lua 5.1: bsw@1071: #if LUA_VERSION_NUM < 502 bsw@1071: #define luaL_newlib(L, t) lua_newtable((L)); luaL_register(L, NULL, t) bsw@1071: #define lua_rawlen lua_objlen bsw@1071: #define lua_len lua_objlen bsw@1071: #define luaL_setmetatable(L, regkey) \ bsw@1071: lua_getfield((L), LUA_REGISTRYINDEX, (regkey)); \ bsw@1071: lua_setmetatable((L), -2); bsw@1071: #endif bsw@1071: bsw@1071: // prefix for all Lua registry entries of this library: bsw@1071: #define MLDAP_REGKEY "556aeaf3c864af2e_mldap_" bsw@1071: bsw@1071: bsw@1071: static const char *mldap_get_named_string_arg( bsw@1071: // gets a named argument of type "string" from a table at the given stack position bsw@1071: bsw@1071: lua_State *L, // pointer to lua_State variable bsw@1071: int idx, // stack index of the table containing the named arguments bsw@1071: const char *argname, // name of the argument bsw@1071: int mandatory // if not 0, then the argument is mandatory and an error is raised if it isn't found bsw@1071: bsw@1071: // leaves the string as new element on top of the stack bsw@1071: ) { bsw@1071: bsw@1071: // pushes the table entry with the given argument name on top of the stack: bsw@1071: lua_getfield(L, idx, argname); bsw@1071: bsw@1071: // check, if the entry is nil or false: bsw@1071: if (!lua_toboolean(L, -1)) { bsw@1071: bsw@1071: // throw error, if named argument is mandatory: bsw@1071: if (mandatory) return luaL_error(L, "Named argument '%s' missing", argname), NULL; bsw@1071: bsw@1071: // return NULL pointer, if named argument is not mandatory: bsw@1071: return NULL; bsw@1071: bsw@1071: } bsw@1071: bsw@1071: // throw error, if the value of the argument is not string: bsw@1071: if (lua_type(L, -1) != LUA_TSTRING) return luaL_error(L, "Named argument '%s' is not a string", argname), NULL; bsw@1071: bsw@1071: // return a pointer to the string, leaving the string on top of the stack to avoid garbage collection: bsw@1071: return lua_tostring(L, -1); bsw@1071: bsw@1071: // leaves one element on the stack bsw@1071: } bsw@1071: bsw@1071: bsw@1071: static int mldap_get_named_number_arg( bsw@1071: // gets a named argument of type "number" from a table at the given stack position bsw@1071: bsw@1071: lua_State *L, // pointer to lua_State variable bsw@1071: int idx, // stack index of the table containing the named arguments bsw@1071: const char *argname, // name of the argument bsw@1071: int mandatory, // if not 0, then the argument is mandatory and an error is raised if it isn't found bsw@1071: lua_Number default_value // default value to return, if the argument is not mandatory and nil or false bsw@1071: bsw@1071: // opposed to 'mldap_get_named_string_arg', this function leaves no element on the stack bsw@1071: ) { bsw@1071: bsw@1071: lua_Number value; // value to return bsw@1071: bsw@1071: // pushes the table entry with the given argument name on top of the stack: bsw@1071: lua_getfield(L, idx, argname); bsw@1071: bsw@1071: // check, if the entry is nil or false: bsw@1071: if (lua_isnil(L, -1)) { bsw@1071: bsw@1071: // throw error, if named argument is mandatory: bsw@1071: if (mandatory) return luaL_error(L, "Named argument '%s' missing", argname), 0; bsw@1071: bsw@1071: // set default value as return value, if named argument is not mandatory: bsw@1071: value = default_value; bsw@1071: bsw@1071: } else { bsw@1071: bsw@1071: // throw error, if the value of the argument is not a number: bsw@1071: if (lua_type(L, -1) != LUA_TNUMBER) return luaL_error(L, "Named argument '%s' is not a number", argname), 0; bsw@1071: bsw@1071: // set return value to the number: bsw@1071: value = lua_tonumber(L, -1); bsw@1071: bsw@1071: } bsw@1071: bsw@1071: // remove unnecessary element from stack (not needed to avoid garbage collection): bsw@1071: return value; bsw@1071: bsw@1071: // leaves no new elements on the stack bsw@1071: } bsw@1071: bsw@1071: bsw@1071: static int mldap_scope( bsw@1071: // converts a string ("base", "onelevel", "subtree", "children") to an integer representing the LDAP scope bsw@1071: // and throws an error for any unknown string bsw@1071: bsw@1071: lua_State *L, // pointer to lua_State variable (needed to throw errors) bsw@1071: const char *scope_string // string that is either ("base", "onelevel", "subtree", "children") bsw@1071: bsw@1071: // does not affect or use the Lua stack at all bsw@1071: ) { bsw@1071: bsw@1071: // return integer according to string value: bsw@1071: if (!strcmp(scope_string, "base")) return LDAP_SCOPE_BASE; bsw@1071: if (!strcmp(scope_string, "onelevel")) return LDAP_SCOPE_ONELEVEL; bsw@1071: if (!strcmp(scope_string, "subtree")) return LDAP_SCOPE_SUBTREE; bsw@1071: if (!strcmp(scope_string, "children")) return LDAP_SCOPE_CHILDREN; bsw@1071: bsw@1071: // throw error for unknown string values: bsw@1071: return luaL_error(L, "Invalid LDAP scope: '%s'", scope_string), 0; bsw@1071: bsw@1071: } bsw@1071: bsw@1071: bsw@1071: static int mldap_bind(lua_State *L) { bsw@1071: // Lua C function that takes named arguments as a table bsw@1071: // and returns a userdata object, representing the LDAP connection bsw@1071: // or returns nil, an error string, and an error code (integer) on error bsw@1071: bsw@1071: // named arguments: bsw@1071: // "uri" (string) server URI to connect to bsw@1071: // "who" (string) DN to bind as bsw@1071: // "password" (string) password for DN to bind as bsw@1071: // "timeout" (number) timeout in seconds bsw@1071: bsw@1071: static const int ldap_version = LDAP_VERSION3; // providing a pointer (&ldap_version) to set LDAP protocol version 3 bsw@1071: const char *uri; // C string for "uri" argument bsw@1071: const char *who; // C string for "who" argument bsw@1071: struct berval cred; // credentials ("password") are stored as struct berval bsw@1071: lua_Number timeout_float; // float (lua_Number) for timeout bsw@1071: struct timeval timeout; // timeout is stored as struct timeval bsw@1071: int ldap_error; // LDAP error code (as returned by libldap calls) bsw@1071: LDAP *ldp; // pointer to an opaque OpenLDAP structure representing the connection bsw@1071: LDAP **ldp_ptr; // pointer to a Lua userdata structure (that only contains the 'ldp' pointer) bsw@1071: bsw@1071: // throw error if first argument is not a table: bsw@1071: if (lua_type(L, 1) != LUA_TTABLE) { bsw@1071: luaL_error(L, "Argument to function 'bind' is not a table."); bsw@1071: } bsw@1071: bsw@1071: // extract arguments: bsw@1071: uri = mldap_get_named_string_arg(L, 1, "uri", true); bsw@1071: who = mldap_get_named_string_arg(L, 1, "who", false); bsw@1071: cred.bv_val = mldap_get_named_string_arg(L, 1, "password", false); bsw@1071: if (cred.bv_val) cred.bv_len = strlen(cred.bv_val); bsw@1071: else cred.bv_len = 0; bsw@1071: timeout_float = mldap_get_named_number_arg(L, 1, "timeout", false, -1); bsw@1071: timeout.tv_sec = timeout_float; bsw@1071: timeout.tv_usec = (timeout_float - timeout.tv_sec) * 1000000; bsw@1071: bsw@1071: // initialize OpenLDAP structure and provide URI for connection: bsw@1071: ldap_error = ldap_initialize(&ldp, uri); bsw@1071: // on error, jump to label "mldap_queryconn_error1", as no ldap_unbind_ext_s() is needed: bsw@1071: if (ldap_error != LDAP_SUCCESS) goto mldap_queryconn_error1; bsw@1071: bsw@1071: // set LDAP protocol version 3: bsw@1071: ldap_error = ldap_set_option(ldp, LDAP_OPT_PROTOCOL_VERSION, &ldap_version); bsw@1071: // on error, jump to label "mldap_queryconn_error2", as ldap_unbind_ext_s() must be called: bsw@1071: if (ldap_error != LDAP_SUCCESS) goto mldap_queryconn_error2; bsw@1071: bsw@1071: // set timeout for asynchronous OpenLDAP library calls: bsw@1071: ldap_error = ldap_set_option(ldp, LDAP_OPT_TIMEOUT, &timeout); bsw@1071: // on error, jump to label "mldap_queryconn_error2", as ldap_unbind_ext_s() must be called: bsw@1071: if (ldap_error != LDAP_SUCCESS) goto mldap_queryconn_error2; bsw@1071: bsw@1071: // connect to LDAP server: bsw@1071: ldap_error = ldap_sasl_bind_s( bsw@1071: ldp, // pointer to opaque OpenLDAP structure representing the connection bsw@1071: who, // DN to bind as bsw@1071: LDAP_SASL_SIMPLE, // SASL mechanism "simple" for password authentication bsw@1071: &cred, // password as struct berval bsw@1071: NULL, // no server controls bsw@1071: NULL, // no client controls bsw@1071: NULL // do not store server credentials bsw@1071: ); bsw@1071: bsw@1071: // error handling: bsw@1071: if (ldap_error != LDAP_SUCCESS) { bsw@1071: bsw@1071: // error label to jump to, if a call of ldap_unbind_ext_s() is required: bsw@1071: mldap_queryconn_error2: bsw@1071: bsw@1071: // close connection and free resources: bsw@1071: ldap_unbind_ext_s(ldp, NULL, NULL); bsw@1071: bsw@1071: // error label to jump to, if no call of ldap_unbind_ext_s() is required: bsw@1071: mldap_queryconn_error1: bsw@1071: bsw@1071: // return three values: bsw@1071: lua_pushnil(L); // return nil as first value bsw@1071: lua_pushstring(L, ldap_err2string(ldap_error)); // return error string as second value bsw@1071: lua_pushinteger(L, ldap_error); // return error code (integer) as third value bsw@1071: return 3; bsw@1071: bsw@1071: } bsw@1071: bsw@1071: // create new Lua userdata object (that will contain the 'ldp' pointer) on top of stack: bsw@1071: ldp_ptr = lua_newuserdata(L, sizeof(LDAP *)); bsw@1071: bsw@1071: // set metatable of Lua userdata object: bsw@1071: luaL_setmetatable(L, MLDAP_REGKEY "connection_metatable"); bsw@1071: bsw@1071: // write contents of Lua userdata object (the 'ldp' pointer): bsw@1071: *ldp_ptr = ldp; bsw@1071: bsw@1071: // return Lua userdata object from top of stack: bsw@1071: return 1; bsw@1071: bsw@1071: } bsw@1071: bsw@1071: bsw@1071: static int mldap_search(lua_State *L) { bsw@1071: // Lua C function used as "search" method of Lua userdata object representing the LDAP connection bsw@1071: // that takes a Lua userdata object (the LDAP connection) as first argument, bsw@1071: // a table with named arguments as second argument, bsw@1071: // and returns a result table on success (see below) bsw@1071: // or returns nil, an error string, and an error code (integer) on error bsw@1071: bsw@1071: // named arguments: bsw@1071: // "base" (string) DN of the entry at which to start the search bsw@1071: // "scope" (string) scope of the search, one of: bsw@1071: // "base" to search the object itself bsw@1071: // "onelevel" to search the object's immediate children bsw@1071: // "subtree" to search the object and all its descendants bsw@1071: // "children" to search all of the descendants bsw@1071: // "filter" (string) string representation of the filter to apply in the search bsw@1071: // (conforming to RFC 4515 as extended in RFC 4526) bsw@1071: // "attrs" (table) list of attribute descriptions (each a string) to return from matching entries bsw@1071: bsw@1071: // structure of result table: bsw@1071: // { bsw@1071: // { dn = , bsw@1071: // = { , , ... }, bsw@1071: // = { , , ... }, bsw@1071: // ... bsw@1071: // }, bsw@1071: // { dn = , bsw@1071: // = { , , ... }, bsw@1071: // = { , , ... }, bsw@1071: // ... bsw@1071: // }, bsw@1071: // ... bsw@1071: // } bsw@1071: bsw@1071: const char *base; // C string for "base" argument bsw@1071: const char *scope_string; // C string for "scope" argument bsw@1071: int scope; // integer representing the scope bsw@1071: const char *filter; // C string for "filter" argument bsw@1071: size_t nattrs; // number of attributes in "attrs" argument bsw@1071: char **attrs; // C string array of "attrs" argument bsw@1071: size_t attr_idx; // index variable for building the C string array of "attrs" bsw@1071: int ldap_error; // LDAP error code (as returned by libldap calls) bsw@1071: LDAP **ldp_ptr; // pointer to a pointer to the OpenLDAP structure representing the connection bsw@1071: LDAPMessage *res; // pointer to the result of ldap_search_ext_s() call bsw@1071: LDAPMessage *ent; // pointer to an entry in the result of ldap_search_ext_s() call bsw@1071: int i; // integer to fill the Lua table returned as result bsw@1071: bsw@1071: // truncate the Lua stack to 2 elements: bsw@1071: lua_settop(L, 2); bsw@1071: bsw@1071: // check if the first argument is a Lua userdata object with the correct metatable bsw@1071: // and get a C pointer to that userdata object: bsw@1071: ldp_ptr = luaL_checkudata(L, 1, MLDAP_REGKEY "connection_metatable"); bsw@1071: bsw@1071: // throw an error, if the connection has already been closed: bsw@1071: if (!*ldp_ptr) { bsw@1071: return luaL_error(L, "LDAP connection has already been closed"); bsw@1071: } bsw@1071: bsw@1071: // check if the second argument is a table, and throw an error if it's not a table: bsw@1071: if (lua_type(L, 2) != LUA_TTABLE) { bsw@1071: luaL_error(L, "Argument to function 'bind' is not a table."); bsw@1071: } bsw@1071: bsw@1071: // extract named arguments (requires memory allocation for 'attrs'): bsw@1071: base = mldap_get_named_string_arg(L, 2, "base", true); // pushed to 3 bsw@1071: scope_string = mldap_get_named_string_arg(L, 2, "scope", true); // pushed to 4 bsw@1071: scope = mldap_scope(L, scope_string); bsw@1071: lua_pop(L, 1); // removes stack element 4 bsw@1071: filter = mldap_get_named_string_arg(L, 2, "filter", false); // pushed to 4 bsw@1071: lua_getfield(L, 2, "attrs"); // pushed to 5 bsw@1071: nattrs = lua_len(L, -1); bsw@1071: attrs = calloc(nattrs + 1, sizeof(char *)); // memory allocation, +1 for terminating NULL bsw@1071: if (!attrs) return luaL_error(L, "Memory allocation error in C function 'mldap_queryconn'"); bsw@1071: for (attr_idx=0; attr_idxbv_val, (*val)->bv_len); bsw@1071: bsw@1071: // pop value from Lua stack position 5 bsw@1071: // and store it in table on Lua stack position 4: bsw@1071: lua_rawseti(L, 4, j); bsw@1071: bsw@1071: } bsw@1071: bsw@1071: // free data structure of values: bsw@1071: ldap_value_free_len(vals); bsw@1071: bsw@1071: // pop attribute name from Lua stack position 3 bsw@1071: // and pop value table from Lua stack position 4 bsw@1071: // and store them in result table for entry at Lua stack position 2: bsw@1071: lua_settable(L, 2); bsw@1071: bsw@1071: } bsw@1071: bsw@1071: // free 'BerElement *ber' stucture that has been used to iterate through all attributes bsw@1071: // (second argument is zero due to manpage of ldap_first_attribute()): bsw@1071: ber_free(ber, 0); bsw@1071: bsw@1071: // store distinguished name (DN) on Lua stack position 3 bsw@1071: // (aquired memory is free'd with ldap_memfree()): bsw@1071: dn = ldap_get_dn(*ldp_ptr, ent); bsw@1071: lua_pushstring(L, dn); bsw@1071: ldap_memfree(dn); bsw@1071: bsw@1071: // pop distinguished name (DN) from Lua stack position 3 bsw@1071: // and store it in field "dn" of entry result table at stack position 2 bsw@1071: lua_setfield(L, 2, "dn"); bsw@1071: bsw@1071: // pop entry result table from Lua stack position 2 bsw@1071: // and store it in table at stack position 1: bsw@1071: lua_rawseti(L, 1, i); bsw@1071: bsw@1071: } bsw@1071: bsw@1071: // return result table from top of Lua stack (stack position 1): bsw@1071: return 1; bsw@1071: bsw@1071: } bsw@1071: bsw@1071: static int mldap_unbind(lua_State *L) { bsw@1071: // Lua C function used as "unbind" function of module and "unbind" method of Lua userdata object bsw@1071: // closing the LDAP connection (if still open) bsw@1071: // returning nothing bsw@1071: bsw@1071: LDAP **ldp_ptr; // pointer to a pointer to the OpenLDAP structure representing the connection bsw@1071: bsw@1071: // check if the first argument is a Lua userdata object with the correct metatable bsw@1071: // and get a C pointer to that userdata object: bsw@1071: ldp_ptr = luaL_checkudata(L, 1, MLDAP_REGKEY "connection_metatable"); bsw@1071: bsw@1071: // check if the Lua userdata object still contains a pointer: bsw@1071: if (*ldp_ptr) { bsw@1071: bsw@1071: // if it does, then call ldap_unbind_ext_s(): bsw@1071: ldap_unbind_ext_s( bsw@1071: *ldp_ptr, // pointer to the opaque OpenLDAP structure representing the connection bsw@1071: NULL, // no server controls bsw@1071: NULL // no client controls bsw@1071: ); bsw@1071: bsw@1071: // store NULL pointer in Lua userdata to mark connection as closed bsw@1071: *ldp_ptr = NULL; bsw@1071: } bsw@1071: bsw@1071: // returning nothing: bsw@1071: return 0; bsw@1071: bsw@1071: } bsw@1071: bsw@1071: bsw@1071: // registration information for library functions: bsw@1071: static const struct luaL_Reg mldap_module_functions[] = { bsw@1071: {"bind", mldap_bind}, bsw@1071: {"unbind", mldap_unbind}, bsw@1071: {NULL, NULL} bsw@1071: }; bsw@1071: bsw@1071: bsw@1071: // registration information for methods of connection object: bsw@1071: static const struct luaL_Reg mldap_connection_methods[] = { bsw@1071: {"search", mldap_search}, bsw@1071: {"unbind", mldap_unbind}, bsw@1071: {NULL, NULL} bsw@1071: }; bsw@1071: bsw@1071: bsw@1071: // registration information for connection metatable: bsw@1071: static const struct luaL_Reg mldap_connection_metamethods[] = { bsw@1071: {"__gc", mldap_unbind}, bsw@1071: {NULL, NULL} bsw@1071: }; bsw@1071: bsw@1071: bsw@1071: // luaopen function to initialize/register library: bsw@1071: int luaopen_mldap(lua_State *L) { bsw@1071: bsw@1071: // clear Lua stack: bsw@1071: lua_settop(L, 0); bsw@1071: bsw@1071: // create table with library functions on Lua stack position 1: bsw@1071: luaL_newlib(L, mldap_module_functions); bsw@1071: bsw@1071: // create metatable for connection objects on Lua stack position 2: bsw@1071: luaL_newlib(L, mldap_connection_metamethods); bsw@1071: bsw@1071: // create table with methods for connection object on Lua stack position 3: bsw@1071: luaL_newlib(L, mldap_connection_methods); bsw@1071: bsw@1071: // pop table with methods for connection object from Lua stack position 3 bsw@1071: // and store it as "__index" in metatable: bsw@1071: lua_setfield(L, 2, "__index"); bsw@1071: bsw@1071: // pop table with metatable for connection objects from Lua stack position 2 bsw@1071: // and store it in the Lua registry: bsw@1071: lua_setfield(L, LUA_REGISTRYINDEX, MLDAP_REGKEY "connection_metatable"); bsw@1071: bsw@1071: // create table for error code mappings on Lua stack position 2: bsw@1071: lua_newtable(L); bsw@1071: bsw@1071: // fill table for error code mappings bsw@1071: // that maps integer error codes to error code strings bsw@1071: // and vice versa: bsw@1071: mldap_set_errorcodes(L); bsw@1071: bsw@1071: // pop table for error code mappings from Lua stack position 2 bsw@1071: // and store it as "errorcodes" in table with library functions: bsw@1071: lua_setfield(L, 1, "errorcodes"); bsw@1071: bsw@1071: // return table with library functions from top of Lua stack: bsw@1071: return 1; bsw@1071: bsw@1071: }