/*
* Copyright (c) 2001-2009 Sendmail, Inc. and its suppliers.
* All rights reserved.
*
* By using this file, you agree to the terms and conditions set
* forth in the LICENSE file which can be found at the top level of
* the sendmail distribution.
*/
/* some "deprecated" calls are used, e.g., ldap_get_values() */
#if LDAPMAP
# include <errno.h>
# include <setjmp.h>
# include <stdlib.h>
# include <unistd.h>
# include <sm/errstring.h>
# ifdef EX_OK
# endif /* EX_OK */
# include <sm/sysexits.h>
"@(#)$Debug: sm_trace_ldap - trace LDAP operations $");
static void ldaptimeout __P((int));
static SM_LDAP_RECURSE_ENTRY *sm_ldap_add_recurse __P((SM_LDAP_RECURSE_LIST **, char *, int, SM_RPOOL_T *));
/*
** SM_LDAP_CLEAR -- set default values for SM_LDAP_STRUCT
**
** Parameters:
** lmap -- pointer to SM_LDAP_STRUCT to clear
**
** Returns:
** None.
**
*/
# endif /* defined(LDAP_VERSION_MAX) && _FFR_LDAP_VERSION > LDAP_VERSION_MAX */
# endif /* defined(LDAP_VERSION_MIN) && _FFR_LDAP_VERSION < LDAP_VERSION_MIN */
#else /* _FFR_LDAP_VERSION */
# define SM_LDAP_VERSION_DEFAULT 0
#endif /* _FFR_LDAP_VERSION */
void
{
return;
# ifdef LDAP_REFERRALS
# else /* LDAP_REFERRALS */
lmap->ldap_options = 0;
# endif /* LDAP_REFERRALS */
lmap->ldap_multi_args = false;
}
/*
** SM_LDAP_START -- actually connect to an LDAP server
**
** Parameters:
** name -- name of map for debug output.
** lmap -- the LDAP map being opened.
**
** Returns:
** true if connection is successful, false otherwise.
**
** Side Effects:
** Populates lmap->ldap_ld.
*/
do \
{ \
if (to != 0) \
{ \
if (setjmp(LDAPTimeout) != 0) \
{ \
return false; \
} \
} \
} while (0)
#define SM_LDAP_CLEARTIMEOUT() \
do \
{ \
sm_clrevent(ev); \
} while (0)
bool
char *name;
{
int bind_result;
int save_errno = 0;
char *id;
else
id = "localhost";
{
/* Don't print a port number for LDAP URIs */
else
}
{
/* LDAP server supports URIs so use them directly */
#else /* SM_CONF_LDAP_INITIALIZE */
int err;
if (err != 0)
{
return false;
}
{
save_errno = errno;
errno = save_errno;
return false;
}
#endif /* SM_CONF_LDAP_INITIALIZE */
}
{
# if USE_LDAP_INIT
save_errno = errno;
# else /* USE_LDAP_INIT */
/*
** If using ldap_open(), the actual connection to the server
** happens now so we need the timeout here. For ldap_init(),
** the connection happens at bind time.
*/
save_errno = errno;
/* clear the event if it has not sprung */
# endif /* USE_LDAP_INIT */
}
errno = save_errno;
return false;
# if USE_LDAP_INIT
/*
** If using ldap_init(), the actual connection to the server
** happens at ldap_bind_s() so we need the timeout here.
*/
# endif /* USE_LDAP_INIT */
# ifdef LDAP_AUTH_KRBV4
{
/*
** Need to put ticket in environment here instead of
** during parseargs as there may be different tickets
** for different LDAP connections.
*/
}
# endif /* LDAP_AUTH_KRBV4 */
# if USE_LDAP_INIT
/* clear the event if it has not sprung */
# endif /* USE_LDAP_INIT */
if (bind_result != LDAP_SUCCESS)
{
return false;
}
/* Save PID to make sure only this PID closes the LDAP connection */
return true;
}
/* ARGSUSED */
static void
int unused;
{
/*
** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
** DOING.
*/
}
/*
** SM_LDAP_SEARCH_M -- initiate multi-key LDAP search
**
** Initiate an LDAP search, return the msgid.
** The calling function must collect the results.
**
** Parameters:
** lmap -- LDAP map information
** argv -- key vector of substitutions in LDAP filter
** NOTE: argv must have SM_LDAP_ARGS elements to prevent
** out of bound array references
**
** Returns:
** <0 on failure (SM_LDAP_ERR*), msgid on success
**
*/
int
char **argv;
{
int msgid;
char *fp, *p, *q;
p = lmap->ldap_filter;
{
char *key;
if (lmap->ldap_multi_args)
{
#if SM_LDAP_ARGS < 10
#endif /* SM_LDAP_ARGS < 10 */
if (q[1] == 's')
else if (q[1] >= '0' && q[1] <= '9')
{
{
return SM_LDAP_ERR_ARG_MISS;
# else /* SM_LDAP_ERROR_ON_MISSING_ARGS */
key = "";
# endif /* SM_LDAP_ERROR_ON_MISSING_ARGS */
}
}
else
}
else
if (q[1] == 's')
{
"%.*s%s", (int) (q - p), p, key);
p = q + 2;
}
else if (q[1] == '0' ||
{
char *k = key;
"%.*s", (int) (q - p), p);
p = q + 2;
/* Properly escape LDAP special characters */
*k != '\0')
{
if (*k == '*' || *k == '(' ||
*k == ')' || *k == '\\')
{
(void) sm_strlcat(fp,
(*k == '*' ? "\\2A" :
(*k == '(' ? "\\28" :
(*k == ')' ? "\\29" :
(*k == '\\' ? "\\5C" :
"\00")))),
k++;
}
else
*fp++ = *k++;
}
}
else
{
"%.*s", (int) (q - p + 1), p);
p = q + (q[1] == '%' ? 2 : 1);
}
}
return msgid;
}
/*
** SM_LDAP_SEARCH -- initiate LDAP search
**
** Initiate an LDAP search, return the msgid.
** The calling function must collect the results.
** Note this is just a wrapper into sm_ldap_search_m()
**
** Parameters:
** lmap -- LDAP map information
** key -- key to substitute in LDAP filter
**
** Returns:
** <0 on failure, msgid on success
**
*/
int
char *key;
{
}
/*
** SM_LDAP_HAS_OBJECTCLASS -- determine if an LDAP entry is part of a
** particular objectClass
**
** Parameters:
** lmap -- pointer to SM_LDAP_STRUCT in use
** entry -- current LDAP entry struct
** ocvalue -- particular objectclass in question.
** may be of form (fee|foo|fum) meaning
** any entry can be part of either fee,
** foo or fum objectclass
**
** Returns:
** true if item has that objectClass
*/
static bool
char *ocvalue;
{
int i;
return false;
return false;
{
char *p;
char *q;
p = q = ocvalue;
while (*p != '\0')
{
while (*p != '\0' && *p != '|')
p++;
sm_strncasecmp(vals[i], q, p - q) == 0)
{
return true;
}
while (*p == '|')
p++;
q = p;
}
}
return false;
}
/*
** SM_LDAP_RESULTS -- return results from an LDAP lookup in result
**
** Parameters:
** lmap -- pointer to SM_LDAP_STRUCT in use
** msgid -- msgid returned by sm_ldap_search()
** flags -- flags for the lookup
** delim -- delimiter for result concatenation
** rpool -- memory pool for storage
** result -- return string
** recurse -- recursion list
**
** Returns:
** status (sysexit)
*/
# define SM_LDAP_ERROR_CLEANUP() \
{ \
{ \
} \
}
static SM_LDAP_RECURSE_ENTRY *
char *item;
int type;
{
int n;
int m;
int p;
int insertat;
int moveb;
int oldsizeb;
int rc;
/*
** This code will maintain a list of
** SM_LDAP_RECURSE_ENTRY structures
** in ascending order.
*/
{
/* Allocate an initial SM_LDAP_RECURSE_LIST struct */
}
{
/* Grow the list of SM_LDAP_RECURSE_ENTRY ptrs */
{
oldsizeb = 0;
}
else
{
}
if (oldsizeb > 0)
}
/*
** Return current entry pointer if already exists.
*/
n = 0;
if (m < 0)
insertat = 0;
else
insertat = -1;
while (insertat == -1)
{
p = (m + n) / 2;
if (rc == 0)
if (rc < 0)
m = p - 1;
else if (rc > 0)
n = p + 1;
else
if (m == -1)
insertat = 0;
else if (m < n)
insertat = m + 1;
}
/*
** Not found in list, make room
** at insert point and add it.
*/
{
if (moveb > 0)
moveb);
}
return newe;
}
int
int msgid;
int flags;
int delim;
char **result;
int *resultln;
int *resultsz;
{
bool toplevel;
int i;
int statp;
int vsize;
int ret;
int save_errno;
char *p;
/* Are we the top top level of the search? */
/* Get results */
statp = EX_NOTFOUND;
&(lmap->ldap_timeout)),
{
/* If we don't want multiple values and we have one, break */
if ((char) delim == '\0' &&
break;
/* Cycle through all entries */
{
char *attr;
char *dn;
/*
** If matching only and found an entry,
** no need to spin through attributes
*/
{
continue;
}
{
/* only wanted one match */
return EX_NOTFOUND;
}
#endif /* _FFR_LDAP_SINGLEDN */
/* record completed DN's to prevent loops */
{
save_errno += E_LDAPBASE;
errno = save_errno;
return EX_TEMPFAIL;
}
rpool);
{
return EX_OSERR;
}
{
/* already on list, skip it */
continue;
}
# if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
/*
** Reset value to prevent lingering
** LDAP_DECODING_ERROR due to
** OpenLDAP 1.X's hack (see below)
*/
# endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
&ber);
ber))
{
int type;
{
attr) == 0)
{
break;
}
}
{
/* URL lookups specify attrs to use */
needobjclass = NULL;
}
if (type == SM_LDAP_ATTR_NONE)
{
/* attribute not requested */
return EX_SOFTWARE;
}
/*
** For recursion on a particular attribute,
** we may need to see if this entry is
** part of a particular objectclass.
** Also, ignore objectClass attribute.
** Otherwise we just ignore this attribute.
*/
if (type == SM_LDAP_ATTR_OBJCLASS ||
(needobjclass != NULL &&
needobjclass)))
{
continue;
}
{
attr);
{
if (save_errno == LDAP_SUCCESS)
{
continue;
}
/* Must be an error */
save_errno += E_LDAPBASE;
errno = save_errno;
return EX_TEMPFAIL;
}
}
# if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
/*
** Reset value to prevent lingering
** LDAP_DECODING_ERROR due to
** OpenLDAP 1.X's hack (see below)
*/
# endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
/*
** If matching only,
** no need to spin through entries
*/
{
continue;
}
/*
** If we don't want multiple values,
** return first found.
*/
if ((char) delim == '\0')
{
{
/* already have a value */
flags))
{
/* only wanted one match */
return EX_NOTFOUND;
}
break;
}
{
attr);
break;
}
{
continue;
}
vsize);
"%s%c%s",
attr,
vals[0]);
else
vsize);
break;
}
/* attributes only */
{
attr);
else
{
flags) &&
{
/* only wanted one match */
return EX_NOTFOUND;
}
vsize);
(void) sm_snprintf(tmp,
vsize, "%s%c%s",
attr);
}
continue;
}
/*
** If there is more than one, munge then
** into a map_coldelim separated string.
** If we are recursing we may have an entry
** with no 'normal' values to put in the
** string.
** This is not an error.
*/
if (type == SM_LDAP_ATTR_NORMAL &&
{
/* only wanted one match */
return EX_NOTFOUND;
}
vsize = 0;
{
if (type == SM_LDAP_ATTR_DN ||
type == SM_LDAP_ATTR_FILTER ||
type == SM_LDAP_ATTR_URL)
{
/* add to recursion */
if (sm_ldap_add_recurse(&recurse,
vals[i],
type,
{
return EX_OSERR;
}
continue;
}
}
/*
** attribute values. Otherwise, just free
** memory and move on to the next
** attribute in this entry.
*/
{
char *pe;
/* Grow result string if needed */
{
{
if (*resultsz == 0)
*resultsz = 1024;
else
*resultsz *= 2;
}
*vp_tmp = '\0';
*result,
*resultsz);
}
{
if (*resultln > 0 &&
p < pe)
*p++ = (char) delim;
{
p += sm_strlcpy(p, attr,
pe - p);
if (p < pe)
*p++ = lmap->ldap_attrsep;
}
p += sm_strlcpy(p, vals[i],
pe - p);
if (p >= pe)
{
/* Internal error: buffer too small for LDAP values */
return EX_OSERR;
}
}
}
}
/*
** We check save_errno != LDAP_DECODING_ERROR since
** OpenLDAP 1.X has a very ugly *undocumented*
** hack of returning this error code from
** ldap_next_attribute() if the library freed the
** ber attribute. See:
*/
if (save_errno != LDAP_SUCCESS &&
{
/* Must be an error */
save_errno += E_LDAPBASE;
errno = save_errno;
return EX_TEMPFAIL;
}
/* mark this DN as done */
{
}
{
}
/* We don't want multiple values and we have one */
if ((char) delim == '\0' &&
break;
}
if (save_errno != LDAP_SUCCESS &&
{
/* Must be an error */
save_errno += E_LDAPBASE;
errno = save_errno;
return EX_TEMPFAIL;
}
}
if (ret == 0)
else
{
int rc;
/*
** We may have gotten an LDAP_RES_SEARCH_RESULT response
** with an error inside it, so we have to extract that
** with ldap_parse_result(). This can happen when talking
** to an LDAP proxy whose backend has gone down.
*/
if (save_errno == LDAP_SUCCESS)
save_errno = rc;
}
if (save_errno != LDAP_SUCCESS)
{
statp = EX_TEMPFAIL;
switch (save_errno)
{
#ifdef LDAP_SERVER_DOWN
case LDAP_SERVER_DOWN:
#endif /* LDAP_SERVER_DOWN */
case LDAP_TIMEOUT:
case ETIMEDOUT:
case LDAP_UNAVAILABLE:
/*
** server disappeared,
** try reopen on next search
*/
statp = EX_RESTART;
break;
}
if (ret != 0)
save_errno += E_LDAPBASE;
errno = save_errno;
return statp;
}
{
}
if (toplevel)
{
int rlidx;
/*
** Spin through the built-up recurse list at the top
** of the recursion. Since new items are added at the
** end of the shared list, we actually only ever get
** one level of recursion before things pop back to the
** top. Any items added to the list during that recursion
** will be expanded by the top level.
*/
rlidx++)
{
int newflags;
int sid;
int status;
{
/* already expanded */
continue;
}
{
/* do DN search */
"(objectClass=*)",
}
{
/* do new search */
}
{
/* Parse URL */
if (sid != 0)
{
return EX_TEMPFAIL;
}
/* We need to add objectClass */
{
int attrnum = 0;
{
"objectClass") == 0)
{
/* already requested */
attrnum = -1;
break;
}
attrnum++;
}
if (attrnum >= 0)
{
int i;
{
save_errno = errno;
errno = save_errno;
return EX_TEMPFAIL;
}
for (i = 0 ; i < attrnum; i++)
{
}
}
}
/*
** Use the existing connection
** for this search. It really
** should use lud_scheme://lud_host:lud_port/
** instead but that would require
** opening a new connection.
** This should be fixed ASAP.
*/
/* Use the attributes specified by URL */
}
else
{
/* unknown or illegal attribute type */
return EX_SOFTWARE;
}
/* Collect results */
if (sid == -1)
{
statp = EX_TEMPFAIL;
switch (save_errno)
{
#ifdef LDAP_SERVER_DOWN
case LDAP_SERVER_DOWN:
#endif /* LDAP_SERVER_DOWN */
case LDAP_TIMEOUT:
case ETIMEDOUT:
case LDAP_UNAVAILABLE:
/*
** server disappeared,
** try reopen on next search
*/
statp = EX_RESTART;
break;
}
return statp;
}
save_errno = errno;
{
errno = save_errno;
return status;
}
/* Mark as done */
{
}
{
}
/* Reset rlidx as new items may have been added */
rlidx = -1;
}
}
return statp;
}
/*
** SM_LDAP_CLOSE -- close LDAP connection
**
** Parameters:
** lmap -- LDAP map information
**
** Returns:
** None.
**
*/
void
{
return;
}
/*
** SM_LDAP_SETOPTS -- set LDAP options
**
** Parameters:
** ld -- LDAP session handle
** lmap -- LDAP map information
**
** Returns:
** None.
**
*/
void
{
# if USE_LDAP_SET_OPTION
if (lmap->ldap_version != 0)
{
&lmap->ldap_version);
}
else
# if _FFR_LDAP_NETWORK_TIMEOUT && defined(LDAP_OPT_NETWORK_TIMEOUT)
if (lmap->ldap_networktmo > 0)
{
}
# endif /* _FFR_LDAP_NETWORK_TIMEOUT && defined(LDAP_OPT_NETWORK_TIMEOUT) */
# ifdef LDAP_OPT_RESTART
# endif /* LDAP_OPT_RESTART */
# else /* USE_LDAP_SET_OPTION */
/* From here on in we can use ldap internal timelimits */
# endif /* USE_LDAP_SET_OPTION */
}
/*
** SM_LDAP_GETERRNO -- get ldap errno value
**
** Parameters:
** ld -- LDAP session handle
**
** Returns:
** LDAP errno.
**
*/
int
{
# else /* defined(LDAP_VERSION_MAX) && LDAP_VERSION_MAX >= 3 */
# ifdef LDAP_OPT_SIZELIMIT
# else /* LDAP_OPT_SIZELIMIT */
/*
** Reset value to prevent lingering LDAP_DECODING_ERROR due to
** OpenLDAP 1.X's hack (see above)
*/
# endif /* LDAP_OPT_SIZELIMIT */
# endif /* defined(LDAP_VERSION_MAX) && LDAP_VERSION_MAX >= 3 */
return err;
}
# endif /* LDAPMAP */