diff --git a/CMakeLists.txt b/CMakeLists.txt index 855d9e3..058aaee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,68 @@ pkg_search_module ( ) +# +# Version Information +# + +find_package (Git) + +exec_program ( + ${GIT_EXECUTABLE} + ARGS diff --quiet + RETURN_VALUE GIT_HAVE_CHANGES +) + +exec_program ( + ${GIT_EXECUTABLE} + ARGS describe --tags --match 'version-*.*-*' + OUTPUT_VARIABLE GIT_VERSION_INFO +) + +string ( + REGEX REPLACE "^version-([1-9][0-9]*|0)[.]([1-9][0-9]*|0)-(.*)$" + "\\1" + GIT_VERSION_MAJOR + ${GIT_VERSION_INFO} +) + +string ( + REGEX REPLACE "^version-([1-9][0-9]*|0)[.]([1-9][0-9]*|0)-(.*)$" + "\\2" + GIT_VERSION_MINOR + ${GIT_VERSION_INFO} +) + +if (GIT_HAVE_CHANGES EQUAL 0) + + string ( + REGEX REPLACE "^version-([1-9][0-9]*|0)[.]([1-9][0-9]*|0)-(.*)$" + "\\3" + GIT_VERSION_PATCHLEVEL + ${GIT_VERSION_INFO} + ) + + set ( + USER_SUPPLIED_PATCHLEVEL + "${GIT_VERSION_PATCHLEVEL}" + CACHE STRING "User-override for patch level under ${GIT_VERSION_MAJOR}.${GIT_VERSION_MINOR}" + ) + +else() + + exec_program ( + date + ARGS '+%Y%m%d-%H%M%S' + OUTPUT_VARIABLE GIT_CHANGES_TIMESTAMP + ) + set (GIT_VERSION_PATCHLEVEL "local-${GIT_CHANGES_TIMESTAMP}") + message (WARNING "Git reports local changes, fixing patch level to local-${GIT_CHANGES_TIMESTAMP}") + + unset (USER_SUPPLIED_PATCHLEVEL CACHE) + +endif() + + # # Building # @@ -98,10 +160,13 @@ add_custom_target ( SOURCES lib/msgop.gperf ) -add_dependencies (lillydapStatic msgop.tab openpa) -add_dependencies (lillydapShared msgop.tab openpa) +add_dependencies (lillydapStatic msgop.tab) +add_dependencies (lillydapShared msgop.tab) +if (NOT ${BUILD_SINGLE_THREADED}) + add_dependencies (lillydapStatic openpa) + add_dependencies (lillydapShared openpa) +endif() -#TODO# Dit komt met /usr/local onterecht ook in CPack terecht configure_file ( contrib/pkgconfig/lillydap.pc.in ${PROJECT_BINARY_DIR}/lillydap.pc @@ -116,7 +181,7 @@ configure_file ( enable_testing () add_executable ( - lillydump.test #TODO# EXCLUDE_FROM_ALL + lillydump.test test/lillydump.c lib/mem.c lib/derbuf.c @@ -135,30 +200,32 @@ target_link_libraries ( ${QuickDER_LDFLAGS} #TODO# Proper form, _LDFLAGS??? ) -add_executable ( - stampede.test #TODO# EXCLUDE_FROM_ALL - test/stampede.c - lib/mem.c - lib/queue.c - lib/sillymem.c -) -set_target_properties ( - stampede.test - PROPERTIES COMPILE_DEFINITIONS USE_SILLYMEM -) -#TODO# Really -pthread, literally?!? -if(THREADS_HAVE_PTHREAD_ARG) - target_compile_options(stampede.test "-pthread") +if (NOT ${BUILD_SINGLE_THREADED}) + add_executable ( + stampede.test + test/stampede.c + lib/mem.c + lib/queue.c + lib/sillymem.c + ) + set_target_properties ( + stampede.test + PROPERTIES COMPILE_DEFINITIONS USE_SILLYMEM + ) + #TODO# Really -pthread, literally?!? + if(THREADS_HAVE_PTHREAD_ARG) + target_compile_options(stampede.test "-pthread") + endif() + target_link_libraries ( + stampede.test + lillydapStatic + ${QuickDER_LDFLAGS} #TODO# Proper form, _LDFLAGS??? + ${CMAKE_THREAD_LIBS_INIT} -lpthread + ) endif() -target_link_libraries ( - stampede.test - lillydapStatic - ${QuickDER_LDFLAGS} #TODO# Proper form, _LDFLAGS??? - ${CMAKE_THREAD_LIBS_INIT} -lpthread -) add_executable ( - lillypass.test #TODO# EXCLUDE_FROM_ALL + lillypass.test test/lillypass.c lib/mem.c lib/derbuf.c @@ -190,15 +257,18 @@ foreach (netpkg ${netpkgs}) endforeach() #TODO# Test that all serials exist, in proper order, for each threadid -add_test ( - NAME stampede.test - COMMAND stampede.test 250 -) +if (NOT ${BUILD_SINGLE_THREADED}) + # This is a test of lock-free concurrency, useless when single-threaded + add_test ( + NAME stampede.test + COMMAND stampede.test 250 + ) +endif() #TODO# Test that the output matches expectations foreach (netpkg ${netpkgs}) get_filename_component (netpkgname ${netpkg} NAME) - foreach (level 0 1 2 3 4) + foreach (level 0 1 2) #TODO# 3 4 add_test ( NAME lillypass-level${level}-netpkg-${netpkgname} COMMAND lillypass.test ${level} ${netpkg} @@ -239,12 +309,21 @@ install ( # PACKAGING # +set (CPACK_PACKAGE_VERSION_MAJOR ${GIT_VERSION_MAJOR}) +set (CPACK_PACKAGE_VERSION_MINOR ${GIT_VERSION_MINOR}) +if (GIT_HAVE_CHANGES EQUAL 0) + set (CPACK_PACKAGE_VERSION_PATCH ${USER_SUPPLIED_PATCHLEVEL}) +else() + set (CPACK_PACKAGE_VERSION_PATCH "local-${GIT_CHANGES_TIMESTAMP}") +endif() + set (CPACK_BUNDLE_NAME "LillyDAP") set (CPACK_PACKAGE_CONTACT "Rick van Rein ") set (CPACK_PACKAGE_VENDOR "ARPA2.net") set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "LDAP devkit, turns LDAP operations into functions/callbacks") set (CPACK_PACKAGE_DESCRIPTION_FILE ${CMAKE_CURRENT_SOURCE_DIR}/README.MD) set (CPACK_GENERATOR DEB RPM) +set (CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}) include (CPack) diff --git a/INSTALL.MD b/INSTALL.MD new file mode 100644 index 0000000..e631a99 --- /dev/null +++ b/INSTALL.MD @@ -0,0 +1,86 @@ +# Installing LillyDAP + +> *You should be able to get started with LillyDAP quite easily.* + + +## Requirements + +Runtime requirements: + + * [Quick DER](http://github.com/vanrein/quick-der) + * We may add `libpcre` later, for optional attrtype syntax checking + +Building requirements: + + * C compiler (we test with `gcc`) + * [CMake](https://cmake.org) + +Packaging requirements: + + * `rpmbuild` to build an RPM package for RedHat, SuSe, Fedora + * `dpkg-buildpackage` to build a DEB package for Debian, Ubuntu, Mint + * `makensis` to build a Nullsoft Installer for Windows + +By default, both the generators `RPM` and `DEB` are used. +You can select what packages to provide by selecting a generator in the +CPack command, for instance + + cpack -G NSIS + + +## Configuration Choices + +We use atomic operations to achieve lock-free concurrency. If you are in a +single-threaded environment, you do not need those. You will be able to set +the `BUILD_SINGLE_THREADED` option to replace atomic operations with simpler +ones. You will break the lock-free concurrency model if you use +multi-threading on any such setups. + +The major and minor version will be derived from Git automatically. The +same goes for the patch level, which may however be locally overridden by +package makers if they so desire. The one exception is when Git has local +changes; in that case, the patch level must be supplied by the user; a +warning will be emitted from CMake and the string is set to `local-` and +a timestamp in `YYYYMMDD-hhmmss` notation. + +To make configuration changes, use either `ccmake` or `cmake-gui` instead +of `cmake` when building. A first pass gives you the option that you can +override. Or, to automate this choice, you could use + + cmake -D BUILD_SINGLE_THREADED:BOOL=ON .. + + +## Building, Testing and Packaging + +We advise out-of-source builds, even though we have a few items on our +TODO list that require preparations inside the code tree for now. + + cd lillydap + mkdir build + cd build + +In this build directory, configure using defaults, or use a variant as +described under configuration. You need to reference the source tree +relative to the build directory, so `..` after the preparation above, + + cmake .. + +Then proceed to building, + + make + +You can now run the package's tests, + + ctest + +Finally, you can create packages, using `DEB` and `RPM` generators by +default, using + + cpack + +We will work to integrate LillyDAP with the [MXE](http://mxe.cc) +cross-building environment for Windows. You will then be able to +cross-package using + + cpack -G NSIS + diff --git a/README.MD b/README.MD index e7a84b6..ca6e369 100644 --- a/README.MD +++ b/README.MD @@ -7,7 +7,7 @@ Most of today's developers focus solely on HTTP to carry their data definitions. This even happens when LDAP would be a better carrier, which -it usually is, due to much richer and finer-grained syntax and semantics. +it usually is because of its richer and finer-grained syntax and semantics. But this isn't just about standards; it's also about the technology implementing it. The most well-known open source directory is @@ -19,7 +19,7 @@ and storage-centric, though they add dynamicity options through mechanisms like [overlays](http://www.openldap.org/doc/admin24/overlays.html) and -[stored procedures with triggers](https://people.apache.org/~ersiner/ldapcon.2007/LDAPcon.2007_LDAP.Stored.Procedures.and.Triggers.in.ApacheDS_by.Ersin.Er_Paper.pdf) +[stored procedures with triggers](https://people.apache.org/~ersiner/ldapcon.2007/LDAPcon.2007_LDAP.Stored.Procedures.and.Triggers.in.ApacheDS_by.Ersin.Er_Paper.pdf). In all cases, dynamicity is a bit of an afterthought, storage is central and there is only limited facilitation for dynamicity. @@ -353,3 +353,12 @@ The reverse flow direction will normally not be used until an has asked for it, but as should be clear from this, Nginx holds good cards for formulating treatment of the Turn operation in its configuration files. +## Related Work + + * [ldapjs](http://ldapjs.org) + implements LDAP in JavaScript; it can use Node.JS to run as a server. + It can support middleware such as filters and proxies. + + * [SteamWorks Crank](http://steamworks.arpa2.net/crank.html) + makes LDAP content available as JSON structures over a RESTful API. + diff --git a/TODO b/TODO index 819f55a..ae15d20 100644 --- a/TODO +++ b/TODO @@ -9,7 +9,31 @@ - the data will always be needed anyway - generic handling: opcode table entry can list -* Consider regex-checking text fields with constraining syntax +* Support individual operations such as lillyput_BindRequest() + - a generic version goes into opswi.c, as it reverses lillyget_operation() + - let's name the generic version lillyput_generic_operation() + - in addition, there are wrapper functions lillyput_BindRequest() + - wrappers can come as static inline functions, but can you point to them? + - http://stackoverflow.com/questions/8885665/c-pointer-to-inline-function + - should test this at varying -O levels and on various compilers == trouble + - it is simple to provide additional non-inlined functions in the library + - this is cheap in both .so and .a flavours -- the latter with inidividual .o + +* Advance the testing framework + - check not only the exit code, but also the generated output + - see test/CMatch.c which starts off this idea + - wrap it in add_test() around lillydump.test and lillypass.test + +* Consider regex-checking attributes with constraining syntax + - Ragel would be interesting to use, but we don't need to intersperse code + - libprce is probably more compact; it is already present in Nginx + - Would be friendly as a syntax checking conveniency, in style of LillyDAP + - Parts of the fields could be harvested (e.g. labeledURI's two pieces) + - This is probably best as a separate wrapper API calling generic code + - Split into separate .o as much as possible, for compaction of .a * Have a dictionary API to manipulate controls; gperf may be used here too +* Support builds to be completely out-of-source + - Broken by the mapping from `.gperf` to `.tab` + - Broken by the imported `openpa` which is extracted and configured diff --git a/contrib/pkgconfig/lillydap.pc.in b/contrib/pkgconfig/lillydap.pc.in new file mode 100644 index 0000000..a9ac795 --- /dev/null +++ b/contrib/pkgconfig/lillydap.pc.in @@ -0,0 +1,11 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +includedir=${prefix}/include +libdir=${exec_prefix}/lib + +Name: LillyDAP +Description: Little LDAP kernel: Event-based, lock-free, LDAP as storage RPC +Version: 0.1.1 +Requires: Quick-DER +Cflags: -I${includedir} +Libs: -L${libdir} -llillydap diff --git a/include/lillydap/api.h b/include/lillydap/api.h index 4bb047d..3c4f7a0 100644 --- a/include/lillydap/api.h +++ b/include/lillydap/api.h @@ -86,6 +86,7 @@ struct LillyConnection { int (*lillyget_ldapmessage) (); //TODO//TYPING//MOVE_TO_STATIC int (*lillyget_operation) (); //TODO//TYPING//MOVE_TO_STATIC int (*lillyput_operation) (); //TODO//TYPING//MOVE_TO_STATIC + int (*lillyput_ldapmessage) (); //TODO//TYPING//MOVE_TO_STATIC int (*lillyput_dercursor) (); //TODO//TYPING//MOVE_TO_STATIC // Functions to implement the standard API // (RFC-compatible wrappers are defined below) @@ -125,12 +126,17 @@ int lillyget_operation (LDAP *lil, * to the network. */ int lillyput_operation (LDAP *lil, - const LillyPool qpool, + LillyPool qpool, const LillyMsgId msgid, const uint8_t opcode, const dercursor *data, const dercursor controls); -int lillyput_dercursor (LDAP *lil, const dercursor dermsg); //TODO// +int lillyput_ldapmessage (LDAP *lil, + LillyPool qpool, + const LillyMsgId msgid, + const dercursor operation, + const dercursor controls); +int lillyput_dercursor (LillyDAP *lil, LillyPool qpool, dercursor dermsg); void lillyput_enqueue (LillyDAP *lil, struct LillySend *addend); bool lillyput_cansend (LillyDAP *lil); int lillyput_event (LDAP *lil); diff --git a/include/lillydap/queue.h b/include/lillydap/queue.h index 38575e6..c098b99 100644 --- a/include/lillydap/queue.h +++ b/include/lillydap/queue.h @@ -55,6 +55,13 @@ typedef struct LillySend { void lillyput_enqueue (struct LillyConnection *lil, struct LillySend *addend); +/* Enqueue a message in a single dercursor. Normally, we supply a series of + * dermessages, so this is just there to mirror properly; it may actually be + * useful as a value for a lillyget_dercursor() pointer. + */ +int lillyput_dercursor (LillyDAP *lil, LillyPool qpool, dercursor dermsg); + + /* Test if there is anything in the queue for LillyPut */ bool lillyput_cansend (struct LillyConnection *lil); diff --git a/lib/derbuf.c b/lib/derbuf.c index 74d6f62..c30bda3 100644 --- a/lib/derbuf.c +++ b/lib/derbuf.c @@ -11,49 +11,6 @@ #include - -#ifdef TODO_PREFER_OLD_FUNCTION_API -/* This function helps a network component determine if it has collected - * the bytes of a complete DER value. This can be used to invoke parsers - * that assume that a full value has been loaded. Examples are LDAP and - * GSSAPI. - * - * The functions does not copy the buffers, but assumes that the network - * layer will grow the buffer until it is complete; to help, this function - * informs the network layer how much more should be loaded before the buffer - * is full. (Or the will return maxint if they don't know.) Upon error, - * -1 is returned and errno set. A complete buffer returns the DER value - * size which is then less than or equal to the size provided. In the case - * where not enough has been provided to parse the length, the value in - * retsz_toolittle is returned; you should set that to a value higher - * than buflen, even if just buflen+1 -- so you know you need to keep - * chasing for input. You might choose to set it to a value < 0 when - * you find it reasonable to expect that a complete header should be - * there, but keep in mind that errno will not be set when this value - * is returned. - * - * As soon as the function returns rv where (rv > 0) && (rv <= buflen), - * one DER value can be processed at and a remainder may be - * left at . - */ -size_t derbuf_datasize (uint8_t *buf, size_t buflen, size_t retsz_toolittle) { - size_t len; - uint8_t hlen; - uint8_t tag; - dercursor crs; - if (buflen < 2) { - return retsz_toolittle; - } - crs.derptr = buf; - crs.derlen = buflen; - if (der_header (&crs, &tag, &len, &hlen) == -1) { - return -1; - } - return len; -} -#endif - - /* Signal that information is available for reading to lillyget_xxx() * processing. This first loads a header, determines the total length to * read and allocates a buffer for it; then, it incrementally loads the diff --git a/lib/dermsg.c b/lib/dermsg.c index 9412b37..e657ee7 100644 --- a/lib/dermsg.c +++ b/lib/dermsg.c @@ -26,7 +26,7 @@ static const derwalk pck_ldapmsg_shallow [] = { DER_PACK_STORE | DER_TAG_INTEGER, // messageID DER_PACK_STORE | DER_PACK_ANY, // protocolOp CHOICE { ... } DER_PACK_OPTIONAL, - DER_PACK_STORE | DER_PACK_ANY, // controls SEQ-OF OPTIONAL + DER_PACK_STORE | DER_TAG_SEQUENCE, // controls SEQ-OF OPTIONAL DER_PACK_LEAVE, // ...} DER_PACK_END }; @@ -41,8 +41,8 @@ static const derwalk pck_ldapmsg_shallow [] = { * Out of range values are returned as 0. This value only indicates invalid * return when len > 1, so check for that. */ -//TODO// SIMPLIFIED -- we know that INTEGER is clipped to 32 bits under RFC 4511 -//TODO// SIMPLIFIED:CHECKIFOKAY -- is the outcome always positive too? +// SIMPLIFIED -- we know that INTEGER is clipped to 32 bits under RFC 4511 +// SIMPLIFIED:CHECKIFOKAY -- is the outcome always positive too? int32_t qder2b_unpack_int32 (dercursor data4) { int32_t retval = 0; int idx; @@ -69,9 +69,36 @@ int32_t qder2b_unpack_int32 (dercursor data4) { } +/* DER utility: This should probably appear in Quick DER sometime soon. + * + * Pack an Int32 or UInt32 and return the number of bytes. Do not pack a header + * around it. The function returns the number of bytes taken, even 0 is valid. + */ +typedef uint8_t QDERBUF_INT32_T [4]; +dercursor qder2b_pack_int32 (uint8_t *target_4b, int32_t value) { + dercursor retval; + int shift = 24; + retval.derptr = target_4b; + retval.derlen = 0; + while (shift >= 0) { + if ((retval.derlen == 0) && (shift > 0)) { + // Skip sign-extending initial bytes + uint32_t neutro = (value >> (shift - 1) ) & 0x000001ff; + if ((neutro == 0x000001ff) || (neutro == 0x00000000)) { + shift -= 8; + continue; + } + } + target_4b [retval.derlen] = (value >> shift) & 0xff; + retval.derlen++; + shift -= 8; + } + return retval; +} + + /* Process a dercursor, meaning a combination as an LDAPMessage */ -//TODO// Consider adding an optional qpool, whose responsibility is passed in. int lillyget_dercursor (LDAP *lil, LillyPool *qpool_opt, dercursor msg) { // // Unpack the DER cursor as an LDAPMessage, but stay shallow @@ -101,4 +128,44 @@ int lillyget_dercursor (LDAP *lil, LillyPool *qpool_opt, dercursor msg) { } - +/* Shallowly pack an LDAPMessage, into a DER message. + */ +int lillyput_ldapmessage (LDAP *lil, + LillyPool qpool, + const LillyMsgId msgid, + const dercursor operation, + const dercursor controls) { + // + // Allocate space for all the fields in a shallow LDAPMessage + // (async delivery requires it, and the LillyPool makes it cheap) + dercursor *mid_op_ctl = lillymem_alloc (qpool, 3 * sizeof (dercursor)); + uint8_t *mid_int32 = lillymem_alloc (qpool, sizeof (QDERBUF_INT32_T)); + if ((mid_int32 == NULL) || (mid_op_ctl == NULL)) { + errno = ENOMEM; + goto bail_out; + } + // + // Set the three fields to the message ID, operation and controls + mid_op_ctl [0] = qder2b_pack_int32 (mid_int32, msgid); + mid_op_ctl [1] = operation; + mid_op_ctl [2] = controls; + // + // Find the size of the packed DER message + // Note: More optimal schemes are possible, passing multiple buffers + dercursor total; + total.derlen = der_pack (pck_ldapmsg_shallow, mid_op_ctl, NULL); + total.derptr = lillymem_alloc (qpool, total.derlen); + if (total.derptr == NULL) { + errno = ENOMEM; + goto bail_out; + } + der_pack (pck_ldapmsg_shallow, mid_op_ctl, total.derptr + total.derlen); + return lillyput_dercursor (lil, qpool, total); + // + // We ran into a problem +bail_out: + if (qpool != NULL) { + lillymem_endpool (qpool); + } + return -1; +} diff --git a/lib/mem.c b/lib/mem.c index 1a9b625..690cb68 100644 --- a/lib/mem.c +++ b/lib/mem.c @@ -158,7 +158,7 @@ void lillymsg_id_free (LDAP *lil, LillyMsgId cango) { } layer = layer->next_layer; } - //TODO// We should never end up here; maybe we're leaking memory + // We should never end up here; maybe we're leaking memory } @@ -176,7 +176,7 @@ LillyPool lillymsg_id_qpool (LDAP *lil, LillyMsgId mid) { } layer = layer->next_layer; } - //TODO// We should never end up here; maybe we're confused + // We should never end up here; maybe we're confused return NULL; } diff --git a/lib/msgop.c b/lib/msgop.c index d0bac96..1fdcb3f 100644 --- a/lib/msgop.c +++ b/lib/msgop.c @@ -5,6 +5,7 @@ #include +#include #include @@ -20,7 +21,6 @@ * will because the responsibility of lillyget_ldapmessage() -- which it * may pass down to further lillyget_xxx() or other functions. */ -//TODO// Consider adding an optional qpool, whose responsibility is passed in. int lillyget_ldapmessage (LDAP *lil, LillyPool qpool, const LillyMsgId msgid, @@ -49,7 +49,6 @@ int lillyget_ldapmessage (LDAP *lil, } // // Request a memory pool for the msgid - //TODO// Does it improve anything if we share request/response spaces? if (qpool == NULL) { qpool = lillymem_newpool (); if (qpool == NULL) { @@ -110,7 +109,6 @@ int lillyget_ldapmessage (LDAP *lil, // // Pass down the information -- and the responsibility // The response value also comes from lillyget_operation() - //TODO// Parse the controls? Or better to provide lookups maybe? return lil->lillyget_operation (lil, qpool, msgid, opcode, data, controls); // // Upon failure, cleanup and report the failure to the upstream @@ -150,11 +148,10 @@ size_t qder2b_prefixhead (uint8_t *dest_opt, uint8_t header, size_t len) { /* Send an operation based on the given msgid, operation and control. -//TODO// Passing a dercursor by value means it gets copied -- nice for async? //TODO// Run the same code twice, first with NULL, then loop back with a ptr */ int lillyput_operation (LDAP *lil, - const LillyPool qpool, + LillyPool qpool, const LillyMsgId msgid, const uint8_t opcode, const dercursor *data, @@ -185,9 +182,9 @@ int lillyput_operation (LDAP *lil, // Count the number of bytes for the controls if (controls.derptr != NULL) { totlen += qder2b_prefixhead (NULL, - DER_TAG_CONTEXT(0), + DER_TAG_CONTEXT(0) | 0x20, qder2b_prefixhead (NULL, - DER_TAG_SEQUENCE, + DER_TAG_SEQUENCE | 0x20, controls.derlen)); } // @@ -216,13 +213,15 @@ int lillyput_operation (LDAP *lil, controls.derptr, controls.derlen); totlen = qder2b_prefixhead (NULL, - DER_TAG_CONTEXT(0), + DER_TAG_CONTEXT(0) | 0x20, qder2b_prefixhead (NULL, DER_TAG_SEQUENCE, controls.derlen)); } // // Perform the actual packing in the now-prepared buffer + // Start counting totlen from 0 and hope to find the same again + totlen = 0; totlen += der_pack (opcode_table [opcode].pck_message, data, dermsg.derptr + dermsg.derlen - totlen); @@ -231,25 +230,25 @@ int lillyput_operation (LDAP *lil, mid = msgid; uint8_t midlen = 0; while (mid > 0) { - dermsg.derptr [dermsg.derlen - totlen++] = (mid & 0xff); + dermsg.derptr [dermsg.derlen - ++totlen] = (mid & 0xff); mid >>= 8; midlen++; } - totlen = qder2b_prefixhead (dermsg.derptr + dermsg.derlen - totlen, + totlen += qder2b_prefixhead (dermsg.derptr + dermsg.derlen - totlen, DER_TAG_INTEGER, - totlen); + midlen) - midlen; // // Now construct the LDAPMessage as a SEQUENCE totlen = qder2b_prefixhead (dermsg.derptr + dermsg.derlen - totlen, - DER_TAG_SEQUENCE, + DER_TAG_SEQUENCE | 0x20, totlen); #if 1 if (totlen != dermsg.derlen) { - fprintf (stderr, "ERROR: Reproduced length %z instead of %z\n", totlen, dermsg.derlen); + fprintf (stderr, "ERROR: Reproduced length %zd instead of %zd\n", totlen, dermsg.derlen); } #endif // // Pass the resulting DER message on to lillyput_dercursor() - return lil->lillyput_dercursor (lil, dermsg); + return lil->lillyput_dercursor (lil, qpool, dermsg); } diff --git a/lib/msgop.gperf b/lib/msgop.gperf index 7ab5c59..48a826f 100644 --- a/lib/msgop.gperf +++ b/lib/msgop.gperf @@ -297,10 +297,6 @@ static const struct packer_info opcode_table [] = { #define opcode_reject ( opcode_table [31] ) -//TODO:NOGO// #if sizeof(opcode_table) != OPCODE_EXT_UNDEF * sizeof(struct packer_info) -//TODO:NOGO// # error "Number of entries in opcode_table does not equal OPCODE_EXT_UNDEF" -//TODO:NOGO// #endif - %} diff --git a/lib/queue.c b/lib/queue.c index 0a47dd2..cc15202 100644 --- a/lib/queue.c +++ b/lib/queue.c @@ -5,6 +5,7 @@ #include +#include #include #include @@ -13,7 +14,9 @@ #include #include -#include "opa_primitives.h" +#ifndef CONFIG_SINGLE_THREADED +# include "opa_primitives.h" +#endif /* This code gathers input from potentially many threads into one queue. @@ -76,14 +79,35 @@ /* We are not particularly interested in the typing model of OPA; it means * including the header files everywhere, which may be better to avoid. + * + * Note: These macros are only used below, they are not a generic API. + * Because of this, we have not had a lot of zeal in placing bracing. */ -#define cas_ptr(ptrptr,old,new) OPA_cas_ptr ((OPA_ptr_t *) ptrptr, old, new) -#define xcg_ptr(ptrptr,new) OPA_swap_ptr ((OPA_ptr_t *) ptrptr, new) -#define set_ptr(ptrptr,new) OPA_store_ptr ((OPA_ptr_t *) ptrptr, new) -#define get_ptr(ptrptr) ( (LillySend *) \ - OPA_load_ptr ((OPA_ptr_t *) ptrptr) ) -#define nil_ptr(ptrptr) (NULL == get_ptr (ptrptr)) +#ifndef CONFIG_SINGLE_THREADED + +/* The atomic operations from OpenPA make us lock-free yet stable */ +# define cas_ptr(ptrptr,old,new) OPA_cas_ptr ((OPA_ptr_t *) ptrptr, old, new) +# define xcg_ptr(ptrptr,new) OPA_swap_ptr ((OPA_ptr_t *) ptrptr, new) +# define set_ptr(ptrptr,new) OPA_store_ptr ((OPA_ptr_t *) ptrptr, new) +# define get_ptr(ptrptr) ( (LillySend *) \ + OPA_load_ptr ((OPA_ptr_t *) ptrptr) ) +# define nil_ptr(ptrptr) (NULL == get_ptr (ptrptr)) + +#else /* CONFIG_SINGLE_THREADED */ + + +/* No need for atomic operations when we are sure to have only one thread */ +static void *_tmp; +# define cas_ptr(ptrptr,old,new) ((*ptrptr == old) \ + ? (*ptrptr=new, old) \ + : (*ptrptr)) +# define xcg_ptr(ptrptr,new) (_tmp = (*ptrptr), (*ptrptr)= new, _tmp) +# define set_ptr(ptrptr,new) (*ptrptr = new) +# define get_ptr(ptrptr) (*ptrptr) +# define nil_ptr(ptrptr) (NULL == get_ptr (ptrptr)) + +#endif /* CONFIG_SIGNLE_THREADED */ /* Initialise the signaling routine that hints that lillyput_event() may work. @@ -199,6 +223,8 @@ int lillyput_event (LDAP *lil) { // // Send out what we have in the current dercursor *crs //TODO// Pile up multiple elements? Difficult to combine ok with error + // Simple enough for EAGAIN / EWOULDBLOCK, but there are still the other errors... + // http://stackoverflow.com/questions/19391208/when-a-non-blocking-send-only-transfers-partial-data-can-we-assume-it-would-r ssize_t sent = write (lil->put_fd, crs->derptr, crs->derlen); if (sent > 0) { crs->derlen -= sent; @@ -209,3 +235,22 @@ int lillyput_event (LDAP *lil) { return sent; } + +/* Enqueue a message in a single dercursor. Normally, we supply a series of + * dermessages, so this is just there to mirror properly; it may actually be + * useful as a value for a lillyget_dercursor() pointer. + */ +int lillyput_dercursor (LillyDAP *lil, LillyPool qpool, dercursor dermsg) { + LillySend *lise = lillymem_alloc (qpool, + sizeof(LillySend) + sizeof(dercursor)); + if (lise == NULL) { + errno = ENOMEM; + return -1; + } + lise->put_qpool = qpool; + memcpy (&lise->cursori [0], &dermsg, sizeof (dercursor)); + memset (&lise->cursori [1], 0, sizeof (dercursor)); + lillyput_enqueue (lil, lise); + return 0; +} + diff --git a/lib/rfc1823.c b/lib/rfc1823.c index 797401f..8cc0f3a 100644 --- a/lib/rfc1823.c +++ b/lib/rfc1823.c @@ -16,6 +16,9 @@ */ +#include + + //TODO// What a drag this seems, now the crisp API of LillyDAP is clear :-S diff --git a/lib/sillymem.c b/lib/sillymem.c index bfd4453..a9bcd9d 100644 --- a/lib/sillymem.c +++ b/lib/sillymem.c @@ -8,7 +8,7 @@ * entire pool can go. * * Unlike the rest of LilyDAP, you must not assume that these routines - * are re-entrant. TODO: Maybe add a crudo global lock to lift this? + * are re-entrant. * * From: Rick van Rein */ diff --git a/test/Makefile b/test/Makefile index af001f2..937e266 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,4 +1,4 @@ -TARGETS=lillydump stampede #lillypass +TARGETS=lillydump stampede lillypass lillydump_OBJS = lillydump.o sillymem.o lillydump_LIBS = ../lib/mem.o ../lib/derbuf.o ../lib/dermsg.o ../lib/msgop.o ../lib/opswi.o ../lib/queue.o /usr/local/src/quick-der/lib/der_header.o /usr/local/src/quick-der/lib/der_unpack.o /usr/local/src/quick-der/lib/der_pack.o /usr/local/src/quick-der/lib/der_skipenter.o diff --git a/test/README.MD b/test/README.MD index 2f0ff6b..678526b 100644 --- a/test/README.MD +++ b/test/README.MD @@ -57,5 +57,8 @@ appear at the output just as they are on the input, with no more than encoding changes caused by the variability in the BER format. In many cases however, BER is sent as DER, and that would always be the same. -Needless to say that the next step is a filtering utility. +Needless to say that the next step is a filtering utility. Or one that +actually implements some functionality for which it needs to read and +write. + diff --git a/test/lillypass.c b/test/lillypass.c new file mode 100644 index 0000000..7bd145f --- /dev/null +++ b/test/lillypass.c @@ -0,0 +1,354 @@ +/* lillypass.c -- Passthrough for LDAPMessage chunks. + * + * This routine passes binary data into the lillyget_* routines, until it is + * delivered. At that point, it passes it back up, and delivers it to its + * output stream. + * + * Coupling can be done at various levels, and this is why the number of + * levels to pass through LDAP can be set as a first parameter; levels are: + * + * 0. Directly pass LDAPMessage chunks as a dercursor + * 1. Pass a LDAPMessage after splitting into request, opcode and controls + * 2. Pass LDAP operations with unpacked data, but use the same code for each + * 3. Pass LDAP operations through individual operations (big risk of ENOSYS) + * 4. The LDAP operations unpack the controls, and later pack them again + * + * TODO: level 3 still does what it did in lillydump. + * TODO: level 4 has not been implemented yet. + * + * Reading / writing is highly structured, so it can be used for testing. + * For this reason, query IDs and times will not be randomly generated. + * Note that some operations may not be supported -- which is then reported. + * + * From: Rick van Rein + */ + + +#include +#include +#include + +#include +#include + +#define USE_SILLYMEM + +#include +#include + +#include + + +int lillypass_BindRequest (LDAP *lil, + LillyPool qpool, + const LillyMsgId msgid, + const LillyPack_BindRequest *br, + const dercursor controls) { + printf ("Got BindRequest\n"); + printf (" - version in %d bytes %02x,...\n", br->version.derlen, br->version.derptr [0]); + printf (" - name \"%.*s\"\n", br->name.derlen, br->name.derptr); + if (br->authentication.simple.derptr != NULL) { + printf (" - simple authentication with \"%.*s\"\n", br->authentication.simple.derlen, br->authentication.simple.derptr); + } + if (br->authentication.sasl.mechanism.derptr != NULL) { + printf (" - SASL mechanism \"%.*s\"\n", br->authentication.sasl.mechanism.derlen, br->authentication.sasl.mechanism.derptr); + if (br->authentication.sasl.credentials.derptr != NULL) { + printf (" - SASL credentias \"%.*s\"\n", br->authentication.sasl.credentials.derlen, br->authentication.sasl.credentials.derptr); + } + } + return 0; +} + +int lillypass_BindResponse (LDAP *lil, + LillyPool qpool, + const LillyMsgId msgid, + const LillyPack_BindResponse *br, + const dercursor controls) { + printf ("Got BindResponse\n"); + printf (" - resultCode in %d bytes %02x,%02x,%02x,%02x,...\n", br->resultCode.derlen, br->resultCode.derptr [0], br->resultCode.derptr [1], br->resultCode.derptr [2], br->resultCode.derptr [3]); + printf (" - matchedDN \"%.*s\"\n", br->matchedDN.derlen, br->matchedDN.derptr); + printf (" - diagnosticMessage \"%.*s\"\n", br->diagnosticMessage.derlen, br->diagnosticMessage.derptr); + return 0; +} + +int lillypass_UnbindRequest (LDAP *lil, + LillyPool qpool, + const LillyMsgId msgid, + const LillyPack_UnbindRequest *ur, + const dercursor controls) { + printf ("Got UnbindRequest\n"); + printf (" - payload length is %s\n", (ur->derptr == NULL) ? "absent": (ur->derlen == 0) ? "empty" : "filled?!?"); + return 0; +} + +int lillypass_SearchRequest (LDAP *lil, + LillyPool qpool, + const LillyMsgId msgid, + const LillyPack_SearchRequest *sr, + const dercursor controls) { + printf ("Got SearchRequest\n"); + printf (" - baseObject \"%.*s\"\n", sr->baseObject.derlen, sr->baseObject.derptr); + if (sr->scope.derlen != 1) { + printf (" ? scope has awkward size %zd instead of 1\n", sr->scope.derlen); + } else { + switch (*sr->scope.derptr) { + case 0: + printf (" - scope base\n"); + break; + case 1: + printf (" - scope one\n"); + break; + case 2: + printf (" - scope sub\n"); + break; + default: + printf (" ? scope weird value %d instead of 0, 1 or 2\n", *sr->scope.derptr); + } + } + if (sr->derefAliases.derlen != 1) { + printf (" ? derefAliases has awkward size %zd instead of 1\n", sr->derefAliases.derlen); + } else { + switch (*sr->derefAliases.derptr) { + case 0: + printf (" - derefAliases neverDerefAlias\n"); + break; + case 1: + printf (" - derefAliases derefInSearching\n"); + break; + case 2: + printf (" - derefAliases derefFindingBaseObj\n"); + break; + case 3: + printf (" - derefAliases derefAlways\n"); + break; + default: + printf (" ? derefAliases weird value %d instead of 0, 1, 2 or 3\n", *sr->derefAliases.derptr); + } + } + // attributes SEQUENCE OF LDAPString + dercursor attrs = sr->attributes; + printf (" - attributes.derlen = %zd\n", attrs.derlen); + printf (" - attributes.enter.derlen = %zd\n", attrs.derlen); + while (attrs.derlen > 0) { + dercursor attr = attrs; + if (der_focus (&attr)) { + fprintf (stderr, "ERROR while focussing on attribute of SearchRequest: %s\n", strerror (errno)); + } else { + printf (" - attr.derlen = %zd\n", attr.derlen); + printf (" - attributes \"%.*s\"\n", attr.derlen, attr.derptr); + } + der_skip (&attrs); + } + return 0; +} + +int lillypass_SearchResultEntry (LDAP *lil, + LillyPool qpool, + const LillyMsgId msgid, + const LillyPack_SearchResultEntry *sre, + const dercursor controls) { + printf ("Got SearchResultEntry\n"); + printf (" - objectName \"%.*s\"\n", sre->objectName.derlen, sre->objectName.derptr); + // partialAttribute SEQUENCE OF PartialAttribute + dercursor pa = sre->attributes; + der_enter (&pa); + while (pa.derlen > 0) { + dercursor type = pa; + // SEQUENCE { type AttributeDescription, + // vals SET OF AttributeValue } + der_enter (&type); + printf (" - partialAttribute.type \"%.*s\"\n", type.derlen, type.derptr); + der_skip (&pa); + dercursor vals = pa; + der_enter (&vals); + while (vals.derlen > 0) { + dercursor val = vals; + der_enter (&val); + printf (" - value \"%.*s\"\n", val.derlen, val.derptr); + der_skip (&vals); + } + der_skip (&pa); + } + return 0; +} + +int lillypass_SearchResultReference (LDAP *lil, + LillyPool qpool, + const LillyMsgId msgid, + const LillyPack_SearchResultReference *srr, + const dercursor controls) { + printf ("Got SearchResultReference\n"); + dercursor uris = *srr; + do { + dercursor uri = uris; + der_enter (&uri); + printf (" - URI \"%.*s\"\n", uri.derlen, uri.derptr); + der_skip (&uris); + } while (uris.derlen > 0); + return 0; +} + +int lillypass_SearchResultDone (LDAP *lil, + LillyPool qpool, + const LillyMsgId msgid, + const LillyPack_SearchResultDone *srd, + const dercursor controls) { + printf ("Got SearchResultDone\n"); + printf (" - resultCode is %zd==1 byte valued %d\n", srd->resultCode.derlen, *srd->resultCode.derptr); + printf (" - matchedDN \"%.*s\"\n", srd->matchedDN.derlen, srd->matchedDN.derptr); + printf (" - diagnosticMessage \"%.*s\"\n", srd->diagnosticMessage.derlen, srd->diagnosticMessage.derptr); + if (srd->referral.derptr != NULL) { + dercursor uris = srd->referral; + do { + dercursor uri = uris; + der_enter (&uri); + printf (" - URI \"%.*s\"\n", uri.derlen, uri.derptr); + der_skip (&uris); + } while (uris.derlen > 0); + } + return 0; +} + + +void process (LDAP *lil, char *progname, char *derfilename) { + // + // Open the file + int fd = open (derfilename, O_RDONLY); + if (fd < 0) { + fprintf (stderr, "%s: Failed to open \"%s\"\n", progname, derfilename); + exit (1); + } + // + // Print the file being handled + // + // Setup the input file descriptor + int flags = fcntl (fd, F_GETFL, 0); + if (flags == -1) { + fprintf (stderr, "%s: Failed to get flags on stdin\n", progname); + exit (1); + } + flags |= O_NONBLOCK; + if (fcntl (fd, F_SETFL, flags) == -1) { + fprintf (stderr, "%s: Failed to set non-blocking flag on stdin\n", progname); + exit (1); + } + // + // Set the file handle for input and output file handles in lil + lil->get_fd = fd; + lil->put_fd = 1; + // + // Send events until no more can be read + int i; + for (i=0; i<1000; i++) { + lillyget_event (lil); + lillyput_event (lil); + } + // + // Close off processing + close (fd); +} + + +void setup (void) { + lillymem_newpool_fun = sillymem_newpool; + lillymem_endpool_fun = sillymem_endpool; + lillymem_alloc_fun = sillymem_alloc; +} + + +static const LillyOpRegistry opregistry = { + .by_name = { + .BindRequest = lillypass_BindRequest, + .BindResponse = lillypass_BindResponse, + .UnbindRequest = lillypass_UnbindRequest, + .SearchRequest = lillypass_SearchRequest, + .SearchResultEntry = lillypass_SearchResultEntry, + .SearchResultReference = lillypass_SearchResultReference, + .SearchResultDone = lillypass_SearchResultDone, + } +}; + + +int main (int argc, char *argv []) { + // + // Check arguments + char *progname = argv [0]; + if (argc < 3) { + fprintf (stderr, "Usage: %s level ldapmsg.der...\nThe level is a value from 0 to 4, with increasing code being used\n", progname); + exit (1); + } + // + // Initialise functions and structures + setup (); + // + // Create the memory pool + LillyPool *lipo = lillymem_newpool (); + if (lipo == NULL) { + fprintf (stderr, "%s: Failed to allocate a memory pool\n", progname); + exit (1); + } + // + // Allocate the connection structuur + LillyDAP *lil; + lil = lillymem_alloc0 (lipo, sizeof (LillyDAP)); + // + // We first setup all operations to pass over to output directly... + lil->lillyget_dercursor = + lil->lillyput_dercursor = lillyput_dercursor; + lil->lillyget_ldapmessage = + lil->lillyput_ldapmessage = lillyput_ldapmessage; + lil->lillyget_operation = + lil->lillyput_operation = lillyput_operation; + // + // ...and then we turn it back depending on the level + char level = '\0'; + if (strlen (argv [1]) == 1) { + level = argv [1] [0]; + } + switch (level) { + default: + fprintf (stderr, "%s: Invalid level '%s'\n", + argv [0], argv [1]); + exit (1); + case '4': + fprintf (stderr, "%s: Level 4 is not yet implemented\n", + argv [0]); + // and fallthrough... + case '3': + lil->lillyget_operation = lillyget_operation; + // and fallthrough... + case '2': + lil->lillyget_ldapmessage = lillyget_ldapmessage; + // and fallthrough... + case '1': + lil->lillyget_dercursor = lillyget_dercursor; + // and fallthrough... + case '0': + // Keep everything as-is, passing as directly as possible + break; + } + // + // For level 4 we need the operation registry + lil->opregistry = &opregistry; + // + // Allocate a connection pool + lil->cnxpool = lillymem_newpool (); + if (lil->cnxpool == NULL) { + fprintf (stderr, "%s: Failed to allocate connection memory pool\n", progname); + exit (1); + } + // + // Iterate over the LDAP binary files in argv [1..] + int argi = 2; + while (argi < argc) { + process (lil, progname, argv [argi]); + argi++; + } + + // + // Cleanup and exit + lillymem_endpool (lil->cnxpool); + lillymem_endpool (lipo); + exit (0); +} +