liquid_feedback_frontend
view lib/mldap/mldap.c @ 1797:cdb2e4e28d86
Removed string 'hint'
| author | bsw | 
|---|---|
| date | Thu Oct 21 14:49:53 2021 +0200 (2021-10-21) | 
| parents | ab837b075cf7 | 
| children | 
 line source
     1 /*
     2  * minimalistic Lua LDAP interface library
     3  *
     4  * The library does not set any global variable itself and must thus be
     5  * loaded with
     6  *
     7  *     mldap = require("mldap")
     8  *
     9  * or a similar statement.
    10  *
    11  * The library provides two functions, conn = bind{...} and unbind(conn)
    12  * to connect to and disconnect from the LDAP server respectively. The
    13  * unbind function is also provided as a method of the connection userdata
    14  * object (see below).
    15  *
    16  * The arguments to the bind{...} function are documented in the source code
    17  * (see C function mldap_bind). In case of success, the bind function returns
    18  * a userdata object that provides two methods: query{...} and unbind(). In
    19  * case of error, the bind function returns nil as first return value, an
    20  * error string as second return value, and a numeric error code as third
    21  * return value. A positive error code is an LDAP resultCode, a negative
    22  * error code is an OpenLDAP API error code:
    23  *
    24  *     connection, error_string, numeric_error_code = mldap.bind{...}
    25  *
    26  * For translating numeric error codes to an identifying (machine readable)
    27  * string identifier, the library provides in addition to the two functions
    28  * a table named 'errorcodes', for example:
    29  *
    30  *     49 == mldap.errorcodes["invalid_credentials"]
    31  *
    32  * and
    33  *
    34  *     mldap.errorcodes[49] == "invalid_credentials"
    35  *
    36  * The arguments and return value of the query{...} method of the connection
    37  * userdata object are also documented in the source code below (see
    38  * C function mldap_query). Error conditions are reported the same way as the
    39  * bind{...} function does.
    40  *
    41  * To close the connection, either the unbind() function of the library or
    42  * the unbind() method can be called; it is allowed to call them multiple
    43  * times, and they are also invoked by the garbage collector.
    44  *
    45  */
    47 // Lua header inclusions:
    48 #include <lua.h>
    49 #include <lauxlib.h>
    51 // OpenLDAP header inclusions:
    52 #include <ldap.h>
    54 // Standard C inclusions:
    55 #include <stdlib.h>
    56 #include <stdbool.h>
    57 #include <unistd.h>
    59 // Error code translation is included from separate C file:
    60 #include "mldap_errorcodes.c"
    62 // Provide compatibility with Lua 5.1:
    63 #if LUA_VERSION_NUM < 502
    64 #define luaL_newlib(L, t) lua_newtable((L)); luaL_register(L, NULL, t)
    65 #define lua_rawlen lua_objlen
    66 #define lua_len lua_objlen
    67 #define luaL_setmetatable(L, regkey) \
    68   lua_getfield((L), LUA_REGISTRYINDEX, (regkey)); \
    69   lua_setmetatable((L), -2);
    70 #endif
    72 // prefix for all Lua registry entries of this library:
    73 #define MLDAP_REGKEY "556aeaf3c864af2e_mldap_"
    76 static const char *mldap_get_named_string_arg(
    77   // gets a named argument of type "string" from a table at the given stack position
    79   lua_State *L,         // pointer to lua_State variable
    80   int idx,              // stack index of the table containing the named arguments
    81   const char *argname,  // name of the argument
    82   bool mandatory        // if not 0, then the argument is mandatory and an error is raised if it isn't found
    84   // leaves the string as new element on top of the stack
    85 ) {
    87   // pushes the table entry with the given argument name on top of the stack:
    88   lua_getfield(L, idx, argname);
    90   // check, if the entry is nil or false:
    91   if (!lua_toboolean(L, -1)) {
    93     // throw error, if named argument is mandatory:
    94     if (mandatory) return luaL_error(L, "Named argument '%s' missing", argname), NULL;
    96     // return NULL pointer, if named argument is not mandatory:
    97     return NULL;
    99   }
   101   // throw error, if the value of the argument is not string:
   102   if (lua_type(L, -1) != LUA_TSTRING) return luaL_error(L, "Named argument '%s' is not a string", argname), NULL;
   104   // return a pointer to the string, leaving the string on top of the stack to avoid garbage collection:
   105   return lua_tostring(L, -1);
   107   // leaves one element on the stack
   108 }
   111 static int mldap_get_named_number_arg(
   112   // gets a named argument of type "number" from a table at the given stack position
   114   lua_State *L,             // pointer to lua_State variable
   115   int idx,                  // stack index of the table containing the named arguments
   116   const char *argname,      // name of the argument
   117   bool mandatory,           // if not 0, then the argument is mandatory and an error is raised if it isn't found
   118   lua_Number default_value  // default value to return, if the argument is not mandatory and nil
   120   // opposed to 'mldap_get_named_string_arg', this function leaves no element on the stack
   121 ) {
   123   lua_Number value;  // value to return
   125   // pushes the table entry with the given argument name on top of the stack:
   126   lua_getfield(L, idx, argname);
   128   // check, if the entry is nil or false:
   129   if (lua_isnil(L, -1)) {
   131     // throw error, if named argument is mandatory:
   132     if (mandatory) return luaL_error(L, "Named argument '%s' missing", argname), 0;
   134     // set default value as return value, if named argument is not mandatory:
   135     value = default_value;
   137   } else {
   139     // throw error, if the value of the argument is not a number:
   140     if (lua_type(L, -1) != LUA_TNUMBER) return luaL_error(L, "Named argument '%s' is not a number", argname), 0;
   142     // set return value to the number:
   143     value = lua_tonumber(L, -1);
   145   }
   147   // remove unnecessary element from stack (not needed to avoid garbage collection):
   148   lua_pop(L, 1);
   150   return value;
   152   // leaves no new elements on the stack
   153 }
   156 static bool mldap_get_named_boolean_arg(
   157   // gets a named argument of type "boolean" from a table at the given stack position
   159   lua_State *L,             // pointer to lua_State variable
   160   int idx,                  // stack index of the table containing the named arguments
   161   const char *argname,      // name of the argument
   162   bool mandatory,           // if not 0, then the argument is mandatory and an error is raised if it isn't found
   163   bool default_value        // default value to return, if the argument is not mandatory and nil
   165   // opposed to 'mldap_get_named_string_arg', this function leaves no element on the stack
   166 ) {
   168   bool value;  // value to return
   170   // pushes the table entry with the given argument name on top of the stack:
   171   lua_getfield(L, idx, argname);
   173   // check, if the entry is nil:
   174   if (lua_isnil(L, -1)) {
   176     // throw error, if named argument is mandatory:
   177     if (mandatory) return luaL_error(L, "Named argument '%s' missing", argname), 0;
   179     // set default value as return value, if named argument is not mandatory:
   180     value = default_value;
   182   } else {
   184     // throw error, if the value of the argument is not a number:
   185     if (lua_type(L, -1) != LUA_TBOOLEAN) return luaL_error(L, "Named argument '%s' is not a boolean", argname), 0;
   187     // set return value to the number:
   188     value = lua_toboolean(L, -1);
   190   }
   192   // remove unnecessary element from stack (not needed to avoid garbage collection):
   193   lua_pop(L, 1);
   195   return value;
   197   // leaves no new elements on the stack
   198 }
   201 static int mldap_scope(
   202   // converts a string ("base", "onelevel", "subtree", "children") to an integer representing the LDAP scope
   203   // and throws an error for any unknown string
   205   lua_State *L,             // pointer to lua_State variable (needed to throw errors)
   206   const char *scope_string  // string that is either ("base", "onelevel", "subtree", "children")
   208   // does not affect or use the Lua stack at all
   209 ) {
   211   // return integer according to string value:
   212   if (!strcmp(scope_string, "base"))     return LDAP_SCOPE_BASE;
   213   if (!strcmp(scope_string, "onelevel")) return LDAP_SCOPE_ONELEVEL;
   214   if (!strcmp(scope_string, "subtree"))  return LDAP_SCOPE_SUBTREE;
   215   if (!strcmp(scope_string, "children")) return LDAP_SCOPE_CHILDREN;
   217   // throw error for unknown string values:
   218   return luaL_error(L, "Invalid LDAP scope: '%s'", scope_string), 0;
   220 }
   223 static int mldap_bind(lua_State *L) {
   224   // Lua C function that takes named arguments as a table
   225   // and returns a userdata object, representing the LDAP connection
   226   // or returns nil, an error string, and an error code (integer) on error
   228   // named arguments:
   229   // "uri"      (string)  server URI to connect to
   230   // "who"      (string)  DN to bind as
   231   // "password" (string)  password for DN to bind as
   232   // "timeout"  (number)  timeout in seconds
   233   // "tls"      (boolean) use TLS
   235   static const int ldap_version = LDAP_VERSION3;  // providing a pointer (&ldap_version) to set LDAP protocol version 3
   236   const char *uri;           // C string for "uri" argument
   237   bool tls;                  // boolean indicating if TLS is to be used
   238   const char *who;           // C string for "who" argument
   239   struct berval cred;        // credentials ("password") are stored as struct berval
   240   lua_Number timeout_float;  // float (lua_Number) for timeout
   241   struct timeval timeout;    // timeout is stored as struct timeval
   242   int ldap_error;            // LDAP error code (as returned by libldap calls)
   243   LDAP *ldp;                 // pointer to an opaque OpenLDAP structure representing the connection
   244   LDAP **ldp_ptr;            // pointer to a Lua userdata structure (that only contains the 'ldp' pointer)
   246   // throw error if first argument is not a table:
   247   if (lua_type(L, 1) != LUA_TTABLE) {
   248     luaL_error(L, "Argument to function 'bind' is not a table.");
   249   }
   251   // extract arguments:
   252   uri = mldap_get_named_string_arg(L, 1, "uri", true);
   253   tls = mldap_get_named_boolean_arg(L, 1, "tls", false, false);
   254   who = mldap_get_named_string_arg(L, 1, "who", false);
   255   cred.bv_val = (char *)mldap_get_named_string_arg(L, 1, "password", false);
   256   // use (char *) cast to suppress compiler warning (should be const anyway)
   257   if (cred.bv_val) cred.bv_len = strlen(cred.bv_val);
   258   else cred.bv_len = 0;
   259   timeout_float = mldap_get_named_number_arg(L, 1, "timeout", false, -1);
   260   timeout.tv_sec = timeout_float;
   261   timeout.tv_usec = (timeout_float - timeout.tv_sec) * 1000000;
   263   // initialize OpenLDAP structure and provide URI for connection:
   264   ldap_error = ldap_initialize(&ldp, uri);
   265   // on error, jump to label "mldap_queryconn_error1", as no ldap_unbind_ext_s() is needed:
   266   if (ldap_error != LDAP_SUCCESS) goto mldap_queryconn_error1;
   268   // set LDAP protocol version 3:
   269   ldap_error = ldap_set_option(ldp, LDAP_OPT_PROTOCOL_VERSION, &ldap_version);
   270   // on error, jump to label "mldap_queryconn_error2", as ldap_unbind_ext_s() must be called:
   271   if (ldap_error != LDAP_SUCCESS) goto mldap_queryconn_error2;
   273   // set timeout for asynchronous OpenLDAP library calls:
   274   ldap_error = ldap_set_option(ldp, LDAP_OPT_TIMEOUT, &timeout);
   275   // on error, jump to label "mldap_queryconn_error2", as ldap_unbind_ext_s() must be called:
   276   if (ldap_error != LDAP_SUCCESS) goto mldap_queryconn_error2;
   278   // initiate TLS if requested
   279   if (tls) {
   280     ldap_error = ldap_start_tls_s(ldp, NULL, NULL);  
   281     if (ldap_error != LDAP_SUCCESS) goto mldap_queryconn_error2;
   282   }
   284   // connect to LDAP server:
   285   ldap_error = ldap_sasl_bind_s(
   286     ldp,               // pointer to opaque OpenLDAP structure representing the connection
   287     who,               // DN to bind as
   288     LDAP_SASL_SIMPLE,  // SASL mechanism "simple" for password authentication
   289     &cred,             // password as struct berval
   290     NULL,              // no server controls
   291     NULL,              // no client controls
   292     NULL               // do not store server credentials
   293   );
   295   // error handling:
   296   if (ldap_error != LDAP_SUCCESS) {
   298     // error label to jump to, if a call of ldap_unbind_ext_s() is required:
   299     mldap_queryconn_error2:
   301     // close connection and free resources:
   302     ldap_unbind_ext_s(ldp, NULL, NULL);
   304     // error label to jump to, if no call of ldap_unbind_ext_s() is required:
   305     mldap_queryconn_error1:
   307     // return three values:
   308     lua_pushnil(L);                                  // return nil as first value
   309     lua_pushstring(L, ldap_err2string(ldap_error));  // return error string as second value
   310     lua_pushinteger(L, ldap_error);                  // return error code (integer) as third value
   311     return 3;
   313   }
   315   // create new Lua userdata object (that will contain the 'ldp' pointer) on top of stack:
   316   ldp_ptr = lua_newuserdata(L, sizeof(LDAP *));
   318   // set metatable of Lua userdata object:
   319   luaL_setmetatable(L, MLDAP_REGKEY "connection_metatable");
   321   // write contents of Lua userdata object (the 'ldp' pointer):
   322   *ldp_ptr = ldp;
   324   // return Lua userdata object from top of stack:
   325   return 1;
   327 }
   330 static int mldap_search(lua_State *L) {
   331   // Lua C function used as "search" method of Lua userdata object representing the LDAP connection
   332   // that takes a Lua userdata object (the LDAP connection) as first argument,
   333   // a table with named arguments as second argument,
   334   // and returns a result table on success (see below)
   335   // or returns nil, an error string, and an error code (integer) on error
   337   // named arguments:
   338   // "base"  (string)   DN of the entry at which to start the search
   339   // "scope" (string)   scope of the search, one of:
   340   //                      "base" to search the object itself
   341   //                      "onelevel" to search the object's immediate children
   342   //                      "subtree" to search the object and all its descendants
   343   //                      "children" to search all of the descendants
   344   // "filter" (string)  string representation of the filter to apply in the search
   345   //                    (conforming to RFC 4515 as extended in RFC 4526)
   346   // "attrs"  (table)   list of attribute descriptions (each a string) to return from matching entries
   348   // structure of result table:
   349   // {
   350   //   { dn      = <distinguished name (DN)>,
   351   //     <attr1> = { <value1>, <value2>, ... },
   352   //     <attr2> = { <value1>, <value2>, ... },
   353   //     ...
   354   //   },
   355   //   { dn      = <distinguished name (DN)>,
   356   //     <attr1> = { <value1>, <value2>, ... },
   357   //     <attr2> = { <value1>, <value2>, ... },
   358   //     ...
   359   //   },
   360   //   ...
   361   // }
   363   const char *base;          // C string for "base" argument
   364   const char *scope_string;  // C string for "scope" argument
   365   int scope;                 // integer representing the scope
   366   const char *filter;        // C string for "filter" argument
   367   size_t nattrs;             // number of attributes in "attrs" argument
   368   const char **attrs;        // C string array of "attrs" argument
   369   size_t attr_idx;           // index variable for building the C string array of "attrs"
   370   int ldap_error;            // LDAP error code (as returned by libldap calls)
   371   LDAP **ldp_ptr;            // pointer to a pointer to the OpenLDAP structure representing the connection
   372   LDAPMessage *res;          // pointer to the result of ldap_search_ext_s() call
   373   LDAPMessage *ent;          // pointer to an entry in the result of ldap_search_ext_s() call
   374   int i;                     // integer to fill the Lua table returned as result
   376   // truncate the Lua stack to 2 elements:
   377   lua_settop(L, 2);
   379   // check if the first argument is a Lua userdata object with the correct metatable
   380   // and get a C pointer to that userdata object:
   381   ldp_ptr = luaL_checkudata(L, 1, MLDAP_REGKEY "connection_metatable");
   383   // throw an error, if the connection has already been closed:
   384   if (!*ldp_ptr) {
   385     return luaL_error(L, "LDAP connection has already been closed");
   386   }
   388   // check if the second argument is a table, and throw an error if it's not a table:
   389   if (lua_type(L, 2) != LUA_TTABLE) {
   390     luaL_error(L, "Argument to function 'bind' is not a table.");
   391   }
   393   // extract named arguments (requires memory allocation for 'attrs'):
   394   base = mldap_get_named_string_arg(L, 2, "base", true);  // pushed to 3
   395   scope_string = mldap_get_named_string_arg(L, 2, "scope", true);  // pushed to 4
   396   scope = mldap_scope(L, scope_string);
   397   lua_pop(L, 1);  // removes stack element 4
   398   filter = mldap_get_named_string_arg(L, 2, "filter", false);  // pushed to 4
   399   lua_getfield(L, 2, "attrs");  // pushed to 5
   400   nattrs = luaL_len(L, -1);
   401   attrs = calloc(nattrs + 1, sizeof(char *));  // memory allocation, +1 for terminating NULL
   402   if (!attrs) return luaL_error(L, "Memory allocation error in C function 'mldap_queryconn'");
   403   for (attr_idx=0; attr_idx<nattrs; attr_idx++) {
   404     lua_pushinteger(L, attr_idx+1);
   405     lua_gettable(L, 5);  // pushed onto variable stack position
   406     if (lua_type(L, -1) != LUA_TSTRING) {
   407       free(attrs);
   408       return luaL_error(L, "Element in attribute list is not a string");
   409     }
   410     attrs[attr_idx] = lua_tostring(L, -1);
   411   }
   412   // attrs[nattrs] = NULL;  // unnecessary due to calloc
   414   // execute LDAP search and store pointer to the result in 'res':
   415   ldap_error = ldap_search_ext_s(
   416     *ldp_ptr,  // pointer to the opaque OpenLDAP structure representing the connection
   417     base,      // DN of the entry at which to start the search
   418     scope,     // scope of the search
   419     filter,    // string representation of the filter to apply in the search
   420     (char **)attrs,  // array of attribute descriptions (array of strings)
   421                // cast to suppress compiler warning (should be const anyway)
   422     0,         // do not only request attribute descriptions but also values
   423     NULL,      // no server controls
   424     NULL,      // no client controls
   425     NULL,      // do not set another timeout
   426     0,         // no sizelimit
   427     &res       // store result in 'res'
   428   );
   430   // free data structures that have been allocated for the 'attrs' array:
   431   free(attrs);
   433   // return nil, an error string, and an error code (integer) in case of error:
   434   if (ldap_error != LDAP_SUCCESS) {
   435     lua_pushnil(L);
   436     lua_pushstring(L, ldap_err2string(ldap_error));
   437     lua_pushinteger(L, ldap_error);
   438     return 3;
   439   }
   441   // clear Lua stack:
   442   lua_settop(L, 0);
   444   // create result table for all entries at stack position 1:
   445   lua_newtable(L);
   447   // use ldap_first_entry() and ldap_next_entry() functions
   448   // to iterate through all entries in the result 'res'
   449   // and count 'i' from 1 up:
   450   for (
   451     ent=ldap_first_entry(*ldp_ptr, res), i=1;
   452     ent;
   453     ent=ldap_next_entry(*ldp_ptr, ent), i++
   454   ) {
   456     char *attr;       // name of attribute
   457     BerElement *ber;  // structure to iterate through attributes
   458     char *dn;         // LDAP entry name (distinguished name)
   460     // create result table for one entry at stack position 2:
   461     lua_newtable(L);
   463     // use ldap_first_attribute() and ldap_next_attribute()
   464     // as well as 'BerElement *ber' to iterate through all attributes
   465     // ('ber' must be free'd with ber_free(ber, 0) call later):
   466     for (
   467       attr=ldap_first_attribute(*ldp_ptr, ent, &ber);
   468       attr;
   469       attr=ldap_next_attribute(*ldp_ptr, ent, ber)
   470     ) {
   472       struct berval **vals;  // pointer to (first element of) array of values
   473       struct berval **val;   // pointer to a value represented as 'struct berval' structure
   474       int j;                 // integer to fill a Lua table
   476       // push the attribute name to Lua stack position 3:
   477       lua_pushstring(L, attr);
   479       // create a new table for the attribute's values on Lua stack position 4:
   480       lua_newtable(L);
   482       // call ldap_get_values_len() to obtain the values
   483       // (required to be free'd later with ldap_value_free_len()):
   484       vals = ldap_get_values_len(*ldp_ptr, ent, attr);
   486       // iterate through values and count 'j' from 1 up:
   487       for (val=vals, j=1; *val; val++, j++) {
   489         // push value to Lua stack position 5:
   490         lua_pushlstring(L, (*val)->bv_val, (*val)->bv_len);
   492         // pop value from Lua stack position 5
   493         // and store it in table on Lua stack position 4:
   494         lua_rawseti(L, 4, j);
   496       }
   498       // free data structure of values:
   499       ldap_value_free_len(vals);
   501       // pop attribute name from Lua stack position 3
   502       // and pop value table from Lua stack position 4
   503       // and store them in result table for entry at Lua stack position 2:
   504       lua_settable(L, 2);
   506     }
   508     // free 'BerElement *ber' stucture that has been used to iterate through all attributes
   509     // (second argument is zero due to manpage of ldap_first_attribute()):
   510     ber_free(ber, 0);
   512     // store distinguished name (DN) on Lua stack position 3
   513     // (aquired memory is free'd with ldap_memfree()):
   514     dn = ldap_get_dn(*ldp_ptr, ent);
   515     lua_pushstring(L, dn);
   516     ldap_memfree(dn);
   518     // pop distinguished name (DN) from Lua stack position 3
   519     // and store it in field "dn" of entry result table at stack position 2
   520     lua_setfield(L, 2, "dn");
   522     // pop entry result table from Lua stack position 2
   523     // and store it in table at stack position 1:
   524     lua_rawseti(L, 1, i);
   526   }
   528   // return result table from top of Lua stack (stack position 1):
   529   return 1;
   531 }
   533 static int mldap_unbind(lua_State *L) {
   534   // Lua C function used as "unbind" function of module and "unbind" method of Lua userdata object
   535   // closing the LDAP connection (if still open)
   536   // returning nothing
   538   LDAP **ldp_ptr;  // pointer to a pointer to the OpenLDAP structure representing the connection
   540   // check if the first argument is a Lua userdata object with the correct metatable
   541   // and get a C pointer to that userdata object:
   542   ldp_ptr = luaL_checkudata(L, 1, MLDAP_REGKEY "connection_metatable");
   544   // check if the Lua userdata object still contains a pointer:
   545   if (*ldp_ptr) {
   547     // if it does, then call ldap_unbind_ext_s():
   548     ldap_unbind_ext_s(
   549       *ldp_ptr,  // pointer to the opaque OpenLDAP structure representing the connection
   550       NULL,      // no server controls
   551       NULL       // no client controls
   552     );
   554     // store NULL pointer in Lua userdata to mark connection as closed
   555     *ldp_ptr = NULL;
   556   }
   558   // returning nothing:
   559   return 0;
   561 }
   564 // registration information for library functions:
   565 static const struct luaL_Reg mldap_module_functions[] = {
   566   {"bind", mldap_bind},
   567   {"unbind", mldap_unbind},
   568   {NULL, NULL}
   569 };
   572 // registration information for methods of connection object: 
   573 static const struct luaL_Reg mldap_connection_methods[] = {
   574   {"search", mldap_search},
   575   {"unbind", mldap_unbind},
   576   {NULL, NULL}
   577 };
   580 // registration information for connection metatable: 
   581 static const struct luaL_Reg mldap_connection_metamethods[] = {
   582   {"__gc", mldap_unbind},
   583   {NULL, NULL}
   584 };
   587 // luaopen function to initialize/register library:
   588 int luaopen_mldap(lua_State *L) {
   590   // clear Lua stack:
   591   lua_settop(L, 0);
   593   // create table with library functions on Lua stack position 1:
   594   luaL_newlib(L, mldap_module_functions);
   596   // create metatable for connection objects on Lua stack position 2:
   597   luaL_newlib(L, mldap_connection_metamethods);
   599   // create table with methods for connection object on Lua stack position 3:
   600   luaL_newlib(L, mldap_connection_methods);
   602   // pop table with methods for connection object from Lua stack position 3
   603   // and store it as "__index" in metatable:
   604   lua_setfield(L, 2, "__index");
   606   // pop table with metatable for connection objects from Lua stack position 2
   607   // and store it in the Lua registry:
   608   lua_setfield(L, LUA_REGISTRYINDEX, MLDAP_REGKEY "connection_metatable");
   610   // create table for error code mappings on Lua stack position 2:
   611   lua_newtable(L);
   613   // fill table for error code mappings
   614   // that maps integer error codes to error code strings
   615   // and vice versa:
   616   mldap_set_errorcodes(L);
   618   // pop table for error code mappings from Lua stack position 2
   619   // and store it as "errorcodes" in table with library functions:
   620   lua_setfield(L, 1, "errorcodes");
   622   // return table with library functions from top of Lua stack:
   623   return 1;
   625 }
