diff options
author | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
---|---|---|
committer | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
commit | 7c478bd95313f5f23a4c958a745db2134aa03244 (patch) | |
tree | c871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/lib/libldap5/sources/ldap/ssldap/ldapsinit.c | |
download | illumos-gate-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz |
OpenSolaris Launch
Diffstat (limited to 'usr/src/lib/libldap5/sources/ldap/ssldap/ldapsinit.c')
-rw-r--r-- | usr/src/lib/libldap5/sources/ldap/ssldap/ldapsinit.c | 1317 |
1 files changed, 1317 insertions, 0 deletions
diff --git a/usr/src/lib/libldap5/sources/ldap/ssldap/ldapsinit.c b/usr/src/lib/libldap5/sources/ldap/ssldap/ldapsinit.c new file mode 100644 index 0000000000..795b727ff2 --- /dev/null +++ b/usr/src/lib/libldap5/sources/ldap/ssldap/ldapsinit.c @@ -0,0 +1,1317 @@ +/* + * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * The contents of this file are subject to the Netscape 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/NPL/ + * + * 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 Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * ldapsinit.c + */ + +#if defined(NET_SSL) + +#if defined( _WINDOWS ) +#include <windows.h> +#endif + +/* XXX:mhein The following is a workaround for the redefinition of */ +/* const problem on OSF. Fix to be provided by NSS */ +/* This is a pretty benign workaround for us which */ +/* should not cause problems in the future even if */ +/* we forget to take it out :-) */ + +#ifdef OSF1V4D +#ifndef __STDC__ +# define __STDC__ +#endif /* __STDC__ */ +#endif /* OSF1V4D */ + +#include <errno.h> +#include <nspr.h> +#include <cert.h> +#include <key.h> +#include <ssl.h> +#include <sslproto.h> +#include <sslerr.h> +#include <prnetdb.h> + +#include <ldap.h> + +#include <ldappr.h> +#include <pk11func.h> + +#ifdef _SOLARIS_SDK +#include "solaris-int.h" +#include <libintl.h> +#include <syslog.h> +#include <nsswitch.h> +#include <synch.h> +#include <nss_dbdefs.h> +#include <netinet/in.h> + +#define HOST_BUF_SIZE 2048 + +#ifndef INADDR_NONE +#define INADDR_NONE (-1) +#endif + +extern int +str2hostent(const char *instr, int lenstr, void *ent, char *buffer, + int buflen); + +extern int +str2hostent6(const char *instr, int lenstr, void *ent, char *buffer, + int buflen); + +extern LDAPHostEnt * +_ns_gethostbyaddr(LDAP *ld, const char *addr, int length, int type, + LDAPHostEnt *result, char *buffer, int buflen, int *statusp, + void *extradata); + +static char *host_service = NULL; + +static DEFINE_NSS_DB_ROOT(db_root_hosts); +static DEFINE_NSS_DB_ROOT(db_root_ipnodes); +#endif + +/* + * Data structure to hold the standard NSPR I/O function pointers set by + * libprldap. We save them in our session data structure so we can call + * them from our own I/O functions (we add functionality to support SSL + * while using libprldap's functions as much as possible). + */ +typedef struct ldapssl_std_functions { + LDAP_X_EXTIOF_CLOSE_CALLBACK *lssf_close_fn; + LDAP_X_EXTIOF_CONNECT_CALLBACK *lssf_connect_fn; + LDAP_X_EXTIOF_DISPOSEHANDLE_CALLBACK *lssf_disposehdl_fn; +} LDAPSSLStdFunctions; + + + +/* + * LDAP session data structure. + */ +typedef struct ldapssl_session_info { + int lssei_using_pcks_fns; + int lssei_ssl_strength; + char *lssei_certnickname; + char *lssei_keypasswd; + LDAPSSLStdFunctions lssei_std_functions; + CERTCertDBHandle *lssei_certdbh; +#ifdef _SOLARIS_SDK + /* + * This is a hack. + * ld is used so that we can use libldap's gethostbyaddr + * resolver. This is needed to prevent recursion with libsldap. + */ + LDAP *ld; +#endif /* _SOLARIS_SDK */ +} LDAPSSLSessionInfo; + + +/* + * LDAP socket data structure. + */ +typedef struct ldapssl_socket_info { + LDAPSSLSessionInfo *soi_sessioninfo; /* session info */ +} LDAPSSLSocketInfo; + + +/* + * XXXceb This is a hack until the new IO functions are done. + * this function MUST be called before ldap_enable_clienauth. + * right now, this function is called in ldapssl_pkcs_init(); + */ + +static int using_pkcs_functions = 0; + +void set_using_pkcs_functions( int val ) +{ + using_pkcs_functions = val; +} + + +/* + * Utility functions: + */ +static void ldapssl_free_session_info( LDAPSSLSessionInfo **ssipp ); +static void ldapssl_free_socket_info( LDAPSSLSocketInfo **soipp ); + + +/* + * SSL Stuff + */ + +static int ldapssl_AuthCertificate(void *sessionarg, PRFileDesc *fd, + PRBool checkSig, PRBool isServer); + +/* + * client auth stuff + */ +static int get_clientauth_data( void *sessionarg, PRFileDesc *prfd, + CERTDistNames *caNames, CERTCertificate **pRetCert, + SECKEYPrivateKey **pRetKey ); +static int get_keyandcert( LDAPSSLSessionInfo *ssip, + CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey, + char **errmsgp ); +static int check_clientauth_nicknames_and_passwd( LDAP *ld, + LDAPSSLSessionInfo *ssip ); +static char *get_keypassword( PK11SlotInfo *slot, PRBool retry, + void *sessionarg ); + +/* + * Static variables. + */ +#ifdef _SOLARIS_SDK +static int default_ssl_strength = LDAPSSL_AUTH_CNCHECK; +#else +static int default_ssl_strength = LDAPSSL_AUTH_CERT; +#endif + +/* + * Like ldap_init(), except also install I/O routines from libsec so we + * can support SSL. If defsecure is non-zero, SSL is enabled for the + * default connection as well. + */ +LDAP * +LDAP_CALL +ldapssl_init( const char *defhost, int defport, int defsecure ) +{ + LDAP *ld; + + +#ifndef LDAP_SSLIO_HOOKS + return( NULL ); +#else + if (0 ==defport) + defport = LDAPS_PORT; + + if (( ld = ldap_init( defhost, defport )) == NULL ) { + return( NULL ); + } + + if ( ldapssl_install_routines( ld ) < 0 || ldap_set_option( ld, + LDAP_OPT_SSL, defsecure ? LDAP_OPT_ON : LDAP_OPT_OFF ) != 0 ) { + PR_SetError( PR_UNKNOWN_ERROR, EINVAL ); /* XXXmcs: just a guess! */ + ldap_unbind( ld ); + return( NULL ); + } + + return( ld ); +#endif +} + + +static int +ldapssl_close(int s, struct lextiof_socket_private *socketarg) +{ + PRLDAPSocketInfo soi; + LDAPSSLSocketInfo *ssoip; + LDAPSSLSessionInfo *sseip; + + memset( &soi, 0, sizeof(soi)); + soi.soinfo_size = PRLDAP_SOCKETINFO_SIZE; + if ( prldap_get_socket_info( s, socketarg, &soi ) != LDAP_SUCCESS ) { + return( -1 ); + } + + ssoip = (LDAPSSLSocketInfo *)soi.soinfo_appdata; + sseip = ssoip->soi_sessioninfo; + + ldapssl_free_socket_info( (LDAPSSLSocketInfo **)&soi.soinfo_appdata ); + + return( (*(sseip->lssei_std_functions.lssf_close_fn))( s, socketarg )); +} + +static int +do_ldapssl_connect(const char *hostlist, int defport, int timeout, + unsigned long options, struct lextiof_session_private *sessionarg, + struct lextiof_socket_private **socketargp, int clientauth ) +{ + int intfd = -1; + PRBool secure; + PRLDAPSessionInfo sei; + PRLDAPSocketInfo soi; + LDAPSSLSocketInfo *ssoip = NULL; + LDAPSSLSessionInfo *sseip; + PRFileDesc *sslfd = NULL; +#ifdef _SOLARIS_SDK + int port; + int parse_err; + char *host = NULL; + char *name; + struct ldap_x_hostlist_status + *status = NULL; + in_addr_t addr_ipv4; + in6_addr_t addr_ipv6; + char *host_buf; + LDAPHostEnt *hent; + LDAPHostEnt host_ent; + int stat; + int type; +#endif /* _SOLARIS_SDK */ + + /* + * Determine if secure option is set. Also, clear secure bit in options + * the we pass to the standard connect() function (since it doesn't know + * how to handle the secure option). + */ + if ( 0 != ( options & LDAP_X_EXTIOF_OPT_SECURE )) { + secure = PR_TRUE; + options &= ~LDAP_X_EXTIOF_OPT_SECURE; + } else { + secure = PR_FALSE; + } + + /* + * Retrieve session info. so we can store a pointer to our session info. + * in our socket info. later. + */ + memset( &sei, 0, sizeof(sei)); + sei.seinfo_size = PRLDAP_SESSIONINFO_SIZE; + if ( prldap_get_session_info( NULL, sessionarg, &sei ) != LDAP_SUCCESS ) { + return( -1 ); + } + sseip = (LDAPSSLSessionInfo *)sei.seinfo_appdata; + + /* + * Call the standard connect() callback to make the TCP connection. + * If it succeeds, *socketargp is set. + */ + + intfd = (*(sseip->lssei_std_functions.lssf_connect_fn))( hostlist, defport, + timeout, options, sessionarg, socketargp +#ifdef _SOLARIS_SDK + , &host ); +#else + ); +#endif /* _SOLARIS_SDK */ + + if ( intfd < 0 ) { + return( intfd ); + } + +#ifdef _SOLARIS_SDK + /* + * Determine if the "host name" is an ip address. If so, + * we must look up the actual host name corresponding to + * it. + */ + if ( NULL == host ) { + goto close_socket_and_exit_with_error; + } + type = AF_UNSPEC; + if (strlen(host) < INET6_ADDRSTRLEN && + inet_pton(AF_INET6, host, &addr_ipv6) == 1) { + type = AF_INET6; + } else if (strlen(host) < INET_ADDRSTRLEN && + inet_pton(AF_INET, host, &addr_ipv4) == 1) { + type = AF_INET; + } + if (type == AF_INET || type == AF_INET6) { + host_buf = malloc(HOST_BUF_SIZE); + if (host_buf == NULL) { + /* will free host in close_socket_and_exit_with_error */ + goto close_socket_and_exit_with_error; + } + + /* Call ldap layer's gethostbyaddr resolver */ + hent = _ns_gethostbyaddr(sseip->ld, host, strlen(host), type, + &host_ent, host_buf, HOST_BUF_SIZE, &stat, NULL); + + /* If we are unable to lookup the host addr, we fail! */ + if (hent == NULL) { + syslog(LOG_WARNING, + "libldap: do_ldapssl_connect: " + "Unable to resolve '%s'", host); + free(host_buf); + /* will free host in close_socket_and_exit_with_error */ + goto close_socket_and_exit_with_error; + } + /* We support only the primary host name */ + else { + if (hent->ldaphe_name != NULL) + name = strdup(hent->ldaphe_name); + free(host_buf); + if (name == NULL) + goto close_socket_and_exit_with_error; + else + ldap_memfree(host); host = NULL; + host = name; + } + } +#endif /* _SOLARIS_SDK */ + + /* + * Retrieve socket info. so we have the PRFileDesc. + */ + memset( &soi, 0, sizeof(soi)); + soi.soinfo_size = PRLDAP_SOCKETINFO_SIZE; + if ( prldap_get_socket_info( intfd, *socketargp, &soi ) != LDAP_SUCCESS ) { + goto close_socket_and_exit_with_error; + } + + /* + * Allocate a structure to hold our socket-specific data. + */ + if ( NULL == ( ssoip = PR_Calloc( 1, sizeof( LDAPSSLSocketInfo )))) { + goto close_socket_and_exit_with_error; + } + ssoip->soi_sessioninfo = sseip; + + /* + * Add SSL layer and let the standard NSPR to LDAP layer and enable SSL. + */ + if (( sslfd = SSL_ImportFD( NULL, soi.soinfo_prfd )) == NULL ) { + goto close_socket_and_exit_with_error; + } + + if ( SSL_OptionSet( sslfd, SSL_SECURITY, secure ) != SECSuccess || + SSL_OptionSet( sslfd, SSL_HANDSHAKE_AS_CLIENT, secure ) + != SECSuccess || ( secure && SSL_ResetHandshake( sslfd, + PR_FALSE ) != SECSuccess )) { + goto close_socket_and_exit_with_error; + } + + /* + * Let the standard NSPR to LDAP layer know about the new socket and + * our own socket-specific data. + */ + soi.soinfo_prfd = sslfd; + soi.soinfo_appdata = (void *)ssoip; + if ( prldap_set_socket_info( intfd, *socketargp, &soi ) != LDAP_SUCCESS ) { + goto close_socket_and_exit_with_error; + } + +#ifdef _SOLARIS_SDK + /* + * Set hostname which will be retrieved (depending on ssl strength) when + * using client or server auth. + */ + if (SSL_SetURL(sslfd, host) != SECSuccess) + goto close_socket_and_exit_with_error; + ldap_memfree(host); + host = NULL; +#endif /* _SOLARIS_SDK */ + + sslfd = NULL; /* so we don't close the socket twice upon error */ + + /* + * Install certificate hook function. + */ + SSL_AuthCertificateHook( soi.soinfo_prfd, + (SSLAuthCertificate)ldapssl_AuthCertificate, + (void *)sseip); + + if ( SSL_GetClientAuthDataHook( soi.soinfo_prfd, + get_clientauth_data, clientauth ? sseip : NULL ) != 0 ) { + goto close_socket_and_exit_with_error; + } + + return( intfd ); /* success */ + +close_socket_and_exit_with_error: +#ifdef _SOLARIS_SDK + if ( NULL != host ) ldap_memfree(host); +#endif /* _SOLARIS_SDK */ + if ( NULL != sslfd ) { + PR_Close( sslfd ); + } + if ( NULL != ssoip ) { + ldapssl_free_socket_info( &ssoip ); + } + if ( intfd >= 0 && NULL != *socketargp ) { + (*(sseip->lssei_std_functions.lssf_close_fn))( intfd, *socketargp ); + } + return( -1 ); +} + + +static int +ldapssl_connect(const char *hostlist, int defport, int timeout, + unsigned long options, struct lextiof_session_private *sessionarg, + struct lextiof_socket_private **socketargp ) +{ + return( do_ldapssl_connect( hostlist, defport, timeout, options, + sessionarg, socketargp, 0 )); +} + + +static int +ldapssl_clientauth_connect(const char *hostlist, int defport, int timeout, + unsigned long options, struct lextiof_session_private *sessionarg, + struct lextiof_socket_private **socketargp ) +{ + return( do_ldapssl_connect( hostlist, defport, timeout, options, + sessionarg, socketargp, 1 )); +} + + +static void +ldapssl_disposehandle(LDAP *ld, struct lextiof_session_private *sessionarg) +{ + PRLDAPSessionInfo sei; + LDAPSSLSessionInfo *sseip; + LDAP_X_EXTIOF_DISPOSEHANDLE_CALLBACK *disposehdl_fn; + + memset( &sei, 0, sizeof( sei )); + sei.seinfo_size = PRLDAP_SESSIONINFO_SIZE; + if ( prldap_get_session_info( ld, NULL, &sei ) == LDAP_SUCCESS ) { + sseip = (LDAPSSLSessionInfo *)sei.seinfo_appdata; + disposehdl_fn = sseip->lssei_std_functions.lssf_disposehdl_fn; + ldapssl_free_session_info( &sseip ); + (*disposehdl_fn)( ld, sessionarg ); + } +} + + +/* + * Install I/O routines from libsec and NSPR into libldap to allow libldap + * to do SSL. + * + * We rely on libprldap to provide most of the functions, and then we override + * a few of them to support SSL. + */ +int +LDAP_CALL +ldapssl_install_routines( LDAP *ld ) +{ +#ifndef LDAP_SSLIO_HOOKS + ldap_set_lderrno( ld, LDAP_LOCAL_ERROR, NULL, NULL ); + return( -1 ); +#else + struct ldap_x_ext_io_fns iofns; + LDAPSSLSessionInfo *ssip; + PRLDAPSessionInfo sei; + +/* + * This is done within ldap_init() and + * ldap_init() is called from ldapssl_init() + */ +#ifndef _SOLARIS_SDK + if ( prldap_install_routines( + ld, + 1 /* shared -- we have to assume it is */ ) + != LDAP_SUCCESS ) { + return( -1 ); + } +#endif /*_SOLARIS_SDK*/ + + /* + * Allocate our own session information. + */ + if ( NULL == ( ssip = (LDAPSSLSessionInfo *)PR_Calloc( 1, + sizeof( LDAPSSLSessionInfo )))) { + ldap_set_lderrno( ld, LDAP_NO_MEMORY, NULL, NULL ); + return( -1 ); + } + /* + * Initialize session info. + * XXX: it would be nice to be able to set these on a per-session basis: + * lssei_using_pcks_fns + * lssei_certdbh + */ + ssip->lssei_ssl_strength = default_ssl_strength; + ssip->lssei_using_pcks_fns = using_pkcs_functions; + ssip->lssei_certdbh = CERT_GetDefaultCertDB(); +#ifdef _SOLARIS_SDK + /* + * This is part of a hack to allow the ssl portion of the + * library to call the ldap library gethostbyaddr resolver. + */ + ssip->ld = ld; +#endif /* _SOLARIS_SDK */ + + /* + * override a few functions, saving a pointer to the standard function + * in each case so we can call it from our SSL savvy functions. + */ + memset( &iofns, 0, sizeof(iofns)); + iofns.lextiof_size = LDAP_X_EXTIO_FNS_SIZE; + if ( ldap_get_option( ld, LDAP_X_OPT_EXTIO_FN_PTRS, (void *)&iofns ) < 0 ) { + ldapssl_free_session_info( &ssip ); + return( -1 ); + } + + /* override socket, connect, and ioctl */ + ssip->lssei_std_functions.lssf_connect_fn = iofns.lextiof_connect; + iofns.lextiof_connect = ldapssl_connect; + ssip->lssei_std_functions.lssf_close_fn = iofns.lextiof_close; + iofns.lextiof_close = ldapssl_close; + ssip->lssei_std_functions.lssf_disposehdl_fn = iofns.lextiof_disposehandle; + iofns.lextiof_disposehandle = ldapssl_disposehandle; + + if ( ldap_set_option( ld, LDAP_X_OPT_EXTIO_FN_PTRS, (void *)&iofns ) < 0 ) { + ldapssl_free_session_info( &ssip ); + return( -1 ); + } + + /* + * Store session info. for later retrieval. + */ + sei.seinfo_size = PRLDAP_SESSIONINFO_SIZE; + sei.seinfo_appdata = (void *)ssip; + if ( prldap_set_session_info( ld, NULL, &sei ) != LDAP_SUCCESS ) { + return( -1 ); + } + + return( 0 ); +#endif +} + + +/* + * Set the SSL strength for an existing SSL-enabled LDAP session handle. + * + * See the description of ldapssl_serverauth_init() above for valid + * sslstrength values. If ld is NULL, the default for new LDAP session + * handles is set. + * + * Returns 0 if all goes well and -1 if an error occurs. + */ +int +LDAP_CALL +ldapssl_set_strength( LDAP *ld, int sslstrength ) +{ + int rc = 0; /* assume success */ + + if ( sslstrength != LDAPSSL_AUTH_WEAK && + sslstrength != LDAPSSL_AUTH_CERT && + sslstrength != LDAPSSL_AUTH_CNCHECK ) { + rc = -1; + } else { + if ( NULL == ld ) { /* set default strength */ + default_ssl_strength = sslstrength; + } else { /* set session-specific strength */ + PRLDAPSessionInfo sei; + LDAPSSLSessionInfo *sseip; + + memset( &sei, 0, sizeof( sei )); + sei.seinfo_size = PRLDAP_SESSIONINFO_SIZE; + if ( prldap_get_session_info( ld, NULL, &sei ) == LDAP_SUCCESS ) +{ + sseip = (LDAPSSLSessionInfo *)sei.seinfo_appdata; + sseip->lssei_ssl_strength = sslstrength; + } else { + rc = -1; + } + } + } + + return( rc ); +} + +int +LDAP_CALL +ldapssl_enable_clientauth( LDAP *ld, char *keynickname, + char *keypasswd, char *certnickname ) +{ +#ifndef LDAP_SSLIO_HOOKS + ldap_set_lderrno( ld, LDAP_LOCAL_ERROR, NULL, NULL ); + return( -1 ); +#else + struct ldap_x_ext_io_fns iofns; + LDAPSSLSessionInfo *ssip; + PRLDAPSessionInfo sei; + + /* + * Check parameters + */ + if ( certnickname == NULL || keypasswd == NULL ) { + ldap_set_lderrno( ld, LDAP_PARAM_ERROR, NULL, NULL ); + return( -1 ); + } + + /* + * Update session info. data structure. + */ + sei.seinfo_size = PRLDAP_SESSIONINFO_SIZE; + if ( prldap_get_session_info( ld, NULL, &sei ) != LDAP_SUCCESS ) { + return( -1 ); + } + ssip = (LDAPSSLSessionInfo *)sei.seinfo_appdata; + if ( NULL == ssip ) { + ldap_set_lderrno( ld, LDAP_PARAM_ERROR, NULL, NULL ); + return( -1 ); + } + ssip->lssei_certnickname = PL_strdup( certnickname ); + ssip->lssei_keypasswd = PL_strdup( keypasswd ); + + if ( NULL == ssip->lssei_certnickname || NULL == ssip->lssei_keypasswd ) { + ldap_set_lderrno( ld, LDAP_NO_MEMORY, NULL, NULL ); + return( -1 ); + } + + if ( check_clientauth_nicknames_and_passwd( ld, ssip ) != 0 ) { + return( -1 ); + } + + /* + * replace standard SSL CONNECT function with client auth aware one + */ + memset( &iofns, 0, sizeof(iofns)); + iofns.lextiof_size = LDAP_X_EXTIO_FNS_SIZE; + if ( ldap_get_option( ld, LDAP_X_OPT_EXTIO_FN_PTRS, (void *)&iofns ) + != 0 ) { + return( -1 ); + } + + if ( iofns.lextiof_connect != ldapssl_connect ) { + /* standard SSL setup has not done */ + ldap_set_lderrno( ld, LDAP_PARAM_ERROR, NULL, NULL ); + return( -1 ); + } + + iofns.lextiof_connect = ldapssl_clientauth_connect; + + if ( ldap_set_option( ld, LDAP_X_OPT_EXTIO_FN_PTRS, (void *)&iofns ) + != 0 ) { + return( -1 ); + } + + return( 0 ); +#endif +} + + +static void +ldapssl_free_session_info( LDAPSSLSessionInfo **ssipp ) +{ + if ( NULL != ssipp && NULL != *ssipp ) { + if ( NULL != (*ssipp)->lssei_certnickname ) { + PL_strfree( (*ssipp)->lssei_certnickname ); + (*ssipp)->lssei_certnickname = NULL; + } + if ( NULL != (*ssipp)->lssei_keypasswd ) { + PL_strfree( (*ssipp)->lssei_keypasswd ); + (*ssipp)->lssei_keypasswd = NULL; + } + PR_Free( *ssipp ); + *ssipp = NULL; + } +} + + +static void +ldapssl_free_socket_info( LDAPSSLSocketInfo **soipp ) +{ + if ( NULL != soipp && NULL != *soipp ) { + PR_Free( *soipp ); + *soipp = NULL; + } +} + + +/* this function provides cert authentication. This is called during + * the SSL_Handshake process. Once the cert has been retrieved from + * the server, the it is checked, using VerifyCertNow(), then + * the cn is checked against the host name, set with SSL_SetURL() + */ + +static int +ldapssl_AuthCertificate(void *sessionarg, PRFileDesc *fd, PRBool checkSig, + PRBool isServer) +{ + SECStatus rv = SECFailure; + LDAPSSLSessionInfo *sseip; + CERTCertificate *cert; + SECCertUsage certUsage; + char *hostname = (char *)0; + + if (!sessionarg || !socket) + return rv; + + sseip = (LDAPSSLSessionInfo *)sessionarg; + + if (LDAPSSL_AUTH_WEAK == sseip->lssei_ssl_strength ) { /* no check */ + return SECSuccess; + } + + if ( isServer ) { + certUsage = certUsageSSLClient; + } else { + certUsage = certUsageSSLServer; + } + cert = SSL_PeerCertificate( fd ); + + rv = CERT_VerifyCertNow(sseip->lssei_certdbh, cert, checkSig, + certUsage, NULL); + + if ( rv != SECSuccess || isServer ) + return rv; + + if ( LDAPSSL_AUTH_CNCHECK == sseip->lssei_ssl_strength ) + { + /* cert is OK. This is the client side of an SSL connection. + * Now check the name field in the cert against the desired hostname. + * NB: This is our only defense against Man-In-The-Middle (MITM) + * attacks! + */ + + hostname = SSL_RevealURL( fd ); + + if (hostname && hostname[0]) { + rv = CERT_VerifyCertName(cert, hostname); + } else { + rv = SECFailure; + } + if (hostname) + PORT_Free(hostname); + if (rv != SECSuccess) + PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN); + } + + return((int)rv); +} + + +/* + * called during SSL client auth. when server wants our cert and key. + * return 0 if we succeeded and set *pRetCert and *pRetKey, -1 otherwise. + * if -1 is returned SSL will proceed without sending a cert. + */ + +static int +get_clientauth_data( void *sessionarg, PRFileDesc *prfd, + CERTDistNames *caNames, CERTCertificate **pRetCert, + SECKEYPrivateKey **pRetKey ) + +{ + LDAPSSLSessionInfo *ssip; + + if (( ssip = (LDAPSSLSessionInfo *)sessionarg ) == NULL ) { + return( -1 ); /* client auth. not enabled */ + } + + return( get_keyandcert( ssip, pRetCert, pRetKey, NULL )); +} + +static int +get_keyandcert( LDAPSSLSessionInfo *ssip, + CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey, + char **errmsgp ) +{ + CERTCertificate *cert; + SECKEYPrivateKey *key; + + if (( cert = PK11_FindCertFromNickname( ssip->lssei_certnickname, NULL )) + == NULL ) { + if ( errmsgp != NULL ) { + *errmsgp = dgettext(TEXT_DOMAIN, "unable to find certificate"); + } + return( -1 ); + } + + { + PK11_SetPasswordFunc( get_keypassword ); + } + + + + if (( key = PK11_FindKeyByAnyCert( cert, (void *)ssip )) == NULL ) { + CERT_DestroyCertificate( cert ); + if ( errmsgp != NULL ) { + *errmsgp = dgettext(TEXT_DOMAIN, "bad key or key password"); + } + return( -1 ); + } + + *pRetCert = cert; + *pRetKey = key; + return( 0 ); +} + + +/* + * This function returns the password to NSS. + * This function is enable through PK11_SetPasswordFunc + * only if pkcs functions are not being used. + */ + +static char * +get_keypassword( PK11SlotInfo *slot, PRBool retry, void *sessionarg ) +{ + LDAPSSLSessionInfo *ssip; + + if ( retry) + return (NULL); + + ssip = (LDAPSSLSessionInfo *)sessionarg; + if ( NULL == ssip ) { + return( NULL ); + } + + return( ssip->lssei_keypasswd ); +} + + +/* + * performs some basic checks on clientauth cert and key/password + * + * XXXmcs: could perform additional checks... see servers/slapd/ssl.c + * 1) check expiration + * 2) check that public key in cert matches private key + * see ns/netsite/ldap/servers/slapd/ssl.c:slapd_ssl_init() for example code. + */ +static int +check_clientauth_nicknames_and_passwd( LDAP *ld, LDAPSSLSessionInfo *ssip ) +{ + char *errmsg = NULL; + CERTCertificate *cert = NULL; + SECKEYPrivateKey *key = NULL; + int rv; + + rv = get_keyandcert( ssip, &cert, &key, &errmsg ); + + if ( rv != 0 ) { + if ( errmsg != NULL ) { + errmsg = strdup( errmsg ); + } + ldap_set_lderrno( ld, LDAP_PARAM_ERROR, NULL, errmsg ); + return( -1 ); + } + + if ( cert != NULL ) { + CERT_DestroyCertificate( cert ); + } + if ( key != NULL ) { + SECKEY_DestroyPrivateKey( key ); + } + return( 0 ); +} + + +#if 0 /* NOT_NEEDED_IN_LIBLDAP */ +/* there are patches and kludges. this is both. force some linkers to + * link this stuff in + */ +int stubs_o_stuff( void ) +{ + PRExplodedTime exploded; + PLArenaPool pool; + + const char *name ="t"; + PRUint32 size = 0, align = 0; + + PR_ImplodeTime( &exploded ); + PL_InitArenaPool( &pool, name, size, align); + PR_Cleanup(); + PR_fprintf((PRFileDesc*)stderr, "Bad IDEA!!"); + + return 0; + +} +#endif /* NOT_NEEDED_IN_LIBLDAP */ + + +/* + * Import the file descriptor corresponding to the socket of an already + * open LDAP connection into SSL, and update the socket and session + * information accordingly. + */ +int ldapssl_import_fd ( LDAP *ld, int secure ) +{ + PRLDAPSessionInfo sei; + PRLDAPSocketInfo soi; + LDAPSSLSocketInfo *ssoip = NULL; + LDAPSSLSessionInfo *sseip; + PRFileDesc *sslfd = NULL; + + + /* + * Retrieve session info. so we can store a pointer to our session info. + * in our socket info. later. + */ + memset( &sei, 0, sizeof(sei)); + sei.seinfo_size = PRLDAP_SESSIONINFO_SIZE; + if ( prldap_get_session_info( ld, NULL, &sei ) != LDAP_SUCCESS ) { + return( -1 ); + } + sseip = (LDAPSSLSessionInfo *)sei.seinfo_appdata; + + + /* + * Retrieve socket info. so we have the PRFileDesc. + */ + memset( &soi, 0, sizeof(soi)); + soi.soinfo_size = PRLDAP_SOCKETINFO_SIZE; + if ( prldap_get_default_socket_info( ld, &soi ) != LDAP_SUCCESS ) { + return( -1 ); + } + + /* + * Allocate a structure to hold our socket-specific data. + */ + if ( NULL == ( ssoip = PR_Calloc( 1, sizeof( LDAPSSLSocketInfo )))) { + goto reset_socket_and_exit_with_error; + } + ssoip->soi_sessioninfo = sseip; + + /* + * Add SSL layer and let the standard NSPR to LDAP layer and enable SSL. + */ + if (( sslfd = SSL_ImportFD( NULL, soi.soinfo_prfd )) == NULL ) { + goto reset_socket_and_exit_with_error; + } + + if ( SSL_OptionSet( sslfd, SSL_SECURITY, secure ) != SECSuccess || + SSL_OptionSet( sslfd, SSL_HANDSHAKE_AS_CLIENT, secure ) + != SECSuccess || ( secure && SSL_ResetHandshake( sslfd, + PR_FALSE ) != SECSuccess )) { + goto reset_socket_and_exit_with_error; + } + + /* + * Let the standard NSPR to LDAP layer know about the new socket and + * our own socket-specific data. + */ + soi.soinfo_prfd = sslfd; + soi.soinfo_appdata = (void *)ssoip; + if ( prldap_set_default_socket_info( ld, &soi ) != LDAP_SUCCESS ) { + goto reset_socket_and_exit_with_error; + } + + /* + * Install certificate hook function. + */ + if ( SSL_AuthCertificateHook( soi.soinfo_prfd, + (SSLAuthCertificate)ldapssl_AuthCertificate, + (void *)CERT_GetDefaultCertDB()) != 0 ) { + goto reset_socket_and_exit_with_error; + } + + if ( SSL_GetClientAuthDataHook( soi.soinfo_prfd, + get_clientauth_data, sseip->lssei_certnickname ? sseip : NULL ) + != 0 ) { + goto reset_socket_and_exit_with_error; + } + + return 0; + + reset_socket_and_exit_with_error: + if ( NULL != sslfd ) { + /* + * "Unimport" the socket from SSL, i.e. get rid of the upper layer of + * the file descriptor stack, which represents SSL. + */ + soi.soinfo_prfd = sslfd; + sslfd = PR_PopIOLayer( soi.soinfo_prfd, PR_TOP_IO_LAYER ); + sslfd->dtor( sslfd ); + } + if ( NULL != ssoip ) { + ldapssl_free_socket_info( &ssoip ); + soi.soinfo_appdata = NULL; + } + prldap_set_default_socket_info( ld, &soi ); + + return( -1 ); +} + + +/* + * Reset an LDAP session from SSL to a non-secure status. + * Basically, this function undoes the work done by ldapssl_install_routines. + */ +int ldapssl_reset_to_nonsecure ( LDAP *ld ) +{ + PRLDAPSessionInfo sei; + LDAPSSLSessionInfo *sseip; + + struct ldap_x_ext_io_fns iofns; + int rc = 0; + + /* + * Retrieve session info. + */ + memset( &sei, 0, sizeof(sei)); + sei.seinfo_size = PRLDAP_SESSIONINFO_SIZE; + if ( prldap_get_session_info( ld, NULL, &sei ) != LDAP_SUCCESS ) { + return( -1 ); + } + sseip = (LDAPSSLSessionInfo *)sei.seinfo_appdata; + + if ( sseip != NULL ) { + /* + * Reset the standard extended io functions. + */ + memset( &iofns, 0, sizeof(iofns)); + iofns.lextiof_size = LDAP_X_EXTIO_FNS_SIZE; + if ( ldap_get_option( ld, LDAP_X_OPT_EXTIO_FN_PTRS, (void *)&iofns ) + < 0) { + rc = -1; + goto free_session_info; + } + + /* reset socket, connect, and ioctl */ + iofns.lextiof_connect = sseip->lssei_std_functions.lssf_connect_fn; + iofns.lextiof_close = sseip->lssei_std_functions.lssf_close_fn; + iofns.lextiof_disposehandle = + sseip->lssei_std_functions.lssf_disposehdl_fn; + + if ( ldap_set_option( ld, LDAP_X_OPT_EXTIO_FN_PTRS, (void *)&iofns ) + < 0) { + rc = -1; + goto free_session_info; + } + +free_session_info: + ldapssl_free_session_info( &sseip ); + sei.seinfo_appdata = NULL; + if ( prldap_set_session_info( ld, NULL, &sei ) != LDAP_SUCCESS ) { + rc = -1; + } + } /* if ( sseip && *sseip ) */ + + if ( ldap_set_option( ld, LDAP_OPT_SSL, LDAP_OPT_OFF ) < 0 ) { + return (-1); + } + + return rc; +} + + +#ifdef _SOLARIS_SDK +static void +_nss_initf_ipnodes(nss_db_params_t *p) +{ + static char *no_service = ""; + + p->name = NSS_DBNAM_IPNODES; + p->flags |= NSS_USE_DEFAULT_CONFIG; + p->default_config = host_service == NULL ? no_service : host_service; +} + +static void +_nss_initf_hosts(nss_db_params_t *p) +{ + static char *no_service = ""; + + p->name = NSS_DBNAM_HOSTS; + p->flags |= NSS_USE_DEFAULT_CONFIG; + p->default_config = host_service == NULL ? no_service : host_service; +} + +static struct hostent * +_switch_gethostbyaddr_r(const char *addr, int len, int type, + struct hostent *result, char *buffer, int buflen, + int *h_errnop) +{ + nss_XbyY_args_t arg; + nss_status_t res; + int (*str2ent)(); + void (*nss_initf)(); + nss_db_root_t *nss_db_root; + + if (AF_INET == type) { + str2ent = str2hostent; + nss_initf = _nss_initf_hosts; + nss_db_root = &db_root_hosts; + } else if (AF_INET6 == type) { + str2ent = str2hostent6; + nss_initf = _nss_initf_ipnodes; + nss_db_root = &db_root_ipnodes; + } else { + return NULL; + } + + NSS_XbyY_INIT(&arg, result, buffer, buflen, str2ent); + + arg.key.hostaddr.addr = addr; + arg.key.hostaddr.len = len; + arg.key.hostaddr.type = type; + arg.stayopen = 0; + + res = nss_search(nss_db_root, nss_initf, + NSS_DBOP_HOSTS_BYADDR, &arg); + arg.status = res; + *h_errnop = arg.h_errno; + return (struct hostent *)NSS_XbyY_FINI(&arg); +} + +/* + * ns_gethostbyaddr is used to be a substitute gethostbyaddr for + * libldap when ssl will need to determine the fully qualified + * host name from an address when it is unsafe to use the normal + * nameservice functions. + * + * Note that the ldap name service resolver calls this with the address as + * a character string - which we must convert into address form. + */ + +/*ARGSUSED*/ +static LDAPHostEnt * +ns_gethostbyaddr(const char *addr, int len, int type, + LDAPHostEnt *result, char *buffer, int buflen, int *statusp, + void *extradata) +{ + LDAPHostEnt *ldap_hent; + int h_errno; + struct hostent h_ent; + struct hostent *h_e = NULL; + struct in_addr a; + struct in6_addr a6; + int inet_error; /* error returned by inet_pton */ + + + if (addr == NULL || result == NULL || buffer == NULL || + (type != AF_INET && type != AF_INET6)) + return (NULL); + + + (void) memset(&h_ent, 0, sizeof (h_ent)); + + if (AF_INET == type) { + if (inet_pton(type, addr, &a.s_addr) == 1) { + h_e = _switch_gethostbyaddr_r((char *)&a, + sizeof (a.s_addr), type, &h_ent, + buffer, buflen, &h_errno); + } + } else if (AF_INET6 == type) { + if (inet_pton(type, addr, &a6.s6_addr) == 1) { + h_e = _switch_gethostbyaddr_r((char *)&a6, + sizeof (a6.s6_addr), type, &h_ent, + buffer, buflen, &h_errno); + } + } + + if (h_e == NULL) { + ldap_hent = NULL; + } else { + (void) memset(result, 0, sizeof (LDAPHostEnt)); + ldap_hent = result; + result->ldaphe_name = h_e->h_name; + result->ldaphe_aliases = h_e->h_aliases; + result->ldaphe_addrtype = h_e->h_addrtype; + result->ldaphe_length = h_e->h_length; + result->ldaphe_addr_list = h_e->h_addr_list; + } + return (ldap_hent); +} + +/* + * ldapssl_install_gethostbyaddr attempts to prevent recursion in + * gethostbyaddr calls when an ip address is given to ssl. This ip address + * must be resolved to a host name. + * + * For example, libsldap cannot use LDAP to resolve this address to a + * name because of recursion. The caller is instructing libldap to skip + * the specified name service when resolving addresses for the specified + * ldap connection. + * + * Currently only ldap and dns name services always return fully qualified + * names. The other name services (files, nis, and nisplus) will returned + * fully qualified names if the host names are stored as fully qualified names + * in these name services. + * + * Note: + * + * Since host_service applies to all connections, calling + * ldapssl_install_gethostbyaddr with different name services to + * skip will lead to unpredictable results. + * + * Returns: + * 0 if success + * -1 if failure + */ + +int +ldapssl_install_gethostbyaddr(LDAP *ld, const char *skip) +{ + enum __nsw_parse_err pserr; + struct __nsw_switchconfig *conf; + struct __nsw_lookup *lkp; + struct ldap_dns_fns dns_fns; + char *name_list = NULL; + char *tmp; + const char *name; + int len; + boolean_t got_skip = B_FALSE; + + /* + * db_root_hosts.lock mutex is used to ensure that the name list + * is not in use by the name service switch while we are updating + * the host_service + */ + + (void) mutex_lock(&db_root_hosts.lock); + conf = __nsw_getconfig("hosts", &pserr); + if (conf == NULL) { + (void) mutex_unlock(&db_root_hosts.lock); + return (0); + } + + /* check for ldap and count other backends */ + for (lkp = conf->lookups; lkp != NULL; lkp = lkp->next) { + name = lkp->service_name; + if (strcmp(name, skip) == 0) { + got_skip = B_TRUE; + continue; + } + if (name_list == NULL) + name_list = strdup(name); + else { + len = strlen(name_list); + tmp = realloc(name_list, len + strlen(name) + 2); + if (tmp == NULL) { + free(name_list); + name_list = NULL; + } else { + name_list = tmp; + name_list[len++] = ' '; + (void) strcpy(name_list+len, name); + } + } + if (name_list == NULL) { /* alloc error */ + (void) mutex_unlock(&db_root_hosts.lock); + __nsw_freeconfig(conf); + return (-1); + } + } + __nsw_freeconfig(conf); + if (!got_skip) { + /* + * Since skip name service not used for hosts, we do not need + * to install our private address resolution function + */ + (void) mutex_unlock(&db_root_hosts.lock); + if (name_list != NULL) + free(name_list); + return (0); + } + if (host_service != NULL) + free(host_service); + host_service = name_list; + (void) mutex_unlock(&db_root_hosts.lock); + + if (ldap_get_option(ld, LDAP_OPT_DNS_FN_PTRS, &dns_fns) != 0) + return (-1); + dns_fns.lddnsfn_gethostbyaddr = ns_gethostbyaddr; + if (ldap_set_option(ld, LDAP_OPT_DNS_FN_PTRS, &dns_fns) != 0) + return (-1); + return (0); +} +#endif /* _SOLARIS_SDK */ +#endif /* NET_SSL */ |