/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include "solaris-priv.h" #include "solaris-int.h" #include "ns_sldap.h" #include "ns_internal.h" #include "ns_cache_door.h" #include "ldappr.h" #include #include #include #include extern unsigned int _sleep(unsigned int); extern int ldap_sasl_cram_md5_bind_s(LDAP *, char *, struct berval *, LDAPControl **, LDAPControl **); extern int ldapssl_install_gethostbyaddr(LDAP *ld, const char *skip); static int openConnection(LDAP **, const char *, const ns_cred_t *, int, ns_ldap_error_t **, int, int); static void _DropConnection(ConnectionID cID, int flag, int fini); /* * sessionLock, wait4session, sessionTid * are variables to synchronize the creation/retrieval of a connection. * MTperCon is a flag to enable/disable multiple threads sharing the same * connection. * sessionPoolLock is a mutex lock for the connection pool. * sharedConnNumber is the number of sharable connections in the pool. * sharedConnNumberLock is a mutex for sharedConnNumber. */ static mutex_t sessionLock = DEFAULTMUTEX; static int wait4session = 0; static thread_t sessionTid = 0; int MTperConn = 1; static rwlock_t sessionPoolLock = DEFAULTRWLOCK; static Connection **sessionPool = NULL; static int sessionPoolSize = 0; static int sharedConnNumber = 0; static mutex_t sharedConnNumberLock = DEFAULTMUTEX; static int check_nscd_proc(pid_t pid, boolean_t check_uid); /* * SSF values are for SASL integrity & privacy. * JES DS5.2 does not support this feature but DS6 does. * The values between 0 and 65535 can work with both server versions. */ #define MAX_SASL_SSF 65535 #define MIN_SASL_SSF 0 /* Number of hostnames to allocate memory for */ #define NUMTOMALLOC 32 /* * ns_mtckey is for sharing a ldap connection among multiple * threads; created by ns_ldap_init() in ns_init.c */ extern thread_key_t ns_mtckey; /* Per thread LDAP error resides in thread-specific data. */ struct ldap_error { int le_errno; char *le_matched; char *le_errmsg; }; static struct ldap_error ldap_error_NULL = { LDAP_SUCCESS, NULL, NULL}; /* destructor */ void ns_tsd_cleanup(void *key) { struct ldap_error *le = (struct ldap_error *)key; if (le == NULL) return; if (le->le_matched != NULL) { ldap_memfree(le->le_matched); } if (le->le_errmsg != NULL) { ldap_memfree(le->le_errmsg); } free(le); } /* Callback function for allocating a mutex */ static void * ns_mutex_alloc(void) { mutex_t *mutexp = NULL; if ((mutexp = malloc(sizeof (mutex_t))) != NULL) { if (mutex_init(mutexp, USYNC_THREAD, NULL) != 0) { free(mutexp); mutexp = NULL; } } return (mutexp); } /* Callback function for freeing a mutex */ static void ns_mutex_free(void *mutexp) { (void) mutex_destroy((mutex_t *)mutexp); free(mutexp); } /* * Function for setting up thread-specific data * where per thread LDAP error is stored */ static int tsd_setup() { void *tsd; int rc; /* return success if TSD already set */ rc = thr_getspecific(ns_mtckey, &tsd); if (rc == 0 && tsd != NULL) return (0); /* allocate and set TSD */ tsd = (void *) calloc(1, sizeof (struct ldap_error)); if (tsd == NULL) return (-1); rc = thr_setspecific(ns_mtckey, tsd); if (rc != 0) { /* must be ENOMEM */ free(tsd); return (-1); } return (0); } /* Callback function for setting the per thread LDAP error */ /*ARGSUSED*/ static void set_ld_error(int err, char *matched, char *errmsg, void *dummy) { struct ldap_error *le; if (thr_getspecific(ns_mtckey, (void **)&le) != 0) { syslog(LOG_ERR, "set_ld_error: thr_getspecific failed. errno" " %d", errno); return; } /* play safe, do nothing if TSD pointer is NULL */ if (le == NULL) return; le->le_errno = err; if (le->le_matched != NULL) { ldap_memfree(le->le_matched); } le->le_matched = matched; if (le->le_errmsg != NULL) { ldap_memfree(le->le_errmsg); } le->le_errmsg = errmsg; } int /* check and allocate the thread-specific data for using a shared connection */ __s_api_check_MTC_tsd() { if (tsd_setup() != 0) return (NS_LDAP_MEMORY); return (NS_LDAP_SUCCESS); } /* Callback function for getting the per thread LDAP error */ /*ARGSUSED*/ static int get_ld_error(char **matched, char **errmsg, void *dummy) { struct ldap_error *le; if (thr_getspecific(ns_mtckey, (void **)&le) != 0) { syslog(LOG_ERR, "get_ld_error: thr_getspecific failed. errno" " %d", errno); return (errno); } /* play safe, return NULL error data, if TSD pointer is NULL */ if (le == NULL) le = &ldap_error_NULL; if (matched != NULL) { *matched = le->le_matched; } if (errmsg != NULL) { *errmsg = le->le_errmsg; } return (le->le_errno); } /* Callback function for setting per thread errno */ static void set_errno(int err) { errno = err; } /* Callback function for getting per thread errno */ static int get_errno(void) { return (errno); } /* * set up to allow multiple threads to use the same ldap connection */ static int setup_mt_conn(LDAP *ld) { struct ldap_thread_fns tfns; struct ldap_extra_thread_fns extrafns; int rc; /* * Set the function pointers for dealing with mutexes * and error information */ (void) memset(&tfns, '\0', sizeof (struct ldap_thread_fns)); tfns.ltf_mutex_alloc = (void *(*)(void)) ns_mutex_alloc; tfns.ltf_mutex_free = (void (*)(void *)) ns_mutex_free; tfns.ltf_mutex_lock = (int (*)(void *)) mutex_lock; tfns.ltf_mutex_unlock = (int (*)(void *)) mutex_unlock; tfns.ltf_get_errno = get_errno; tfns.ltf_set_errno = set_errno; tfns.ltf_get_lderrno = get_ld_error; tfns.ltf_set_lderrno = set_ld_error; tfns.ltf_lderrno_arg = NULL; /* * Set up this session to use those function pointers */ rc = ldap_set_option(ld, LDAP_OPT_THREAD_FN_PTRS, (void *) &tfns); if (rc < 0) { syslog(LOG_WARNING, "libsldap: ldap_set_option " "(LDAP_OPT_THREAD_FN_PTRS)"); return (-1); } /* * Set the function pointers for working with semaphores */ (void) memset(&extrafns, '\0', sizeof (struct ldap_extra_thread_fns)); extrafns.ltf_threadid_fn = (void * (*)(void))thr_self; extrafns.ltf_mutex_trylock = NULL; extrafns.ltf_sema_alloc = NULL; extrafns.ltf_sema_free = NULL; extrafns.ltf_sema_wait = NULL; extrafns.ltf_sema_post = NULL; /* Set up this session to use those function pointers */ rc = ldap_set_option(ld, LDAP_OPT_EXTRA_THREAD_FN_PTRS, (void *) &extrafns); if (rc < 0) { syslog(LOG_WARNING, "libsldap: ldap_set_option " "(LDAP_OPT_EXTRA_THREAD_FN_PTRS)"); return (-1); } return (0); } static void ns_setup_mt_conn_and_tsd(LDAP *ld) { thread_t t = thr_self(); void *tsd; /* set up to share this connection among threads */ if (MTperConn == 1) { if (tsd_setup() == -1) { syslog(LOG_ERR, "tid= %d: unable " "to set up TSD\n", t); } else { if (setup_mt_conn(ld) == -1) { /* multiple threads per connection not supported */ syslog(LOG_ERR, "tid= %d: multiple " "threads per connection not " "supported\n", t); (void) thr_getspecific(ns_mtckey, &tsd); ns_tsd_cleanup(tsd); (void) thr_setspecific(ns_mtckey, NULL); MTperConn = 0; } } } } /* * Check name and UID of process, if it is nscd. * * Input: * pid : PID of checked process * check_uid : check if UID == 0 * Output: * 1 : nscd detected * 0 : nscd not confirmed */ static int check_nscd_proc(pid_t pid, boolean_t check_uid) { psinfo_t pinfo; char fname[MAXPATHLEN]; ssize_t ret; int fd; if (snprintf(fname, MAXPATHLEN, "/proc/%d/psinfo", pid) > 0) { if ((fd = open(fname, O_RDONLY)) >= 0) { ret = read(fd, &pinfo, sizeof (psinfo_t)); (void) close(fd); if ((ret == sizeof (psinfo_t)) && (strcmp(pinfo.pr_fname, "nscd") == 0)) { if (check_uid && (pinfo.pr_uid != 0)) return (0); return (1); } } } return (0); } /* * Check if this process is peruser nscd. */ int __s_api_peruser_proc(void) { pid_t my_ppid; static mutex_t nscdLock = DEFAULTMUTEX; static pid_t checkedPpid = (pid_t)-1; static int isPeruserNscd = 0; my_ppid = getppid(); /* * Already checked before for this process? If yes, return cached * response. */ if (my_ppid == checkedPpid) { return (isPeruserNscd); } (void) mutex_lock(&nscdLock); /* Check once more incase another thread has just complete this. */ if (my_ppid == checkedPpid) { (void) mutex_unlock(&nscdLock); return (isPeruserNscd); } /* Reinitialize to be sure there is no residue after fork. */ isPeruserNscd = 0; /* Am I the nscd process? */ if (check_nscd_proc(getpid(), B_FALSE)) { /* Is my parent the nscd process with UID == 0. */ isPeruserNscd = check_nscd_proc(my_ppid, B_TRUE); } /* Remeber for whom isPeruserNscd is. */ checkedPpid = my_ppid; (void) mutex_unlock(&nscdLock); return (isPeruserNscd); } /* * Check if this process is main nscd. */ int __s_api_nscd_proc(void) { pid_t my_pid; static mutex_t nscdLock = DEFAULTMUTEX; static pid_t checkedPid = (pid_t)-1; static int isMainNscd = 0; /* * Don't bother checking if this process isn't root, this cannot * be main nscd. */ if (getuid() != 0) return (0); my_pid = getpid(); /* * Already checked before for this process? If yes, return cached * response. */ if (my_pid == checkedPid) { return (isMainNscd); } (void) mutex_lock(&nscdLock); /* Check once more incase another thread has just done this. */ if (my_pid == checkedPid) { (void) mutex_unlock(&nscdLock); return (isMainNscd); } /* * Am I the nscd process? UID is already checked, not needed from * psinfo. */ isMainNscd = check_nscd_proc(my_pid, B_FALSE); /* Remeber for whom isMainNscd is. */ checkedPid = my_pid; (void) mutex_unlock(&nscdLock); return (isMainNscd); } /* * This function requests a server from the cache manager through * the door functionality */ static int __s_api_requestServer(const char *request, const char *server, ns_server_info_t *ret, ns_ldap_error_t **error, const char *addrType) { union { ldap_data_t s_d; char s_b[DOORBUFFERSIZE]; } space; ldap_data_t *sptr; int ndata; int adata; char errstr[MAXERROR]; const char *ireq; char *rbuf, *ptr, *rest; char *dptr; char **mptr, **mptr1, **cptr, **cptr1; int mcnt, ccnt; char **servers; int rc, len; if (ret == NULL || error == NULL) { return (NS_LDAP_OP_FAILED); } (void) memset(ret, 0, sizeof (ns_server_info_t)); *error = NULL; (void) memset(space.s_b, 0, DOORBUFFERSIZE); if (request == NULL) ireq = NS_CACHE_NEW; else ireq = request; adata = (sizeof (ldap_call_t) + strlen(ireq) + strlen(addrType) + 1); if (server != NULL) { adata += strlen(DOORLINESEP) + 1; adata += strlen(server) + 1; } ndata = sizeof (space); len = sizeof (space) - sizeof (space.s_d.ldap_call.ldap_callnumber); space.s_d.ldap_call.ldap_callnumber = GETLDAPSERVER; if (strlcpy(space.s_d.ldap_call.ldap_u.domainname, ireq, len) >= len) return (NS_LDAP_MEMORY); if (strlcat(space.s_d.ldap_call.ldap_u.domainname, addrType, len) >= len) return (NS_LDAP_MEMORY); if (server != NULL) { if (strlcat(space.s_d.ldap_call.ldap_u.domainname, DOORLINESEP, len) >= len) return (NS_LDAP_MEMORY); if (strlcat(space.s_d.ldap_call.ldap_u.domainname, server, len) >= len) return (NS_LDAP_MEMORY); } sptr = &space.s_d; switch (__ns_ldap_trydoorcall(&sptr, &ndata, &adata)) { case SUCCESS: break; /* this case is for when the $mgr is not running, but ldapclient */ /* is trying to initialize things */ case NOSERVER: /* get first server from config list unavailable otherwise */ servers = NULL; rc = __s_api_getServers(&servers, error); if (rc != NS_LDAP_SUCCESS) { if (servers != NULL) { __s_api_free2dArray(servers); servers = NULL; } return (rc); } if (servers == NULL || servers[0] == NULL) { __s_api_free2dArray(servers); servers = NULL; (void) sprintf(errstr, gettext("No server found in configuration")); MKERROR(LOG_ERR, *error, NS_CONFIG_NODEFAULT, strdup(errstr), NULL); return (NS_LDAP_CONFIG); } ret->server = strdup(servers[0]); if (ret->server == NULL) { __s_api_free2dArray(servers); return (NS_LDAP_MEMORY); } ret->saslMechanisms = NULL; ret->controls = NULL; __s_api_free2dArray(servers); servers = NULL; return (NS_LDAP_SUCCESS); case NOTFOUND: default: return (NS_LDAP_OP_FAILED); } /* copy info from door call return structure here */ rbuf = space.s_d.ldap_ret.ldap_u.config; /* Get the host */ ptr = strtok_r(rbuf, DOORLINESEP, &rest); if (ptr == NULL) { (void) sprintf(errstr, gettext("No server returned from " "ldap_cachemgr")); MKERROR(LOG_WARNING, *error, NS_CONFIG_CACHEMGR, strdup(errstr), NULL); return (NS_LDAP_OP_FAILED); } ret->server = strdup(ptr); if (ret->server == NULL) { return (NS_LDAP_MEMORY); } /* Get the host FQDN format */ if (strcmp(addrType, NS_CACHE_ADDR_HOSTNAME) == 0) { ptr = strtok_r(NULL, DOORLINESEP, &rest); if (ptr == NULL) { (void) sprintf(errstr, gettext("No server FQDN format " "returned from ldap_cachemgr")); MKERROR(LOG_WARNING, *error, NS_CONFIG_CACHEMGR, strdup(errstr), NULL); free(ret->server); ret->server = NULL; return (NS_LDAP_OP_FAILED); } ret->serverFQDN = strdup(ptr); if (ret->serverFQDN == NULL) { free(ret->server); ret->server = NULL; return (NS_LDAP_MEMORY); } } /* get the Supported Controls/SASL mechs */ mptr = NULL; mcnt = 0; cptr = NULL; ccnt = 0; for (; ; ) { ptr = strtok_r(NULL, DOORLINESEP, &rest); if (ptr == NULL) break; if (strncasecmp(ptr, _SASLMECHANISM, _SASLMECHANISM_LEN) == 0) { dptr = strchr(ptr, '='); if (dptr == NULL) continue; dptr++; mptr1 = (char **)realloc((void *)mptr, sizeof (char *) * (mcnt+2)); if (mptr1 == NULL) { __s_api_free2dArray(mptr); if (sptr != &space.s_d) { (void) munmap((char *)sptr, ndata); } __s_api_free2dArray(cptr); __s_api_free_server_info(ret); return (NS_LDAP_MEMORY); } mptr = mptr1; mptr[mcnt] = strdup(dptr); if (mptr[mcnt] == NULL) { if (sptr != &space.s_d) { (void) munmap((char *)sptr, ndata); } __s_api_free2dArray(cptr); cptr = NULL; __s_api_free2dArray(mptr); mptr = NULL; __s_api_free_server_info(ret); return (NS_LDAP_MEMORY); } mcnt++; mptr[mcnt] = NULL; } if (strncasecmp(ptr, _SUPPORTEDCONTROL, _SUPPORTEDCONTROL_LEN) == 0) { dptr = strchr(ptr, '='); if (dptr == NULL) continue; dptr++; cptr1 = (char **)realloc((void *)cptr, sizeof (char *) * (ccnt+2)); if (cptr1 == NULL) { if (sptr != &space.s_d) { (void) munmap((char *)sptr, ndata); } __s_api_free2dArray(cptr); __s_api_free2dArray(mptr); mptr = NULL; __s_api_free_server_info(ret); return (NS_LDAP_MEMORY); } cptr = cptr1; cptr[ccnt] = strdup(dptr); if (cptr[ccnt] == NULL) { if (sptr != &space.s_d) { (void) munmap((char *)sptr, ndata); } __s_api_free2dArray(cptr); cptr = NULL; __s_api_free2dArray(mptr); mptr = NULL; __s_api_free_server_info(ret); return (NS_LDAP_MEMORY); } ccnt++; cptr[ccnt] = NULL; } } if (mptr != NULL) { ret->saslMechanisms = mptr; } if (cptr != NULL) { ret->controls = cptr; } /* clean up door call */ if (sptr != &space.s_d) { (void) munmap((char *)sptr, ndata); } *error = NULL; return (NS_LDAP_SUCCESS); } /* * printCred(): prints the credential structure */ static void printCred(int pri, const ns_cred_t *cred) { thread_t t = thr_self(); if (cred == NULL) { syslog(LOG_ERR, "tid= %d: printCred: cred is NULL\n", t); return; } syslog(pri, "tid= %d: AuthType=%d", t, cred->auth.type); syslog(pri, "tid= %d: TlsType=%d", t, cred->auth.tlstype); syslog(pri, "tid= %d: SaslMech=%d", t, cred->auth.saslmech); syslog(pri, "tid= %d: SaslOpt=%d", t, cred->auth.saslopt); if (cred->hostcertpath) syslog(pri, "tid= %d: hostCertPath=%s\n", t, cred->hostcertpath); if (cred->cred.unix_cred.userID) syslog(pri, "tid= %d: userID=%s\n", t, cred->cred.unix_cred.userID); #ifdef DEBUG if (cred->cred.unix_cred.passwd) syslog(pri, "tid= %d: passwd=%s\n", t, cred->cred.unix_cred.passwd); #endif } /* * printConnection(): prints the connection structure */ static void printConnection(int pri, Connection *con) { thread_t t = thr_self(); if (con == NULL) return; syslog(pri, "tid= %d: connectionID=%d\n", t, con->connectionId); syslog(pri, "tid= %d: shared=%d\n", t, con->shared); syslog(pri, "tid= %d: usedBit=%d\n", t, con->usedBit); syslog(pri, "tid= %d: threadID=%d\n", t, con->threadID); if (con->serverAddr) { syslog(pri, "tid= %d: serverAddr=%s\n", t, con->serverAddr); } printCred(pri, con->auth); } /* * addConnection(): set up a connection so that it can be shared * among multiple threads and then insert the connection in the * connection list. * Returns: -1 = failure, new Connection ID = success * * This function could exit with sessionLock locked. It will be * be unlocked in __s_api_getConnection() when it exits without getting a * connection. */ static int addConnection(Connection *con) { int i, noMTperC = 0; thread_t t = thr_self(); struct ldap_thread_fns tfns; void *tsd; if (!con) return (-1); syslog(LOG_DEBUG, "tid= %d: Adding connection (serverAddr=%s)", t, con->serverAddr); if (MTperConn == 1) { /* * Make sure ld has proper thread functions and tsd * is set up. */ (void) memset(&tfns, 0, sizeof (struct ldap_thread_fns)); /* * ldap_init sets ltf_get_lderrno and ltf_set_lderrno to NULLs. * It's supposed to be overwritten by ns_setup_mt_conn_and_tsd. */ if (ldap_get_option(con->ld, LDAP_OPT_THREAD_FN_PTRS, (void *)&tfns) != 0 || tfns.ltf_get_lderrno != get_ld_error || tfns.ltf_set_lderrno != set_ld_error) { MTperConn = 0; noMTperC = 1; } else { if (thr_getspecific(ns_mtckey, &tsd) != 0 || tsd == NULL) noMTperC = 1; } } else { noMTperC = 1; } (void) rw_wrlock(&sessionPoolLock); if (sessionPool == NULL) { sessionPoolSize = SESSION_CACHE_INC; sessionPool = calloc(sessionPoolSize, sizeof (struct connection **)); if (!sessionPool) { (void) rw_unlock(&sessionPoolLock); return (-1); } syslog(LOG_DEBUG, "tid= %d: Initialized sessionPool", t); } for (i = 0; (i < sessionPoolSize) && (sessionPool[i] != NULL); ++i) ; if (i == sessionPoolSize) { /* run out of array, need to increase sessionPool */ Connection **cl; cl = (Connection **) realloc(sessionPool, (sessionPoolSize + SESSION_CACHE_INC) * sizeof (Connection *)); if (!cl) { (void) rw_unlock(&sessionPoolLock); return (-1); } (void) memset(cl + sessionPoolSize, 0, SESSION_CACHE_INC * sizeof (struct connection *)); sessionPool = cl; sessionPoolSize += SESSION_CACHE_INC; syslog(LOG_DEBUG, "tid: %d: Increased " "sessionPoolSize to: %d\n", t, sessionPoolSize); } sessionPool[i] = con; if (noMTperC == 0) { con->shared++; con->pid = getpid(); (void) mutex_lock(&sharedConnNumberLock); sharedConnNumber++; (void) mutex_unlock(&sharedConnNumberLock); } else con->usedBit = B_TRUE; (void) rw_unlock(&sessionPoolLock); con->connectionId = i + CONID_OFFSET; syslog(LOG_DEBUG, "tid= %d: Connection added [%d]\n", t, i); printConnection(LOG_DEBUG, con); /* * A connection can be shared now, unlock * the session mutex and let other * threads try to use this connection or * get their own. */ if (wait4session != 0 && sessionTid == thr_self()) { wait4session = 0; sessionTid = 0; syslog(LOG_DEBUG, "tid= %d: unlocking sessionLock\n", t); (void) mutex_unlock(&sessionLock); } return (i + CONID_OFFSET); } /* * See if the specified session matches a currently available */ static int findConnectionById(int flags, const ns_cred_t *auth, ConnectionID cID, Connection **conp) { Connection *cp; int id; if ((conp == NULL) || (auth == NULL) || cID < CONID_OFFSET) return (-1); /* * If a new connection is requested, no need to continue. * If the process is not nscd and is not requesting keep connections * alive, no need to continue. */ if ((flags & NS_LDAP_NEW_CONN) || (!__s_api_nscd_proc() && !__s_api_peruser_proc() && !(flags & NS_LDAP_KEEP_CONN))) return (-1); *conp = NULL; if (sessionPool == NULL) return (-1); id = cID - CONID_OFFSET; if (id < 0 || id >= sessionPoolSize) return (-1); (void) rw_rdlock(&sessionPoolLock); if (sessionPool[id] == NULL) { (void) rw_unlock(&sessionPoolLock); return (-1); } cp = sessionPool[id]; /* * Make sure the connection has the same type of authentication method */ if ((cp->usedBit) || (cp->notAvail) || (cp->auth->auth.type != auth->auth.type) || (cp->auth->auth.tlstype != auth->auth.tlstype) || (cp->auth->auth.saslmech != auth->auth.saslmech) || (cp->auth->auth.saslopt != auth->auth.saslopt)) { (void) rw_unlock(&sessionPoolLock); return (-1); } if ((((cp->auth->auth.type == NS_LDAP_AUTH_SASL) && ((cp->auth->auth.saslmech == NS_LDAP_SASL_CRAM_MD5) || (cp->auth->auth.saslmech == NS_LDAP_SASL_DIGEST_MD5))) || (cp->auth->auth.type == NS_LDAP_AUTH_SIMPLE)) && ((cp->auth->cred.unix_cred.userID == NULL) || (strcasecmp(cp->auth->cred.unix_cred.userID, auth->cred.unix_cred.userID) != 0))) { (void) rw_unlock(&sessionPoolLock); return (-1); } /* An existing connection is found but it needs to be reset */ if (flags & NS_LDAP_NEW_CONN) { (void) rw_unlock(&sessionPoolLock); DropConnection(cID, 0); return (-1); } /* found an available connection */ cp->usedBit = B_TRUE; (void) rw_unlock(&sessionPoolLock); cp->threadID = thr_self(); *conp = cp; return (cID); } /* * findConnection(): find an available connection from the list * that matches the criteria specified in Connection structure. * If serverAddr is NULL, then find a connection to any server * as long as it matches the rest of the parameters. * Returns: -1 = failure, the Connection ID found = success. * * This function could exit with sessionLock locked. It will be * be unlocked in addConnection() when this thread adds the connection * to the pool or in __s_api_getConnection() when it exits without getting a * connection. */ #define TRY_TIMES 10 static int findConnection(int flags, const char *serverAddr, const ns_cred_t *auth, Connection **conp) { Connection *cp; int i, j, conn_server_index, up_server_index, drop_conn; int rc; int try; ns_server_info_t sinfo; ns_ldap_error_t *errorp = NULL; char **servers; void **paramVal = NULL; #ifdef DEBUG thread_t t = thr_self(); #endif /* DEBUG */ if (auth == NULL || conp == NULL) return (-1); *conp = NULL; /* * If a new connection is requested, no need to continue. * If the process is not nscd and is not requesting keep connections * alive, no need to continue. */ if ((flags & NS_LDAP_NEW_CONN) || (!__s_api_nscd_proc() && !__s_api_peruser_proc() && !(flags & NS_LDAP_KEEP_CONN))) return (-1); #ifdef DEBUG (void) fprintf(stderr, "tid= %d: Find connection\n", t); (void) fprintf(stderr, "tid= %d: Looking for ....\n", t); if (serverAddr && *serverAddr) (void) fprintf(stderr, "tid= %d: serverAddr=%s\n", t, serverAddr); else (void) fprintf(stderr, "tid= %d: serverAddr=NULL\n", t); printCred(LOG_DEBUG, auth); fflush(stderr); #endif /* DEBUG */ /* * If multiple threads per connection not supported, * no sessionPool means no connection */ (void) rw_rdlock(&sessionPoolLock); if (MTperConn == 0 && sessionPool == NULL) { (void) rw_unlock(&sessionPoolLock); return (-1); } /* * If no sharable connections in cache, then serialize the opening * of connections. Make sure only one is being opened * at a time. Otherwise, we may end up with more * connections than we want (if multiple threads get * here at the same time) */ (void) mutex_lock(&sharedConnNumberLock); if (sessionPool == NULL || (sharedConnNumber == 0 && MTperConn == 1)) { (void) mutex_unlock(&sharedConnNumberLock); (void) rw_unlock(&sessionPoolLock); (void) mutex_lock(&sessionLock); (void) mutex_lock(&sharedConnNumberLock); if (sessionPool == NULL || (sharedConnNumber == 0 && MTperConn == 1)) { (void) mutex_unlock(&sharedConnNumberLock); wait4session = 1; sessionTid = thr_self(); #ifdef DEBUG (void) fprintf(stderr, "tid= %d: get " "connection ... \n", t); fflush(stderr); #endif /* DEBUG */ /* * Exit with sessionLock locked. It will be * be unlocked in addConnection() when this * thread adds the connection to the pool or * in __s_api_getConnection() when it exits * without getting a connection. */ return (-1); } #ifdef DEBUG (void) fprintf(stderr, "tid= %d: shareable connections " "exist\n", t); fflush(stderr); #endif /* DEBUG */ (void) mutex_unlock(&sharedConnNumberLock); /* * There are sharable connections, check to see if * one can be shared. */ (void) mutex_unlock(&sessionLock); (void) rw_rdlock(&sessionPoolLock); } else (void) mutex_unlock(&sharedConnNumberLock); try = 0; check_again: for (i = 0; i < sessionPoolSize; ++i) { if (sessionPool[i] == NULL) continue; cp = sessionPool[i]; #ifdef DEBUG (void) fprintf(stderr, "tid= %d: checking connection " "[%d] ....\n", t, i); printConnection(LOG_DEBUG, cp); #endif /* DEBUG */ if ((cp->usedBit) || (cp->notAvail) || (cp->auth->auth.type != auth->auth.type) || (cp->auth->auth.tlstype != auth->auth.tlstype) || (cp->auth->auth.saslmech != auth->auth.saslmech) || (cp->auth->auth.saslopt != auth->auth.saslopt) || (serverAddr && *serverAddr && (strcasecmp(serverAddr, cp->serverAddr) != 0))) continue; if ((((cp->auth->auth.type == NS_LDAP_AUTH_SASL) && ((cp->auth->auth.saslmech == NS_LDAP_SASL_CRAM_MD5) || (cp->auth->auth.saslmech == NS_LDAP_SASL_DIGEST_MD5))) || (cp->auth->auth.type == NS_LDAP_AUTH_SIMPLE)) && ((cp->auth->cred.unix_cred.userID == NULL) || (cp->auth->cred.unix_cred.passwd == NULL) || ((strcasecmp(cp->auth->cred.unix_cred.userID, auth->cred.unix_cred.userID) != 0)) || ((strcmp(cp->auth->cred.unix_cred.passwd, auth->cred.unix_cred.passwd) != 0)))) continue; if (!(serverAddr && *serverAddr)) { /* * Get preferred server list. * When preferred servers are merged with default * servers (S_LDAP_SERVER_P) by __s_api_getServer, * preferred servers are copied sequencially. * The order should be the same as the order retrieved * by __ns_ldap_getParam. */ if ((rc = __ns_ldap_getParam(NS_LDAP_SERVER_PREF_P, ¶mVal, &errorp)) != NS_LDAP_SUCCESS) { (void) __ns_ldap_freeError(&errorp); (void) __ns_ldap_freeParam(¶mVal); (void) rw_unlock(&sessionPoolLock); return (-1); } servers = (char **)paramVal; /* * Do fallback only if preferred servers are defined. */ if (servers != NULL) { /* * Find the 1st available server */ rc = __s_api_requestServer(NS_CACHE_NEW, NULL, &sinfo, &errorp, NS_CACHE_ADDR_IP); if (rc != NS_LDAP_SUCCESS) { /* * Drop the connection. * Pass 1 to fini so it won't be locked * inside _DropConnection */ _DropConnection( cp->connectionId, NS_LDAP_NEW_CONN, 1); (void) rw_unlock( &sessionPoolLock); (void) __ns_ldap_freeError(&errorp); (void) __ns_ldap_freeParam( (void ***)&servers); return (-1); } if (sinfo.server) { /* * Test if cp->serverAddr is a * preferred server. */ conn_server_index = -1; for (j = 0; servers[j] != NULL; j++) { if (strcasecmp(servers[j], cp->serverAddr) == 0) { conn_server_index = j; break; } } /* * Test if sinfo.server is a preferred * server. */ up_server_index = -1; for (j = 0; servers[j] != NULL; j++) { if (strcasecmp(sinfo.server, servers[j]) == 0) { up_server_index = j; break; } } /* * The following code is to fall back * to preferred servers if servers * are previously down but are up now. * If cp->serverAddr is a preferred * server, it falls back to the servers * ahead of it. If cp->serverAddr is * not a preferred server, it falls * back to any of preferred servers * returned by ldap_cachemgr. */ if (conn_server_index >= 0 && up_server_index >= 0) { /* * cp->serverAddr and * sinfo.server are preferred * servers. */ if (up_server_index == conn_server_index) /* * sinfo.server is the * same as * cp->serverAddr. * Keep the connection. */ drop_conn = 0; else /* * 1. * up_server_index < * conn_server_index * * sinfo is ahead of * cp->serverAddr in * Need to fall back. * 2. * up_server_index > * conn_server_index * cp->serverAddr is * down. Drop it. */ drop_conn = 1; } else if (conn_server_index >= 0 && up_server_index == -1) { /* * cp->serverAddr is a preferred * server but sinfo.server is * not. Preferred servers are * ahead of default servers. * This means cp->serverAddr is * down. Drop it. */ drop_conn = 1; } else if (conn_server_index == -1 && up_server_index >= 0) { /* * cp->serverAddr is not a * preferred server but * sinfo.server is. * Fall back. */ drop_conn = 1; } else { /* * Both index are -1 * cp->serverAddr and * sinfo.server are not * preferred servers. * No fallback. */ drop_conn = 0; } if (drop_conn) { /* * Drop the connection so the * new conection can fall back * to a new server later. * Pass 1 to fini so it won't * be locked inside * _DropConnection */ _DropConnection( cp->connectionId, NS_LDAP_NEW_CONN, 1); (void) rw_unlock( &sessionPoolLock); (void) __ns_ldap_freeParam( (void ***)&servers); __s_api_free_server_info( &sinfo); return (-1); } else { /* * Keep the connection */ (void) __ns_ldap_freeParam( (void ***)&servers); __s_api_free_server_info( &sinfo); } } else { (void) rw_unlock(&sessionPoolLock); syslog(LOG_WARNING, "libsldap: Null " "sinfo.server from " "__s_api_requestServer"); (void) __ns_ldap_freeParam( (void ***)&servers); return (-1); } } } /* found an available connection */ if (MTperConn == 0) cp->usedBit = B_TRUE; else { /* * if connection was established in a different * process, drop it and get a new one */ if (cp->pid != getpid()) { (void) rw_unlock(&sessionPoolLock); DropConnection(cp->connectionId, NS_LDAP_NEW_CONN); goto get_conn; } /* allocate TSD for per thread ldap error */ rc = tsd_setup(); /* if we got TSD, this connection is shared */ if (rc != -1) cp->shared++; else if (cp->shared == 0) { cp->usedBit = B_TRUE; cp->threadID = thr_self(); (void) rw_unlock(&sessionPoolLock); return (-1); } } (void) rw_unlock(&sessionPoolLock); *conp = cp; #ifdef DEBUG (void) fprintf(stderr, "tid= %d: Connection found " "cID=%d, shared =%d\n", t, i, cp->shared); fflush(stderr); #endif /* DEBUG */ return (i + CONID_OFFSET); } get_conn: (void) rw_unlock(&sessionPoolLock); /* * If multiple threads per connection not supported, * we are done, just return -1 to tell the caller to * proceed with opening a connection */ if (MTperConn == 0) return (-1); /* * No connection can be shared, test to see if * one is being opened. If trylock returns * EBUSY then it is, so wait until the opening * is done and try to see if the new connection * can be shared. */ rc = mutex_trylock(&sessionLock); if (rc == EBUSY) { (void) mutex_lock(&sessionLock); (void) mutex_unlock(&sessionLock); (void) rw_rdlock(&sessionPoolLock); #ifdef DEBUG (void) fprintf(stderr, "tid= %d: check session " "pool again\n", t); fflush(stderr); #endif /* DEBUG */ if (try < TRY_TIMES) { try++; goto check_again; } else { syslog(LOG_WARNING, "libsldap: mutex_trylock " "%d times. Stop.", TRY_TIMES); (void) rw_unlock(&sessionPoolLock); return (-1); } } else if (rc == 0) { /* * No connection can be shared, none being opened, * exit with sessionLock locked to open one. The * mutex will be unlocked in addConnection() when * this thread adds the new connection to the pool * or in __s_api_getConnection() when it exits * without getting a connection. */ wait4session = 1; sessionTid = thr_self(); #ifdef DEBUG (void) fprintf(stderr, "tid= %d: no connection found, " "none being opened, get connection ...\n", t); fflush(stderr); #endif /* DEBUG */ return (-1); } else { syslog(LOG_WARNING, "libsldap: mutex_trylock unexpected " "error %d", rc); return (-1); } } /* * Free a Connection structure */ static void freeConnection(Connection *con) { if (con == NULL) return; if (con->serverAddr) free(con->serverAddr); if (con->auth) (void) __ns_ldap_freeCred(&(con->auth)); if (con->saslMechanisms) { __s_api_free2dArray(con->saslMechanisms); } if (con->controls) { __s_api_free2dArray(con->controls); } free(con); } /* * Find a connection matching the passed in criteria. If an open * connection with that criteria exists use it, otherwise open a * new connection. * Success: returns the pointer to the Connection structure * Failure: returns NULL, error code and message should be in errorp */ static int makeConnection(Connection **conp, const char *serverAddr, const ns_cred_t *auth, ConnectionID *cID, int timeoutSec, ns_ldap_error_t **errorp, int fail_if_new_pwd_reqd, int nopasswd_acct_mgmt, int flags, char ***badsrvrs) { Connection *con = NULL; ConnectionID id; char errmsg[MAXERROR]; int rc, exit_rc = NS_LDAP_SUCCESS; ns_server_info_t sinfo; char *hReq, *host = NULL; LDAP *ld = NULL; int passwd_mgmt = 0; int totalbad = 0; /* Number of servers contacted unsuccessfully */ short memerr = 0; /* Variable for tracking memory allocation */ char *serverAddrType = NULL, **bindHost = NULL; if (conp == NULL || errorp == NULL || auth == NULL) return (NS_LDAP_INVALID_PARAM); *errorp = NULL; *conp = NULL; (void) memset(&sinfo, 0, sizeof (sinfo)); if ((wait4session == 0 || sessionTid != thr_self()) && (id = findConnection(flags, serverAddr, auth, &con)) != -1) { /* connection found in cache */ #ifdef DEBUG (void) fprintf(stderr, "tid= %d: connection found in " "cache %d\n", thr_self(), id); fflush(stderr); #endif /* DEBUG */ *cID = id; *conp = con; return (NS_LDAP_SUCCESS); } if (auth->auth.saslmech == NS_LDAP_SASL_GSSAPI) { serverAddrType = NS_CACHE_ADDR_HOSTNAME; bindHost = &sinfo.serverFQDN; } else { serverAddrType = NS_CACHE_ADDR_IP; bindHost = &sinfo.server; } if (serverAddr) { /* * We're given the server address, just use it. * In case of sasl/GSSAPI, serverAddr would need to be a FQDN. * We assume this is the case for now. * * Only the server address fields of sinfo structure are filled * in since these are the only relevant data that we have. Other * fields of this structure (controls, saslMechanisms) are * kept to NULL. */ sinfo.server = strdup(serverAddr); if (sinfo.server == NULL) { return (NS_LDAP_MEMORY); } if (auth->auth.saslmech == NS_LDAP_SASL_GSSAPI) { sinfo.serverFQDN = strdup(serverAddr); if (sinfo.serverFQDN == NULL) { free(sinfo.server); return (NS_LDAP_MEMORY); } } rc = openConnection(&ld, *bindHost, auth, timeoutSec, errorp, fail_if_new_pwd_reqd, passwd_mgmt); if (rc == NS_LDAP_SUCCESS || rc == NS_LDAP_SUCCESS_WITH_INFO) { exit_rc = rc; goto create_con; } else { if (auth->auth.saslmech == NS_LDAP_SASL_GSSAPI) { (void) snprintf(errmsg, sizeof (errmsg), "%s %s", gettext("makeConnection: " "failed to open connection using " "sasl/GSSAPI to"), *bindHost); } else { (void) snprintf(errmsg, sizeof (errmsg), "%s %s", gettext("makeConnection: " "failed to open connection to"), *bindHost); } syslog(LOG_ERR, "libsldap: %s", errmsg); __s_api_free_server_info(&sinfo); return (rc); } } /* No cached connection, create one */ for (; ; ) { if (host == NULL) hReq = NS_CACHE_NEW; else hReq = NS_CACHE_NEXT; rc = __s_api_requestServer(hReq, host, &sinfo, errorp, serverAddrType); if ((rc != NS_LDAP_SUCCESS) || (sinfo.server == NULL) || (host && (strcasecmp(host, sinfo.server) == 0))) { /* Log the error */ if (*errorp) { (void) snprintf(errmsg, sizeof (errmsg), "%s: (%s)", gettext("makeConnection: " "unable to make LDAP connection, " "request for a server failed"), (*errorp)->message); syslog(LOG_ERR, "libsldap: %s", errmsg); } __s_api_free_server_info(&sinfo); if (host) free(host); return (NS_LDAP_OP_FAILED); } if (host) free(host); host = strdup(sinfo.server); if (host == NULL) { __s_api_free_server_info(&sinfo); return (NS_LDAP_MEMORY); } /* check if server supports password management */ passwd_mgmt = __s_api_contain_passwd_control_oid( sinfo.controls); /* check if server supports password less account mgmt */ if (nopasswd_acct_mgmt && !__s_api_contain_account_usable_control_oid( sinfo.controls)) { syslog(LOG_WARNING, "libsldap: server %s does not " "provide account information without password", host); free(host); __s_api_free_server_info(&sinfo); return (NS_LDAP_OP_FAILED); } /* make the connection */ rc = openConnection(&ld, *bindHost, auth, timeoutSec, errorp, fail_if_new_pwd_reqd, passwd_mgmt); /* if success, go to create connection structure */ if (rc == NS_LDAP_SUCCESS || rc == NS_LDAP_SUCCESS_WITH_INFO) { exit_rc = rc; break; } /* * If not able to reach the server, inform the ldap * cache manager that the server should be removed * from its server list. Thus, the manager will not * return this server on the next get-server request * and will also reduce the server list refresh TTL, * so that it will find out sooner when the server * is up again. */ if (rc == NS_LDAP_INTERNAL && *errorp != NULL) { if ((*errorp)->status == LDAP_CONNECT_ERROR || (*errorp)->status == LDAP_SERVER_DOWN) { /* Reset memory allocation error */ memerr = 0; /* * We contacted a server that we could * not either authenticate to or contact. * If it is due to authentication, then * we need to try the server again. So, * do not remove the server yet, but * add it to the bad server list. * The caller routine will remove * the servers if: * a). A good server is found or * b). All the possible methods * are tried without finding * a good server */ if (*badsrvrs == NULL) { if (!(*badsrvrs = (char **)malloc (sizeof (char *) * NUMTOMALLOC))) { memerr = 1; } /* Allocate memory in chunks of NUMTOMALLOC */ } else if ((totalbad % NUMTOMALLOC) == NUMTOMALLOC - 1) { char **tmpptr; if (!(tmpptr = (char **)realloc( *badsrvrs, (sizeof (char *) * NUMTOMALLOC * ((totalbad/NUMTOMALLOC) + 2))))) { memerr = 1; } else { *badsrvrs = tmpptr; } } /* * Store host only if there were no unsuccessful * memory allocations above */ if (!memerr && !((*badsrvrs)[totalbad++] = strdup(host))) { memerr = 1; totalbad--; } (*badsrvrs)[totalbad] = NULL; } } /* else, cleanup and go for the next server */ __s_api_free_server_info(&sinfo); /* Return if we had memory allocation errors */ if (memerr) return (NS_LDAP_MEMORY); if (*errorp) { /* * If openConnection() failed due to * password policy, or invalid credential, * keep *errorp and exit */ if ((*errorp)->pwd_mgmt.status != NS_PASSWD_GOOD || (*errorp)->status == LDAP_INVALID_CREDENTIALS) { free(host); return (rc); } else { (void) __ns_ldap_freeError(errorp); *errorp = NULL; } } } create_con: /* we have created ld, setup con structure */ if (host) free(host); if ((con = calloc(1, sizeof (Connection))) == NULL) { __s_api_free_server_info(&sinfo); /* * If password control attached in **errorp, * e.g. rc == NS_LDAP_SUCCESS_WITH_INFO, * free the error structure */ if (*errorp) { (void) __ns_ldap_freeError(errorp); *errorp = NULL; } (void) ldap_unbind(ld); return (NS_LDAP_MEMORY); } con->serverAddr = sinfo.server; /* Store original format */ if (sinfo.serverFQDN != NULL) { free(sinfo.serverFQDN); sinfo.serverFQDN = NULL; } con->saslMechanisms = sinfo.saslMechanisms; con->controls = sinfo.controls; con->auth = __ns_ldap_dupAuth(auth); if (con->auth == NULL) { (void) ldap_unbind(ld); freeConnection(con); /* * If password control attached in **errorp, * e.g. rc == NS_LDAP_SUCCESS_WITH_INFO, * free the error structure */ if (*errorp) { (void) __ns_ldap_freeError(errorp); *errorp = NULL; } return (NS_LDAP_MEMORY); } con->threadID = thr_self(); con->pid = getpid(); con->ld = ld; if ((id = addConnection(con)) == -1) { (void) ldap_unbind(ld); freeConnection(con); /* * If password control attached in **errorp, * e.g. rc == NS_LDAP_SUCCESS_WITH_INFO, * free the error structure */ if (*errorp) { (void) __ns_ldap_freeError(errorp); *errorp = NULL; } return (NS_LDAP_MEMORY); } #ifdef DEBUG (void) fprintf(stderr, "tid= %d: connection added into " "cache %d\n", thr_self(), id); fflush(stderr); #endif /* DEBUG */ *cID = id; *conp = con; return (exit_rc); } /* * Return the specified connection to the pool. If necessary * delete the connection. */ static void _DropConnection(ConnectionID cID, int flag, int fini) { Connection *cp; int id; int use_lock = !fini; struct timeval zerotime; LDAPMessage *res; zerotime.tv_sec = zerotime.tv_usec = 0L; #ifdef DEBUG thread_t t = thr_self(); #endif /* DEBUG */ id = cID - CONID_OFFSET; if (id < 0 || id >= sessionPoolSize) return; #ifdef DEBUG (void) fprintf(stderr, "tid= %d: " "Dropping connection cID=%d flag=0x%x, fini = %d\n", t, cID, flag, fini); fflush(stderr); #endif /* DEBUG */ if (use_lock) (void) rw_wrlock(&sessionPoolLock); cp = sessionPool[id]; /* sanity check before removing */ if (!cp || (!fini && !cp->shared && !cp->usedBit)) { #ifdef DEBUG if (cp == NULL) (void) fprintf(stderr, "tid= %d: no " "need to remove (fini = %d, cp = %p)\n", t, fini, cp); else (void) fprintf(stderr, "tid= %d: no " "need to remove (fini = %d, cp = %p, shared = %d)" "\n", t, fini, cp, cp->shared); fflush(stderr); #endif /* DEBUG */ if (use_lock) (void) rw_unlock(&sessionPoolLock); return; } if (!fini && ((flag & NS_LDAP_NEW_CONN) == 0) && !cp->notAvail && ((flag & NS_LDAP_KEEP_CONN) || __s_api_nscd_proc() || __s_api_peruser_proc())) { #ifdef DEBUG (void) fprintf(stderr, "tid= %d: keep alive (fini = %d " "shared = %d)\n", t, fini, cp->shared); #endif /* DEBUG */ /* release Connection (keep alive) */ if (cp->shared) cp->shared--; cp->usedBit = B_FALSE; cp->threadID = 0; /* unmark the threadID */ /* * Do sanity cleanup of remaining results if connection is not * shared by any requests. */ if (cp->shared <= 0) { while (ldap_result(cp->ld, LDAP_RES_ANY, LDAP_MSG_ALL, &zerotime, &res) > 0) { if (res != NULL) (void) ldap_msgfree(res); } } if (use_lock) (void) rw_unlock(&sessionPoolLock); } else { /* delete Connection (disconnect) */ if (cp->shared > 0) { #ifdef DEBUG (void) fprintf(stderr, "tid= %d: Connection no " "longer available (fini = %d, shared = %d)\n", t, fini, cp->shared); fflush(stderr); #endif /* DEBUG */ cp->shared--; /* * Mark this connection not available and decrement * sharedConnNumber. There could be multiple threads * sharing this connection so decrement * sharedConnNumber only once per connection. */ if (cp->notAvail == 0) { cp->notAvail = 1; (void) mutex_lock(&sharedConnNumberLock); sharedConnNumber--; (void) mutex_unlock(&sharedConnNumberLock); } } if (cp->shared <= 0) { #ifdef DEBUG (void) fprintf(stderr, "tid= %d: unbind " "(fini = %d, shared = %d)\n", t, fini, cp->shared); fflush(stderr); #endif /* DEBUG */ sessionPool[id] = NULL; (void) ldap_unbind(cp->ld); freeConnection(cp); } if (use_lock) (void) rw_unlock(&sessionPoolLock); } } void DropConnection(ConnectionID cID, int flag) { _DropConnection(cID, flag, 0); } /* * This routine is called after a bind operation is * done in openConnection() to process the password * management information, if any. * * Input: * bind_type: "simple" or "sasl/DIGEST-MD5" * ldaprc : ldap rc from the ldap bind operation * controls : controls returned by the server * errmsg : error message from the server * fail_if_new_pwd_reqd: * flag indicating if connection should be open * when password needs to change immediately * passwd_mgmt: * flag indicating if server supports password * policy/management * * Output : ns_ldap_error structure, which may contain * password status and number of seconds until * expired * * return rc: * NS_LDAP_EXTERNAL: error, connection should not open * NS_LDAP_SUCCESS_WITH_INFO: OK to open but password info attached * NS_LDAP_SUCCESS: OK to open connection * */ static int process_pwd_mgmt(char *bind_type, int ldaprc, LDAPControl **controls, char *errmsg, ns_ldap_error_t **errorp, int fail_if_new_pwd_reqd, int passwd_mgmt) { char errstr[MAXERROR]; LDAPControl **ctrl = NULL; int exit_rc; ns_ldap_passwd_status_t pwd_status = NS_PASSWD_GOOD; int sec_until_exp = 0; /* * errmsg may be an empty string, * even if ldaprc is LDAP_SUCCESS, * free the empty string if that's the case */ if (errmsg && (*errmsg == '\0' || ldaprc == LDAP_SUCCESS)) { ldap_memfree(errmsg); errmsg = NULL; } if (ldaprc != LDAP_SUCCESS) { /* * try to map ldap rc and error message to * a password status */ if (errmsg) { if (passwd_mgmt) pwd_status = __s_api_set_passwd_status( ldaprc, errmsg); ldap_memfree(errmsg); } (void) snprintf(errstr, sizeof (errstr), gettext("openConnection: " "%s bind failed " "- %s"), bind_type, ldap_err2string(ldaprc)); if (pwd_status != NS_PASSWD_GOOD) { MKERROR_PWD_MGMT(*errorp, ldaprc, strdup(errstr), pwd_status, 0, NULL); } else { MKERROR(LOG_ERR, *errorp, ldaprc, strdup(errstr), NULL); } if (controls) ldap_controls_free(controls); return (NS_LDAP_INTERNAL); } /* * ldaprc is LDAP_SUCCESS, * process the password management controls, if any */ exit_rc = NS_LDAP_SUCCESS; if (controls && passwd_mgmt) { /* * The control with the OID * 2.16.840.1.113730.3.4.4 (or * LDAP_CONTROL_PWEXPIRED, as defined * in the ldap.h header file) is the * expired password control. * * This control is used if the server * is configured to require users to * change their passwords when first * logging in and whenever the * passwords are reset. * * If the user is logging in for the * first time or if the user's * password has been reset, the * server sends this control to * indicate that the client needs to * change the password immediately. * * At this point, the only operation * that the client can perform is to * change the user's password. If the * client requests any other LDAP * operation, the server sends back * an LDAP_UNWILLING_TO_PERFORM * result code with an expired * password control. * * The control with the OID * 2.16.840.1.113730.3.4.5 (or * LDAP_CONTROL_PWEXPIRING, as * defined in the ldap.h header file) * is the password expiration warning * control. * * This control is used if the server * is configured to expire user * passwords after a certain amount * of time. * * The server sends this control back * to the client if the client binds * using a password that will soon * expire. The ldctl_value field of * the LDAPControl structure * specifies the number of seconds * before the password will expire. */ for (ctrl = controls; *ctrl; ctrl++) { if (strcmp((*ctrl)->ldctl_oid, LDAP_CONTROL_PWEXPIRED) == 0) { /* * if the caller wants this bind * to fail, set up the error info. * If call to this function is * for searching the LDAP directory, * e.g., __ns_ldap_list(), * there's really no sense to * let a connection open and * then fail immediately afterward * on the LDAP search operation with * the LDAP_UNWILLING_TO_PERFORM rc */ pwd_status = NS_PASSWD_CHANGE_NEEDED; if (fail_if_new_pwd_reqd) { (void) snprintf(errstr, sizeof (errstr), gettext( "openConnection: " "%s bind " "failed " "- password " "expired. It " " needs to change " "immediately!"), bind_type); MKERROR_PWD_MGMT(*errorp, LDAP_SUCCESS, strdup(errstr), pwd_status, 0, NULL); exit_rc = NS_LDAP_INTERNAL; } else { MKERROR_PWD_MGMT(*errorp, LDAP_SUCCESS, NULL, pwd_status, 0, NULL); exit_rc = NS_LDAP_SUCCESS_WITH_INFO; } break; } else if (strcmp((*ctrl)->ldctl_oid, LDAP_CONTROL_PWEXPIRING) == 0) { pwd_status = NS_PASSWD_ABOUT_TO_EXPIRE; if ((*ctrl)-> ldctl_value.bv_len > 0 && (*ctrl)-> ldctl_value.bv_val) sec_until_exp = atoi((*ctrl)-> ldctl_value.bv_val); MKERROR_PWD_MGMT(*errorp, LDAP_SUCCESS, NULL, pwd_status, sec_until_exp, NULL); exit_rc = NS_LDAP_SUCCESS_WITH_INFO; break; } } } if (controls) ldap_controls_free(controls); return (exit_rc); } static int ldap_in_hosts_switch() { enum __nsw_parse_err pserr; struct __nsw_switchconfig *conf; struct __nsw_lookup *lkp; const char *name; int found = 0; conf = __nsw_getconfig("hosts", &pserr); if (conf == NULL) { return (-1); } /* check for skip and count other backends */ for (lkp = conf->lookups; lkp != NULL; lkp = lkp->next) { name = lkp->service_name; if (strcmp(name, "ldap") == 0) { found = 1; break; } } __nsw_freeconfig(conf); return (found); } static int openConnection(LDAP **ldp, const char *serverAddr, const ns_cred_t *auth, int timeoutSec, ns_ldap_error_t **errorp, int fail_if_new_pwd_reqd, int passwd_mgmt) { LDAP *ld = NULL; char *binddn, *passwd; char *digest_md5_name; const char *s; int ldapVersion = LDAP_VERSION3; int derefOption = LDAP_DEREF_ALWAYS; int zero = 0; int rc; char errstr[MAXERROR]; int errnum = 0; LDAPMessage *resultMsg; int msgId; int useSSL = 0, port = 0; struct timeval tv; AuthType_t bindType; int timeoutMilliSec = timeoutSec * 1000; struct berval cred; char *sslServerAddr; char *s1; char *errmsg, *end = NULL; LDAPControl **controls; int pwd_rc, min_ssf = MIN_SASL_SSF, max_ssf = MAX_SASL_SSF; ns_sasl_cb_param_t sasl_param; *errorp = NULL; *ldp = NULL; switch (auth->auth.type) { case NS_LDAP_AUTH_NONE: case NS_LDAP_AUTH_SIMPLE: case NS_LDAP_AUTH_SASL: bindType = auth->auth.type; break; case NS_LDAP_AUTH_TLS: useSSL = 1; switch (auth->auth.tlstype) { case NS_LDAP_TLS_NONE: bindType = NS_LDAP_AUTH_NONE; break; case NS_LDAP_TLS_SIMPLE: bindType = NS_LDAP_AUTH_SIMPLE; break; case NS_LDAP_TLS_SASL: bindType = NS_LDAP_AUTH_SASL; break; default: (void) sprintf(errstr, gettext("openConnection: unsupported " "TLS authentication method " "(%d)"), auth->auth.tlstype); MKERROR(LOG_WARNING, *errorp, LDAP_AUTH_METHOD_NOT_SUPPORTED, strdup(errstr), NULL); return (NS_LDAP_INTERNAL); } break; default: (void) sprintf(errstr, gettext("openConnection: unsupported " "authentication method (%d)"), auth->auth.type); MKERROR(LOG_WARNING, *errorp, LDAP_AUTH_METHOD_NOT_SUPPORTED, strdup(errstr), NULL); return (NS_LDAP_INTERNAL); } if (useSSL) { const char *hostcertpath; char *alloc_hcp = NULL; #ifdef DEBUG (void) fprintf(stderr, "tid= %d: +++TLS transport\n", thr_self()); #endif /* DEBUG */ if (prldap_set_session_option(NULL, NULL, PRLDAP_OPT_IO_MAX_TIMEOUT, timeoutMilliSec) != LDAP_SUCCESS) { (void) snprintf(errstr, sizeof (errstr), gettext("openConnection: failed to initialize " "TLS security")); MKERROR(LOG_WARNING, *errorp, LDAP_CONNECT_ERROR, strdup(errstr), NULL); return (NS_LDAP_INTERNAL); } hostcertpath = auth->hostcertpath; if (hostcertpath == NULL) { alloc_hcp = __s_get_hostcertpath(); hostcertpath = alloc_hcp; } if (hostcertpath == NULL) return (NS_LDAP_MEMORY); if ((rc = ldapssl_client_init(hostcertpath, NULL)) < 0) { if (alloc_hcp) free(alloc_hcp); (void) snprintf(errstr, sizeof (errstr), gettext("openConnection: failed to initialize " "TLS security (%s)"), ldapssl_err2string(rc)); MKERROR(LOG_WARNING, *errorp, LDAP_CONNECT_ERROR, strdup(errstr), NULL); return (NS_LDAP_INTERNAL); } if (alloc_hcp) free(alloc_hcp); /* determine if the host name contains a port number */ s = strchr(serverAddr, ']'); /* skip over ipv6 addr */ if (s == NULL) s = serverAddr; s = strchr(s, ':'); if (s != NULL) { /* * If we do get a port number, we will try stripping * it. At present, referrals will always have a * port number. */ sslServerAddr = strdup(serverAddr); if (sslServerAddr == NULL) return (NS_LDAP_MEMORY); s1 = strrchr(sslServerAddr, ':'); if (s1 != NULL) *s1 = '\0'; (void) snprintf(errstr, sizeof (errstr), gettext("openConnection: cannot use tls with %s. " "Trying %s"), serverAddr, sslServerAddr); syslog(LOG_ERR, "libsldap: %s", errstr); } else sslServerAddr = (char *)serverAddr; ld = ldapssl_init(sslServerAddr, LDAPS_PORT, 1); if (sslServerAddr != serverAddr) free(sslServerAddr); if (ld == NULL || ldapssl_install_gethostbyaddr(ld, "ldap") != 0) { (void) snprintf(errstr, sizeof (errstr), gettext("openConnection: failed to connect " "using TLS (%s)"), strerror(errno)); MKERROR(LOG_WARNING, *errorp, LDAP_CONNECT_ERROR, strdup(errstr), NULL); return (NS_LDAP_INTERNAL); } } else { #ifdef DEBUG (void) fprintf(stderr, "tid= %d: +++Unsecure transport\n", thr_self()); #endif /* DEBUG */ port = LDAP_PORT; if (auth->auth.saslmech == NS_LDAP_SASL_GSSAPI && (end = strchr(serverAddr, ':')) != NULL) { /* * The IP is converted to hostname so it's a * hostname:port up to this point. * * libldap passes hostname:port to the sasl layer. * The ldap service principal is constructed as * ldap/hostname:port@REALM. Kerberos authentication * will fail. So it needs to be parsed to construct * a valid principal ldap/hostname@REALM. * * For useSSL case above, it already parses port so * no need to parse serverAddr */ *end = '\0'; port = atoi(end + 1); } /* Warning message IF cannot connect to host(s) */ if ((ld = ldap_init((char *)serverAddr, port)) == NULL) { char *p = strerror(errno); MKERROR(LOG_WARNING, *errorp, LDAP_CONNECT_ERROR, strdup(p), NULL); if (end) *end = ':'; return (NS_LDAP_INTERNAL); } else { if (end) *end = ':'; /* check and avoid gethostname recursion */ if (ldap_in_hosts_switch() > 0 && ! __s_api_isipv4((char *)serverAddr) && ! __s_api_isipv6((char *)serverAddr)) { /* host: ldap - found, attempt to recover */ if (ldap_set_option(ld, LDAP_X_OPT_DNS_SKIPDB, "ldap") != 0) { (void) snprintf(errstr, sizeof (errstr), gettext("openConnection: " "unrecoverable gethostname " "recursion detected " "in /etc/nsswitch.conf")); MKERROR(LOG_WARNING, *errorp, LDAP_CONNECT_ERROR, strdup(errstr), NULL); (void) ldap_unbind(ld); return (NS_LDAP_INTERNAL); } } } } ns_setup_mt_conn_and_tsd(ld); (void) ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &ldapVersion); (void) ldap_set_option(ld, LDAP_OPT_DEREF, &derefOption); /* * set LDAP_OPT_REFERRALS to OFF. * This library will handle the referral itself * based on API flags or configuration file * specification. If this option is not set * to OFF, libldap will never pass the * referral info up to this library */ (void) ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF); (void) ldap_set_option(ld, LDAP_OPT_TIMELIMIT, &zero); (void) ldap_set_option(ld, LDAP_OPT_SIZELIMIT, &zero); /* setup TCP/IP connect timeout */ (void) ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT, &timeoutMilliSec); /* retry if LDAP I/O was interrupted */ (void) ldap_set_option(ld, LDAP_OPT_RESTART, LDAP_OPT_ON); switch (bindType) { case NS_LDAP_AUTH_NONE: #ifdef DEBUG (void) fprintf(stderr, "tid= %d: +++Anonymous bind\n", thr_self()); #endif /* DEBUG */ break; case NS_LDAP_AUTH_SIMPLE: binddn = auth->cred.unix_cred.userID; passwd = auth->cred.unix_cred.passwd; if (passwd == NULL || *passwd == '\0' || binddn == NULL || *binddn == '\0') { (void) sprintf(errstr, gettext("openConnection: " "missing credentials for Simple bind")); MKERROR(LOG_WARNING, *errorp, LDAP_INVALID_CREDENTIALS, strdup(errstr), NULL); (void) ldap_unbind(ld); return (NS_LDAP_INTERNAL); } #ifdef DEBUG (void) fprintf(stderr, "tid= %d: +++Simple bind\n", thr_self()); #endif /* DEBUG */ msgId = ldap_simple_bind(ld, binddn, passwd); if (msgId == -1) { (void) ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, (void *)&errnum); (void) snprintf(errstr, sizeof (errstr), gettext("openConnection: simple bind failed " "- %s"), ldap_err2string(errnum)); (void) ldap_unbind(ld); MKERROR(LOG_WARNING, *errorp, errnum, strdup(errstr), NULL); return (NS_LDAP_INTERNAL); } tv.tv_sec = timeoutSec; tv.tv_usec = 0; rc = ldap_result(ld, msgId, 0, &tv, &resultMsg); if ((rc == -1) || (rc == 0)) { (void) ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, (void *)&errnum); (void) snprintf(errstr, sizeof (errstr), gettext("openConnection: simple bind failed " "- %s"), ldap_err2string(errnum)); (void) ldap_msgfree(resultMsg); (void) ldap_unbind(ld); MKERROR(LOG_WARNING, *errorp, errnum, strdup(errstr), NULL); return (NS_LDAP_INTERNAL); } /* * get ldaprc, controls, and error msg */ rc = ldap_parse_result(ld, resultMsg, &errnum, NULL, &errmsg, NULL, &controls, 1); if (rc != LDAP_SUCCESS) { (void) snprintf(errstr, sizeof (errstr), gettext("openConnection: simple bind failed " "- unable to parse result")); (void) ldap_unbind(ld); MKERROR(LOG_WARNING, *errorp, NS_LDAP_INTERNAL, strdup(errstr), NULL); return (NS_LDAP_INTERNAL); } /* process the password management info, if any */ pwd_rc = process_pwd_mgmt("simple", errnum, controls, errmsg, errorp, fail_if_new_pwd_reqd, passwd_mgmt); if (pwd_rc == NS_LDAP_INTERNAL) { (void) ldap_unbind(ld); return (pwd_rc); } if (pwd_rc == NS_LDAP_SUCCESS_WITH_INFO) { *ldp = ld; return (pwd_rc); } break; case NS_LDAP_AUTH_SASL: if (auth->auth.saslopt != NS_LDAP_SASLOPT_NONE && auth->auth.saslmech != NS_LDAP_SASL_GSSAPI) { (void) sprintf(errstr, gettext("openConnection: SASL options are " "not supported (%d) for non-GSSAPI sasl bind"), auth->auth.saslopt); MKERROR(LOG_WARNING, *errorp, LDAP_AUTH_METHOD_NOT_SUPPORTED, strdup(errstr), NULL); (void) ldap_unbind(ld); return (NS_LDAP_INTERNAL); } if (auth->auth.saslmech != NS_LDAP_SASL_GSSAPI) { binddn = auth->cred.unix_cred.userID; passwd = auth->cred.unix_cred.passwd; if (passwd == NULL || *passwd == '\0' || binddn == NULL || *binddn == '\0') { (void) sprintf(errstr, gettext("openConnection: missing " "credentials for SASL bind")); MKERROR(LOG_WARNING, *errorp, LDAP_INVALID_CREDENTIALS, strdup(errstr), NULL); (void) ldap_unbind(ld); return (NS_LDAP_INTERNAL); } cred.bv_val = passwd; cred.bv_len = strlen(passwd); } switch (auth->auth.saslmech) { case NS_LDAP_SASL_CRAM_MD5: /* * NOTE: if iDS changes to support cram_md5, * please add password management code here. * Since ldap_sasl_cram_md5_bind_s does not * return anything that could be used to * extract the ldap rc/errmsg/control to * determine if bind failed due to password * policy, a new cram_md5_bind API will need * to be introduced. See * ldap_x_sasl_digest_md5_bind() and case * NS_LDAP_SASL_DIGEST_MD5 below for details. */ if ((rc = ldap_sasl_cram_md5_bind_s(ld, binddn, &cred, NULL, NULL)) != LDAP_SUCCESS) { (void) ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, (void *)&errnum); (void) snprintf(errstr, sizeof (errstr), gettext("openConnection: " "sasl/CRAM-MD5 bind failed - %s"), ldap_err2string(errnum)); MKERROR(LOG_WARNING, *errorp, errnum, strdup(errstr), NULL); (void) ldap_unbind(ld); return (NS_LDAP_INTERNAL); } break; case NS_LDAP_SASL_DIGEST_MD5: digest_md5_name = malloc(strlen(binddn) + 5); /* 5 = strlen("dn: ") + 1 */ if (digest_md5_name == NULL) { (void) ldap_unbind(ld); return (NS_LDAP_MEMORY); } (void) strcpy(digest_md5_name, "dn: "); (void) strcat(digest_md5_name, binddn); tv.tv_sec = timeoutSec; tv.tv_usec = 0; rc = ldap_x_sasl_digest_md5_bind(ld, digest_md5_name, &cred, NULL, NULL, &tv, &resultMsg); if (resultMsg == NULL) { free(digest_md5_name); (void) ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, (void *)&errnum); (void) snprintf(errstr, sizeof (errstr), gettext("openConnection: " "DIGEST-MD5 bind failed - %s"), ldap_err2string(errnum)); (void) ldap_unbind(ld); MKERROR(LOG_WARNING, *errorp, errnum, strdup(errstr), NULL); return (NS_LDAP_INTERNAL); } /* * get ldaprc, controls, and error msg */ rc = ldap_parse_result(ld, resultMsg, &errnum, NULL, &errmsg, NULL, &controls, 1); if (rc != LDAP_SUCCESS) { free(digest_md5_name); (void) snprintf(errstr, sizeof (errstr), gettext("openConnection: " "DIGEST-MD5 bind failed " "- unable to parse result")); (void) ldap_unbind(ld); MKERROR(LOG_WARNING, *errorp, NS_LDAP_INTERNAL, strdup(errstr), NULL); return (NS_LDAP_INTERNAL); } /* process the password management info, if any */ pwd_rc = process_pwd_mgmt("sasl/DIGEST-MD5", errnum, controls, errmsg, errorp, fail_if_new_pwd_reqd, passwd_mgmt); if (pwd_rc == NS_LDAP_INTERNAL) { free(digest_md5_name); (void) ldap_unbind(ld); return (pwd_rc); } if (pwd_rc == NS_LDAP_SUCCESS_WITH_INFO) { *ldp = ld; return (pwd_rc); } free(digest_md5_name); break; case NS_LDAP_SASL_GSSAPI: if (sasl_gssapi_inited == 0) { rc = __s_api_sasl_gssapi_init(); if (rc != NS_LDAP_SUCCESS) { (void) snprintf(errstr, sizeof (errstr), gettext("openConnection: " "GSSAPI initialization " "failed")); (void) ldap_unbind(ld); MKERROR(LOG_WARNING, *errorp, rc, strdup(errstr), NULL); return (rc); } } (void) memset(&sasl_param, 0, sizeof (ns_sasl_cb_param_t)); sasl_param.authid = NULL; sasl_param.authzid = ""; (void) ldap_set_option(ld, LDAP_OPT_X_SASL_SSF_MIN, (void *)&min_ssf); (void) ldap_set_option(ld, LDAP_OPT_X_SASL_SSF_MAX, (void *)&max_ssf); rc = ldap_sasl_interactive_bind_s( ld, NULL, "GSSAPI", NULL, NULL, LDAP_SASL_INTERACTIVE, __s_api_sasl_bind_callback, &sasl_param); if (rc != LDAP_SUCCESS) { (void) snprintf(errstr, sizeof (errstr), gettext("openConnection: " "GSSAPI bind failed " "- %d %s"), rc, ldap_err2string(rc)); (void) ldap_unbind(ld); MKERROR(LOG_WARNING, *errorp, NS_LDAP_INTERNAL, strdup(errstr), NULL); return (NS_LDAP_INTERNAL); } break; default: (void) ldap_unbind(ld); (void) sprintf(errstr, gettext("openConnection: unsupported SASL " "mechanism (%d)"), auth->auth.saslmech); MKERROR(LOG_WARNING, *errorp, LDAP_AUTH_METHOD_NOT_SUPPORTED, strdup(errstr), NULL); return (NS_LDAP_INTERNAL); } } *ldp = ld; return (NS_LDAP_SUCCESS); } /* * FUNCTION: __s_api_getDefaultAuth * * Constructs a credential for authentication using the config module. * * RETURN VALUES: * * NS_LDAP_SUCCESS If successful * NS_LDAP_CONFIG If there are any config errors. * NS_LDAP_MEMORY Memory errors. * NS_LDAP_OP_FAILED If there are no more authentication methods so can * not build a new authp. * NS_LDAP_INVALID_PARAM This overloaded return value means that some of the * necessary fields of a cred for a given auth method * are not provided. * INPUT: * * cLevel Currently requested credential level to be tried * * aMethod Currently requested authentication method to be tried * * OUTPUT: * * authp authentication method to use. */ static int __s_api_getDefaultAuth( int *cLevel, ns_auth_t *aMethod, ns_cred_t **authp) { void **paramVal = NULL; char *modparamVal = NULL; int getUid = 0; int getPasswd = 0; int getCertpath = 0; int rc = 0; ns_ldap_error_t *errorp = NULL; #ifdef DEBUG (void) fprintf(stderr, "__s_api_getDefaultAuth START\n"); #endif if (aMethod == NULL) { /* Require an Auth */ return (NS_LDAP_INVALID_PARAM); } /* * credential level "self" can work with auth method sasl/GSSAPI only */ if (cLevel && *cLevel == NS_LDAP_CRED_SELF && aMethod->saslmech != NS_LDAP_SASL_GSSAPI) return (NS_LDAP_INVALID_PARAM); *authp = (ns_cred_t *)calloc(1, sizeof (ns_cred_t)); if ((*authp) == NULL) return (NS_LDAP_MEMORY); (*authp)->auth = *aMethod; switch (aMethod->type) { case NS_LDAP_AUTH_NONE: return (NS_LDAP_SUCCESS); case NS_LDAP_AUTH_SIMPLE: getUid++; getPasswd++; break; case NS_LDAP_AUTH_SASL: if ((aMethod->saslmech == NS_LDAP_SASL_DIGEST_MD5) || (aMethod->saslmech == NS_LDAP_SASL_CRAM_MD5)) { getUid++; getPasswd++; } else if (aMethod->saslmech != NS_LDAP_SASL_GSSAPI) { (void) __ns_ldap_freeCred(authp); *authp = NULL; return (NS_LDAP_INVALID_PARAM); } break; case NS_LDAP_AUTH_TLS: if ((aMethod->tlstype == NS_LDAP_TLS_SIMPLE) || ((aMethod->tlstype == NS_LDAP_TLS_SASL) && ((aMethod->saslmech == NS_LDAP_SASL_DIGEST_MD5) || (aMethod->saslmech == NS_LDAP_SASL_CRAM_MD5)))) { getUid++; getPasswd++; getCertpath++; } else if (aMethod->tlstype == NS_LDAP_TLS_NONE) { getCertpath++; } else { (void) __ns_ldap_freeCred(authp); *authp = NULL; return (NS_LDAP_INVALID_PARAM); } break; } if (getUid) { paramVal = NULL; if ((rc = __ns_ldap_getParam(NS_LDAP_BINDDN_P, ¶mVal, &errorp)) != NS_LDAP_SUCCESS) { (void) __ns_ldap_freeCred(authp); (void) __ns_ldap_freeError(&errorp); *authp = NULL; return (rc); } if (paramVal == NULL || *paramVal == NULL) { (void) __ns_ldap_freeCred(authp); *authp = NULL; return (NS_LDAP_INVALID_PARAM); } (*authp)->cred.unix_cred.userID = strdup((char *)*paramVal); (void) __ns_ldap_freeParam(¶mVal); if ((*authp)->cred.unix_cred.userID == NULL) { (void) __ns_ldap_freeCred(authp); *authp = NULL; return (NS_LDAP_MEMORY); } } if (getPasswd) { paramVal = NULL; if ((rc = __ns_ldap_getParam(NS_LDAP_BINDPASSWD_P, ¶mVal, &errorp)) != NS_LDAP_SUCCESS) { (void) __ns_ldap_freeCred(authp); (void) __ns_ldap_freeError(&errorp); *authp = NULL; return (rc); } if (paramVal == NULL || *paramVal == NULL) { (void) __ns_ldap_freeCred(authp); *authp = NULL; return (NS_LDAP_INVALID_PARAM); } modparamVal = dvalue((char *)*paramVal); (void) __ns_ldap_freeParam(¶mVal); if (modparamVal == NULL || (strlen((char *)modparamVal) == 0)) { (void) __ns_ldap_freeCred(authp); if (modparamVal != NULL) free(modparamVal); *authp = NULL; return (NS_LDAP_INVALID_PARAM); } (*authp)->cred.unix_cred.passwd = modparamVal; } if (getCertpath) { paramVal = NULL; if ((rc = __ns_ldap_getParam(NS_LDAP_HOST_CERTPATH_P, ¶mVal, &errorp)) != NS_LDAP_SUCCESS) { (void) __ns_ldap_freeCred(authp); (void) __ns_ldap_freeError(&errorp); *authp = NULL; return (rc); } if (paramVal == NULL || *paramVal == NULL) { (void) __ns_ldap_freeCred(authp); *authp = NULL; return (NS_LDAP_INVALID_PARAM); } (*authp)->hostcertpath = strdup((char *)*paramVal); (void) __ns_ldap_freeParam(¶mVal); if ((*authp)->hostcertpath == NULL) { (void) __ns_ldap_freeCred(authp); *authp = NULL; return (NS_LDAP_MEMORY); } } return (NS_LDAP_SUCCESS); } /* * FUNCTION: __s_api_getConnection * * Bind to the specified server or one from the server * list and return the pointer. * * This function can rebind or not (NS_LDAP_HARD), it can require a * credential or bind anonymously * * This function follows the DUA configuration schema algorithm * * RETURN VALUES: * * NS_LDAP_SUCCESS A connection was made successfully. * NS_LDAP_SUCCESS_WITH_INFO * A connection was made successfully, but with * password management info in *errorp * NS_LDAP_INVALID_PARAM If any invalid arguments were passed to the function. * NS_LDAP_CONFIG If there are any config errors. * NS_LDAP_MEMORY Memory errors. * NS_LDAP_INTERNAL If there was a ldap error. * * INPUT: * * server Bind to this LDAP server only * flags If NS_LDAP_HARD is set function will not return until it has * a connection unless there is a authentication problem. * If NS_LDAP_NEW_CONN is set the function must force a new * connection to be created * If NS_LDAP_KEEP_CONN is set the connection is to be kept open * auth Credentials for bind. This could be NULL in which case * a default cred built from the config module is used. * sessionId cookie that points to a previous session * fail_if_new_pwd_reqd * a flag indicating this function should fail if the passwd * in auth needs to change immediately * nopasswd_acct_mgmt * a flag indicating that makeConnection should check before * binding if server supports LDAP V3 password less * account management * * OUTPUT: * * session pointer to a session with connection information * errorp Set if there are any INTERNAL, or CONFIG error. */ int __s_api_getConnection( const char *server, const int flags, const ns_cred_t *cred, /* credentials for bind */ ConnectionID *sessionId, Connection **session, ns_ldap_error_t **errorp, int fail_if_new_pwd_reqd, int nopasswd_acct_mgmt) { char errmsg[MAXERROR]; ns_auth_t **aMethod = NULL; ns_auth_t **aNext = NULL; int **cLevel = NULL; int **cNext = NULL; int timeoutSec = NS_DEFAULT_BIND_TIMEOUT; int rc; Connection *con = NULL; int sec = 1; ns_cred_t *authp = NULL; ns_cred_t anon; int version = NS_LDAP_V2, self_gssapi_only = 0; void **paramVal = NULL; char **badSrvrs = NULL; /* List of problem hostnames */ if ((session == NULL) || (sessionId == NULL)) { return (NS_LDAP_INVALID_PARAM); } *session = NULL; /* if we already have a session id try to reuse connection */ if (*sessionId > 0) { rc = findConnectionById(flags, cred, *sessionId, &con); if (rc == *sessionId && con) { *session = con; return (NS_LDAP_SUCCESS); } *sessionId = 0; } /* get profile version number */ if ((rc = __ns_ldap_getParam(NS_LDAP_FILE_VERSION_P, ¶mVal, errorp)) != NS_LDAP_SUCCESS) return (rc); if (paramVal == NULL) { (void) sprintf(errmsg, gettext("getConnection: no file " "version")); MKERROR(LOG_WARNING, *errorp, NS_CONFIG_FILE, strdup(errmsg), NS_LDAP_CONFIG); return (NS_LDAP_CONFIG); } if (strcasecmp((char *)*paramVal, NS_LDAP_VERSION_1) == 0) version = NS_LDAP_V1; (void) __ns_ldap_freeParam((void ***)¶mVal); /* Get the bind timeout value */ (void) __ns_ldap_getParam(NS_LDAP_BIND_TIME_P, ¶mVal, errorp); if (paramVal != NULL && *paramVal != NULL) { timeoutSec = **((int **)paramVal); (void) __ns_ldap_freeParam(¶mVal); } if (*errorp) (void) __ns_ldap_freeError(errorp); if (cred == NULL) { /* Get the authentication method list */ if ((rc = __ns_ldap_getParam(NS_LDAP_AUTH_P, (void ***)&aMethod, errorp)) != NS_LDAP_SUCCESS) return (rc); if (aMethod == NULL) { aMethod = (ns_auth_t **)calloc(2, sizeof (ns_auth_t *)); if (aMethod == NULL) return (NS_LDAP_MEMORY); aMethod[0] = (ns_auth_t *)calloc(1, sizeof (ns_auth_t)); if (aMethod[0] == NULL) { free(aMethod); return (NS_LDAP_MEMORY); } if (version == NS_LDAP_V1) (aMethod[0])->type = NS_LDAP_AUTH_SIMPLE; else { (aMethod[0])->type = NS_LDAP_AUTH_SASL; (aMethod[0])->saslmech = NS_LDAP_SASL_DIGEST_MD5; (aMethod[0])->saslopt = NS_LDAP_SASLOPT_NONE; } } /* Get the credential level list */ if ((rc = __ns_ldap_getParam(NS_LDAP_CREDENTIAL_LEVEL_P, (void ***)&cLevel, errorp)) != NS_LDAP_SUCCESS) { (void) __ns_ldap_freeParam((void ***)&aMethod); return (rc); } if (cLevel == NULL) { cLevel = (int **)calloc(2, sizeof (int *)); if (cLevel == NULL) return (NS_LDAP_MEMORY); cLevel[0] = (int *)calloc(1, sizeof (int)); if (cLevel[0] == NULL) return (NS_LDAP_MEMORY); if (version == NS_LDAP_V1) *(cLevel[0]) = NS_LDAP_CRED_PROXY; else *(cLevel[0]) = NS_LDAP_CRED_ANON; } } /* setup the anon credential for anonymous connection */ (void) memset(&anon, 0, sizeof (ns_cred_t)); anon.auth.type = NS_LDAP_AUTH_NONE; for (; ; ) { if (cred != NULL) { /* using specified auth method */ rc = makeConnection(&con, server, cred, sessionId, timeoutSec, errorp, fail_if_new_pwd_reqd, nopasswd_acct_mgmt, flags, &badSrvrs); /* not using bad server if credentials were supplied */ if (badSrvrs && *badSrvrs) { __s_api_free2dArray(badSrvrs); badSrvrs = NULL; } if (rc == NS_LDAP_SUCCESS || rc == NS_LDAP_SUCCESS_WITH_INFO) { *session = con; break; } } else { self_gssapi_only = __s_api_self_gssapi_only_get(); /* for every cred level */ for (cNext = cLevel; *cNext != NULL; cNext++) { if (self_gssapi_only && **cNext != NS_LDAP_CRED_SELF) continue; if (**cNext == NS_LDAP_CRED_ANON) { /* * make connection anonymously * Free the down server list before * looping through */ if (badSrvrs && *badSrvrs) { __s_api_free2dArray(badSrvrs); badSrvrs = NULL; } rc = makeConnection(&con, server, &anon, sessionId, timeoutSec, errorp, fail_if_new_pwd_reqd, nopasswd_acct_mgmt, flags, &badSrvrs); if (rc == NS_LDAP_SUCCESS || rc == NS_LDAP_SUCCESS_WITH_INFO) { *session = con; goto done; } continue; } /* for each cred level */ for (aNext = aMethod; *aNext != NULL; aNext++) { if (self_gssapi_only && (*aNext)->saslmech != NS_LDAP_SASL_GSSAPI) continue; /* * self coexists with sasl/GSSAPI only * and non-self coexists with non-gssapi * only */ if ((**cNext == NS_LDAP_CRED_SELF && (*aNext)->saslmech != NS_LDAP_SASL_GSSAPI) || (**cNext != NS_LDAP_CRED_SELF && (*aNext)->saslmech == NS_LDAP_SASL_GSSAPI)) continue; /* make connection and authenticate */ /* with default credentials */ authp = NULL; rc = __s_api_getDefaultAuth(*cNext, *aNext, &authp); if (rc != NS_LDAP_SUCCESS) { continue; } /* * Free the down server list before * looping through */ if (badSrvrs && *badSrvrs) { __s_api_free2dArray(badSrvrs); badSrvrs = NULL; } rc = makeConnection(&con, server, authp, sessionId, timeoutSec, errorp, fail_if_new_pwd_reqd, nopasswd_acct_mgmt, flags, &badSrvrs); (void) __ns_ldap_freeCred(&authp); if (rc == NS_LDAP_SUCCESS || rc == NS_LDAP_SUCCESS_WITH_INFO) { *session = con; goto done; } } } } if (flags & NS_LDAP_HARD) { if (sec < LDAPMAXHARDLOOKUPTIME) sec *= 2; _sleep(sec); } else { break; } } done: /* * If unable to get a connection, and this is * the thread opening the shared connection, * unlock the session mutex and let other * threads try to get their own connection. */ if (wait4session != 0 && sessionTid == thr_self()) { wait4session = 0; sessionTid = 0; #ifdef DEBUG (void) fprintf(stderr, "tid= %d: __s_api_getConnection: " "unlocking sessionLock \n", thr_self()); fflush(stderr); #endif /* DEBUG */ (void) mutex_unlock(&sessionLock); } if (self_gssapi_only && rc == NS_LDAP_SUCCESS && *session == NULL) { /* * self_gssapi_only is true but no self/sasl/gssapi is * configured */ rc = NS_LDAP_CONFIG; } (void) __ns_ldap_freeParam((void ***)&aMethod); (void) __ns_ldap_freeParam((void ***)&cLevel); if (badSrvrs && *badSrvrs) { /* * At this point, either we have a successful * connection or exhausted all the possible auths. * and creds. Mark the problem servers as down * so that the problem servers are not contacted * again until the refresh_ttl expires. */ (void) __s_api_removeBadServers(badSrvrs); __s_api_free2dArray(badSrvrs); } return (rc); } #pragma fini(_free_sessionPool) static void _free_sessionPool() { int id; (void) rw_wrlock(&sessionPoolLock); if (sessionPool != NULL) { for (id = 0; id < sessionPoolSize; id++) _DropConnection(id + CONID_OFFSET, 0, 1); free(sessionPool); sessionPool = NULL; sessionPoolSize = 0; } (void) rw_unlock(&sessionPoolLock); }