/* * 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 #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 #include #include #include #include #include #include #include #include #include #include #ifdef _SOLARIS_SDK #include "solaris-int.h" #include #include #include #include #include #include #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 */