/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Sun LDAP C SDK. * * The Initial Developer of the Original Code is Sun Microsystems, Inc. * * Portions created by Sun Microsystems, Inc are Copyright (C) 2005 * Sun Microsystems, Inc. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #ifdef LDAP_SASLIO_HOOKS #include #include "ldap-int.h" #include "../liblber/lber-int.h" #include /* Should be pulled in from lber-int.h */ #define READBUFSIZ 8192 #define SEARCH_TIMEOUT_SECS 120 #define NSLDAPI_SM_BUF 128 /* * Data structures: */ /* data structure that populates the I/O callback socket-specific arg. */ typedef struct lextiof_socket_private { struct ldap_x_ext_io_fns sess_io_fns; /* the saved layered ld fns from the layer we are "pushing" */ struct lber_x_ext_io_fns sock_io_fns; /* the saved layered ber fns from the layer we are "pushing" */ sasl_conn_t *sasl_ctx; /* the sasl context - pointer to the one from the connection */ char *sb_sasl_ibuf; /* sasl decrypted input buffer */ char *sb_sasl_iptr; /* current location in buffer */ int sb_sasl_bfsz; /* Alloc'd size of input buffer */ int sb_sasl_ilen; /* remaining length to process */ LDAP *ld; /* used to set errno */ Sockbuf *sb; /* pointer to our associated sockbuf */ } SASLIOSocketArg; static void destroy_SASLIOSocketArg(SASLIOSocketArg** sockarg) { if (sockarg && *sockarg) { NSLDAPI_FREE((*sockarg)->sb_sasl_ibuf); NSLDAPI_FREE((*sockarg)); *sockarg = NULL; } } static SASLIOSocketArg* new_SASLIOSocketArg(sasl_conn_t *ctx, int bufsiz, LDAP *ld, Sockbuf *sb) { SASLIOSocketArg *sockarg = NULL; if (bufsiz <= 0) { return sockarg; } sockarg = (SASLIOSocketArg*)NSLDAPI_CALLOC(1, sizeof(SASLIOSocketArg)); if (sockarg) { sockarg->sasl_ctx = ctx; sockarg->sb_sasl_ibuf = NSLDAPI_MALLOC(bufsiz); if (!sockarg->sb_sasl_ibuf) { destroy_SASLIOSocketArg(&sockarg); return sockarg; } sockarg->sb_sasl_iptr = NULL; sockarg->sb_sasl_bfsz = bufsiz; sockarg->sb_sasl_ilen = 0; sockarg->ld = ld; sockarg->sb = sb; } return sockarg; } /* * SASL Dependent routines * * SASL security and integrity options are supported through the * use of the extended I/O functionality. Because the extended * I/O functions may already be in use prior to enabling encryption, * when SASL encryption si enabled, these routine interpose themselves * over the exitng extended I/O routines and add an additional level * of indirection. * IE: Before SASL: client->libldap->lber->extio * After SASL: client->libldap->lber->saslio->extio * Any extio function are stilled used for the raw i/O [IE prldap] * but SASL will decrypt before passing to lber. * SASL cannot decrypt a stream so full packaets must be read * before proceeding. */ /* * Get the 4 octet header [size] for a sasl encrypted buffer. * See RFC222 [section 3]. */ static int nsldapi_sasl_pktlen( char *buf, int maxbufsize ) { int size; #if defined( _WINDOWS ) || defined( _WIN32 ) size = ntohl(*(u_long *)buf); #else size = ntohl(*(uint32_t *)buf); #endif if ( size < 0 || size > maxbufsize ) { return (-1 ); } return( size + 4 ); /* include the first 4 bytes */ } /* * SASL encryption routines */ static int nsldapi_sasl_read( int s, void *buf, int len, struct lextiof_socket_private *arg) { LDAP *ld; const char *dbuf; char *cp; int ret; unsigned dlen, blen; ld = (LDAP *)arg->ld; /* Is there anything left in the existing buffer? */ if ((ret = arg->sb_sasl_ilen) > 0) { ret = (ret > len ? len : ret); SAFEMEMCPY( buf, arg->sb_sasl_iptr, ret ); if (ret == arg->sb_sasl_ilen) { arg->sb_sasl_ilen = 0; arg->sb_sasl_iptr = NULL; } else { arg->sb_sasl_ilen -= ret; arg->sb_sasl_iptr += ret; } return( ret ); } /* buffer is empty - fill it */ cp = arg->sb_sasl_ibuf; dlen = 0; /* Read the length of the packet */ while ( dlen < 4 ) { if (arg->sock_io_fns.lbextiofn_read != NULL) { ret = arg->sock_io_fns.lbextiofn_read( s, cp, 4 - dlen, arg->sock_io_fns.lbextiofn_socket_arg); } else { ret = read( s, cp, 4 - dlen ); } #ifdef EINTR if ( ( ret < 0 ) && ( LDAP_GET_ERRNO(ld) == EINTR ) ) continue; #endif if ( ret <= 0 ) return( ret ); cp += ret; dlen += ret; } blen = 4; ret = nsldapi_sasl_pktlen( arg->sb_sasl_ibuf, arg->sb_sasl_bfsz ); if (ret < 0) { LDAP_SET_ERRNO(ld, EIO); return( -1 ); } dlen = ret - dlen; /* read the rest of the encrypted packet */ while ( dlen > 0 ) { if (arg->sock_io_fns.lbextiofn_read != NULL) { ret = arg->sock_io_fns.lbextiofn_read( s, cp, dlen, arg->sock_io_fns.lbextiofn_socket_arg); } else { ret = read( s, cp, dlen ); } #ifdef EINTR if ( ( ret < 0 ) && ( LDAP_GET_ERRNO(ld) == EINTR ) ) continue; #endif if ( ret <= 0 ) return( ret ); cp += ret; blen += ret; dlen -= ret; } /* Decode the packet */ ret = sasl_decode( arg->sasl_ctx, arg->sb_sasl_ibuf, blen, &dbuf, &dlen); if ( ret != SASL_OK ) { /* sb_sasl_read: failed to decode packet, drop it, error */ arg->sb_sasl_iptr = NULL; arg->sb_sasl_ilen = 0; LDAP_SET_ERRNO(ld, EIO); return( -1 ); } /* copy decrypted packet to the input buffer */ SAFEMEMCPY( arg->sb_sasl_ibuf, dbuf, dlen ); arg->sb_sasl_iptr = arg->sb_sasl_ibuf; arg->sb_sasl_ilen = dlen; ret = (dlen > (unsigned) len ? len : dlen); SAFEMEMCPY( buf, arg->sb_sasl_iptr, ret ); if (ret == arg->sb_sasl_ilen) { arg->sb_sasl_ilen = 0; arg->sb_sasl_iptr = NULL; } else { arg->sb_sasl_ilen -= ret; arg->sb_sasl_iptr += ret; } return( ret ); } static int nsldapi_sasl_write( int s, const void *buf, int len, struct lextiof_socket_private *arg) { int ret = 0; const char *obuf, *optr, *cbuf = (const char *)buf; unsigned olen, clen, tlen = 0; unsigned *maxbuf; ret = sasl_getprop(arg->sasl_ctx, SASL_MAXOUTBUF, (const void **)&maxbuf); if ( ret != SASL_OK ) { /* just a sanity check, should never happen */ return( -1 ); } while (len > 0) { clen = (len > *maxbuf) ? *maxbuf : len; /* encode the next packet. */ ret = sasl_encode( arg->sasl_ctx, cbuf, clen, &obuf, &olen); if ( ret != SASL_OK ) { /* XXX Log error? "sb_sasl_write: failed to encode packet..." */ return( -1 ); } /* Write everything now, buffer is only good until next sasl_encode */ optr = obuf; while (olen > 0) { if (arg->sock_io_fns.lbextiofn_write != NULL) { ret = arg->sock_io_fns.lbextiofn_write( s, optr, olen, arg->sock_io_fns.lbextiofn_socket_arg); } else { ret = write( s, optr, olen); } if ( ret < 0 ) return( ret ); optr += ret; olen -= ret; } len -= clen; cbuf += clen; tlen += clen; } return( tlen ); } /* * What's all this then? * First, take a look at os-ip.c:nsldapi_add_to_cb_pollfds(). When a new descriptor is * added to the pollfds array, the lpoll_socketarg field is initialized to the value from * the socketarg field - sb->sb_ext_io_fns.lbextiofn_socket_arg. In our case, since we * override this with our sasl data (see below nsldapi_sasl_install), we need to restore * the previous value so that the layer below us (i.e. prldap) can use the lpoll_socketarg * which it sets. * So how do which know which fds[i] is a "sasl" fd? * We initialize the lextiof_session_private *arg (see nsldapi_sasl_install) to point to * the socket_private data in sb->sb_ext_io_fns.lbextiofn_socket_arg for "sasl" sockets, * which is then used to initialize lpoll_socketarg (see above). * So, if the arg which gets passed into nsldapi_sasl_poll is the same as the * fds[i].lpoll_socketarg, we know it is a "sasl" socket and we need to "pop" the sasl * layer. We do this by replacing lpoll_socketarg with the one we saved when we "pushed" * the sasl layer. * So why the loop to restore the sasl lpoll_socketarg? * The lower layer only uses lpoll_socketarg during poll(). See ldappr-io.c:prldap_poll() * for more information about how that works. However, after the polling is done, there * is some special magic in os-ip.c in the functions nsldapi_add_to_cb_pollfds(), * nsldapi_clear_from_cb_pollfds(), and nsldapi_find_in_cb_pollfds() to find the correct * Sockbuf to operate on. This is the macro NSLDAPI_CB_POLL_MATCH(). For the extended * io function callbacks to work correctly, it is not sufficient to say that the file * descriptor in the Sockbuf matches the one that poll says has activity - we also need * to match the lpoll_socketarg with the sb->sb_ext_io_fns.lbextiofn_socket_arg to make * sure this really is the Sockbuf we want to use. So we have to restore the * lpoll_socketarg with the original one. * Why have origarg and staticorigarg? * To avoid malloc. The sizeof staticorigarg should be large enough to accomodate almost * all clients without incurring too much additional overhead. However, if we need more * room, origarg will grow to nfds. If this proves to be inadequate, the size of the * staticorigarg is a good candidate for a #define set by configure. */ static int nsldapi_sasl_poll( LDAP_X_PollFD fds[], int nfds, int timeout, struct lextiof_session_private *arg ) { LDAP_X_EXTIOF_POLL_CALLBACK *origpoll; /* poll fn from the pushed layer */ struct lextiof_session_private *origsess = NULL; /* session arg from the pushed layer */ SASLIOSocketArg **origarg = NULL; /* list of saved original socket args */ SASLIOSocketArg *staticorigarg[1024]; /* default list to avoid malloc */ int origargsize = sizeof(staticorigarg)/sizeof(staticorigarg[0]); int rc = -1; /* the return code - -1 means failure */ if (arg == NULL) { /* should not happen */ return( rc ); } origarg = staticorigarg; /* if the static array is not large enough, alloc a dynamic one */ if (origargsize < nfds) { origarg = (SASLIOSocketArg **)NSLDAPI_MALLOC(nfds*sizeof(SASLIOSocketArg *)); } if (fds && nfds > 0) { int i; for(i = 0; i < nfds; i++) { /* save the original socket arg */ origarg[i] = fds[i].lpoll_socketarg; if (arg == (struct lextiof_session_private *)fds[i].lpoll_socketarg) { /* lpoll_socketarg is a sasl socket arg - we need to replace it with the one from the layer we pushed (i.e. prldap) */ SASLIOSocketArg *sockarg = (SASLIOSocketArg *)fds[i].lpoll_socketarg; /* reset to pushed layer's socket arg */ fds[i].lpoll_socketarg = sockarg->sock_io_fns.lbextiofn_socket_arg; /* grab the pushed layers' poll fn and its session arg */ if (!origsess) { origpoll = sockarg->sess_io_fns.lextiof_poll; origsess = sockarg->sess_io_fns.lextiof_session_arg; } } } } if (origsess == NULL) { /* should not happen */ goto done; } /* call the "real" poll function */ rc = origpoll( fds, nfds, timeout, origsess ); /* reset the lpoll_socketarg values to their original values because they must match what's in sb->iofns->lbextiofn_socket_arg in order for NSLDAPI_CB_POLL_MATCH to work - see os-ip.c */ if (fds && nfds > 0) { int i; for(i = 0; i < nfds; i++) { if ((SASLIOSocketArg *)arg == origarg[i]) { fds[i].lpoll_socketarg = origarg[i]; } } } done: /* if we had to use a dynamic array, free it */ if (origarg != staticorigarg) { NSLDAPI_FREE(origarg); } return rc; } int nsldapi_sasl_open( LDAP *ld, LDAPConn *lconn, sasl_conn_t **ctx, sasl_ssf_t ssf ) { int saslrc; char *host = NULL; if ( !NSLDAPI_VALID_LDAP_POINTER( ld )) { LDAP_SET_LDERRNO( ld, LDAP_LOCAL_ERROR, NULL, NULL ); return( LDAP_LOCAL_ERROR ); } if ( lconn == NULL ) { if ( ld->ld_defconn == NULL || ld->ld_defconn->lconn_status != LDAP_CONNST_CONNECTED) { int rc = nsldapi_open_ldap_defconn( ld ); if( rc < 0 ) { return( LDAP_GET_LDERRNO( ld, NULL, NULL ) ); } } lconn = ld->ld_defconn; } /* need to clear out the old context for this connection, if any */ /* client may have re-bind-ed this connection without closing first */ if (lconn->lconn_sasl_ctx) { sasl_dispose(&lconn->lconn_sasl_ctx); lconn->lconn_sasl_ctx = NULL; } if ( 0 != ldap_get_option( ld, LDAP_OPT_HOST_NAME, &host ) ) { LDAP_SET_LDERRNO( ld, LDAP_LOCAL_ERROR, NULL, NULL ); return( LDAP_LOCAL_ERROR ); } saslrc = sasl_client_new( "ldap", host, NULL, NULL, /* iplocalport, ipremoteport - use defaults */ NULL, 0, ctx ); ldap_memfree(host); if ( (saslrc != SASL_OK) || (!*ctx) ) { return( nsldapi_sasl_cvterrno( ld, saslrc, NULL ) ); } if( ssf ) { sasl_ssf_t extprops; memset(&extprops, 0L, sizeof(extprops)); extprops = ssf; (void) sasl_setprop( *ctx, SASL_SSF_EXTERNAL, (void *) &extprops ); } /* (re)set security properties */ sasl_setprop( *ctx, SASL_SEC_PROPS, &ld->ld_sasl_secprops ); /* set the connection context */ lconn->lconn_sasl_ctx = *ctx; return( LDAP_SUCCESS ); } static int nsldapi_sasl_close( struct lextiof_socket_private *arg ) { /* undo function pointer interposing */ ldap_set_option( arg->ld, LDAP_X_OPT_EXTIO_FN_PTRS, &arg->sess_io_fns ); /* have to do this separately to make sure the socketarg is set correctly */ ber_sockbuf_set_option( arg->sb, LBER_SOCKBUF_OPT_EXT_IO_FNS, (void *)&arg->sock_io_fns ); destroy_SASLIOSocketArg(&arg); return( LDAP_SUCCESS ); } static int nsldapi_sasl_close_socket(int s, struct lextiof_socket_private *arg ) { LDAP_X_EXTIOF_CLOSE_CALLBACK *origclose; struct lextiof_socket_private *origsock; if (arg == NULL) { return( -1 ); } origclose = arg->sess_io_fns.lextiof_close; origsock = arg->sock_io_fns.lbextiofn_socket_arg; /* undo SASL */ nsldapi_sasl_close( arg ); arg = NULL; /* arg is destroyed at this point - do not use it */ if (origclose ) return ( origclose( s, origsock ) ); else { /* This is a copy of nsldapi_os_closesocket() * from os-ip.c. It is declared static there, * hence the copy of it. */ int rc; #ifdef NSLDAPI_AVOID_OS_SOCKETS rc = -1; #else /* NSLDAPI_AVOID_OS_SOCKETS */ #ifdef _WINDOWS rc = closesocket( s ); #else /* _WINDOWS */ rc = close( s ); #endif /* _WINDOWS */ #endif /* NSLDAPI_AVOID_OS_SOCKETS */ return( rc ); } } /* * install encryption routines if security has been negotiated */ int nsldapi_sasl_install( LDAP *ld, LDAPConn *lconn ) { struct lber_x_ext_io_fns fns; struct ldap_x_ext_io_fns iofns; sasl_security_properties_t *secprops; int rc, value; int bufsiz; Sockbuf *sb = NULL; sasl_conn_t *ctx = NULL; SASLIOSocketArg *sockarg = NULL; if ( lconn == NULL ) { lconn = ld->ld_defconn; if ( lconn == NULL ) { return( LDAP_LOCAL_ERROR ); } } if ( (sb = lconn->lconn_sb) == NULL ) { return( LDAP_LOCAL_ERROR ); } rc = ber_sockbuf_get_option( sb, LBER_SOCKBUF_OPT_TO_FILE_ONLY, (void *) &value); if (rc != 0 || value != 0) { return( LDAP_LOCAL_ERROR ); } /* the sasl context in the lconn must have been set prior to this */ ctx = lconn->lconn_sasl_ctx; rc = sasl_getprop( ctx, SASL_SEC_PROPS, (const void **)&secprops ); if (rc != SASL_OK) return( LDAP_LOCAL_ERROR ); bufsiz = secprops->maxbufsize; if (bufsiz <= 0) { return( LDAP_LOCAL_ERROR ); } /* create our socket specific context */ sockarg = new_SASLIOSocketArg(ctx, bufsiz, ld, sb); if (!sockarg) { return( LDAP_LOCAL_ERROR ); } /* save a copy of the existing io fns and the session arg */ memset( &sockarg->sess_io_fns, 0, LDAP_X_EXTIO_FNS_SIZE ); sockarg->sess_io_fns.lextiof_size = LDAP_X_EXTIO_FNS_SIZE; rc = ldap_get_option( ld, LDAP_X_OPT_EXTIO_FN_PTRS, &sockarg->sess_io_fns ); if (rc != 0) { destroy_SASLIOSocketArg(&sockarg); return( LDAP_LOCAL_ERROR ); } /* save a copy of the existing ber io fns and the socket arg */ memset( &sockarg->sock_io_fns, 0, LBER_X_EXTIO_FNS_SIZE ); sockarg->sock_io_fns.lbextiofn_size = LBER_X_EXTIO_FNS_SIZE; rc = ber_sockbuf_get_option( sb, LBER_SOCKBUF_OPT_EXT_IO_FNS, (void *)&sockarg->sock_io_fns); if (rc != 0) { destroy_SASLIOSocketArg(&sockarg); return( LDAP_LOCAL_ERROR ); } /* Always set the ext io close fn pointer to ensure we * clean up our sockarg context */ memset( &iofns, 0, sizeof(iofns)); /* first, copy struct - sets defaults */ iofns = sockarg->sess_io_fns; iofns.lextiof_close = nsldapi_sasl_close_socket; iofns.lextiof_session_arg = sockarg; /* needed for close and poll */ /* Set new values for the other ext io funcs if there are any - when using the native io fns (as opposed to prldap) there won't be any */ if ( sockarg->sess_io_fns.lextiof_read != NULL || sockarg->sess_io_fns.lextiof_write != NULL || sockarg->sess_io_fns.lextiof_poll != NULL || sockarg->sess_io_fns.lextiof_connect != NULL ) { /* next, just reset those functions we want to override */ iofns.lextiof_read = nsldapi_sasl_read; iofns.lextiof_write = nsldapi_sasl_write; iofns.lextiof_poll = nsldapi_sasl_poll; } /* set the ext io funcs */ rc = ldap_set_option( ld, LDAP_X_OPT_EXTIO_FN_PTRS, &iofns ); if (rc != 0) { /* frees everything and resets fns above */ nsldapi_sasl_close(sockarg); return( LDAP_LOCAL_ERROR ); } /* set the new ber io funcs and socket arg */ (void) memset( &fns, 0, LBER_X_EXTIO_FNS_SIZE); fns.lbextiofn_size = LBER_X_EXTIO_FNS_SIZE; fns.lbextiofn_read = nsldapi_sasl_read; fns.lbextiofn_write = nsldapi_sasl_write; fns.lbextiofn_socket_arg = sockarg; rc = ber_sockbuf_set_option( sb, LBER_SOCKBUF_OPT_EXT_IO_FNS, (void *)&fns); if (rc != 0) { /* frees everything and resets fns above */ nsldapi_sasl_close(sockarg); return( LDAP_LOCAL_ERROR ); } return( LDAP_SUCCESS ); } #endif