/* * expand a filter (lookup or result) */ static void dict_ldap_expand_filter(char *ldapsource, char *filter, char *value, VSTRING *out) { char *myname = "dict_ldap_expand_filter"; char *sub, *end; /* * Yes, replace all instances of %s with the address to look up. Replace * %u with the user portion, and %d with the domain portion. */ sub = filter; end = sub + strlen(filter); while (sub < end) { /* * Make sure it's %[sud] and not something else. For backward * compatibilty, treat anything other than %u or %d as %s, with a * warning. */ if (*(sub) == '%') { char *u = value; char *p = strrchr(u, '@'); switch (*(sub + 1)) { case 'd': if (p) vstring_strcat(out, p + 1); break; case 'u': if (p) vstring_strncat(out, u, p - u); else vstring_strcat(out, u); break; default: msg_warn("%s: %s: Invalid filter substitution format '%%%c'!", myname, ldapsource, *(sub + 1)); /* fall through */ case 's': vstring_strcat(out, u); break; } sub++; } else vstring_strncat(out, sub, 1); sub++; } }
VSTRING *quote_822_local_flags(VSTRING *dst, const char *mbox, int flags) { const char *start; /* first byte of localpart */ const char *end; /* first byte after localpart */ const char *colon; /* * According to RFC 822, a local-part is a dot-string or a quoted-string. * We first see if the local-part is a dot-string. If it is not, we turn * it into a quoted-string. Anything else would be too painful. But * first, skip over any source route that precedes the local-part. */ if (mbox[0] == '@' && (colon = strchr(mbox, ':')) != 0) start = colon + 1; else start = mbox; if ((end = strrchr(start, '@')) == 0) end = start + strlen(start); if ((flags & QUOTE_FLAG_APPEND) == 0) VSTRING_RESET(dst); if (is_822_dot_string(start, end, flags)) { return (vstring_strcat(dst, mbox)); } else { vstring_strncat(dst, mbox, start - mbox); make_822_quoted_string(dst, start, end, flags & QUOTE_FLAG_8BITCLEAN); return (vstring_strcat(dst, end)); } }
char *casefoldx(int flags, VSTRING *dest, const char *src, ssize_t len) { size_t old_len; #ifdef NO_EAI /* * ASCII mode only. */ if (len < 0) len = strlen(src); if ((flags & CASEF_FLAG_APPEND) == 0) VSTRING_RESET(dest); old_len = VSTRING_LEN(dest); vstring_strncat(dest, src, len); lowercase(STR(dest) + old_len); return (STR(dest)); #else /* * Unicode mode. */ const char myname[] = "casefold"; static VSTRING *fold_buf = 0; static UCaseMap *csm = 0; UErrorCode error; ssize_t space_needed; int n; /* * Handle special cases. */ if (len < 0) len = strlen(src); if (dest == 0) dest = (fold_buf != 0 ? fold_buf : (fold_buf = vstring_alloc(100))); if ((flags & CASEF_FLAG_APPEND) == 0) VSTRING_RESET(dest); old_len = VSTRING_LEN(dest); /* * All-ASCII input, or ASCII mode only. */ if ((flags & CASEF_FLAG_UTF8) == 0 || allascii(src)) { vstring_strncat(dest, src, len); lowercase(STR(dest) + old_len); return (STR(dest)); } /* * ICU 4.8 ucasemap_utf8FoldCase() does not complain about UTF-8 syntax * errors. XXX Based on source-code review we conclude that non-UTF-8 * bytes are copied verbatim, and experiments confirm this. Given that * this behavior is intentional, we assume that it will stay that way. */ #if 0 if (valid_utf8_string(src, len) == 0) { if (err) *err = "malformed UTF-8 or invalid codepoint"; return (0); } #endif /* * One-time initialization. With ICU 4.8 this works while chrooted. */ if (csm == 0) { error = U_ZERO_ERROR; csm = ucasemap_open("en_US", U_FOLD_CASE_DEFAULT, &error); if (U_SUCCESS(error) == 0) msg_fatal("ucasemap_open error: %s", u_errorName(error)); } /* * Fold the input, adjusting the buffer size if needed. Safety: don't * loop forever. * * Note: the requested amount of space for casemapped output (as reported * with space_needed below) does not include storage for the null * terminator. The terminator is written only when the output buffer is * large enough. This is why we overallocate space when the output does * not fit. But if the output fits exactly, then the ouput will be * unterminated, and we have to terminate the output ourselves. */ for (n = 0; n < 3; n++) { error = U_ZERO_ERROR; space_needed = ucasemap_utf8FoldCase(csm, STR(dest) + old_len, vstring_avail(dest), src, len, &error); if (U_SUCCESS(error)) { VSTRING_AT_OFFSET(dest, old_len + space_needed); if (vstring_avail(dest) == 0) /* exact fit, no terminator */ VSTRING_TERMINATE(dest); /* add terminator */ break; } else if (error == U_BUFFER_OVERFLOW_ERROR) { VSTRING_SPACE(dest, space_needed + 1); /* for terminator */ } else { msg_fatal("%s: conversion error for \"%s\": %s", myname, src, u_errorName(error)); } } return (STR(dest)); #endif /* NO_EAI */ }
static const char *dict_ldap_lookup(DICT *dict, const char *name) { char *myname = "dict_ldap_lookup"; DICT_LDAP *dict_ldap = (DICT_LDAP *) dict; LDAPMessage *res = 0; static VSTRING *result; struct timeval tv; VSTRING *escaped_name = 0, *filter_buf = 0; int rc = 0; int sizelimit; char *sub, *end; dict_errno = 0; if (msg_verbose) msg_info("%s: In dict_ldap_lookup", myname); /* * If they specified a domain list for this map, then only search for * addresses in domains on the list. This can significantly reduce the * load on the LDAP server. */ if (dict_ldap->domain) { const char *p = strrchr(name, '@'); if (p == 0 || p == name || match_list_match(dict_ldap->domain, ++p) == 0) { if (msg_verbose) msg_info("%s: domain of %s not found in domain list", myname, name); return (0); } } /* * Initialize the result holder. */ if (result == 0) result = vstring_alloc(2); vstring_strcpy(result, ""); /* * Because the connection may be shared and invalidated via queries for * another map, update private copy of "ld" from shared connection * container. */ dict_ldap->ld = DICT_LDAP_CONN(dict_ldap)->conn_ld; /* * Connect to the LDAP server, if necessary. */ if (dict_ldap->ld == NULL) { if (msg_verbose) msg_info ("%s: No existing connection for LDAP source %s, reopening", myname, dict_ldap->ldapsource); dict_ldap_connect(dict_ldap); /* * if dict_ldap_connect() set dict_errno, abort. */ if (dict_errno) return (0); } else if (msg_verbose) msg_info("%s: Using existing connection for LDAP source %s", myname, dict_ldap->ldapsource); /* * Connection caching, means that the connection handle may have the * wrong size limit. Re-adjust before each query. This is cheap, just * sets a field in the ldap connection handle. We also do this in the * connect code, because we sometimes reconnect (below) in the middle of * a query. */ sizelimit = dict_ldap->size_limit ? dict_ldap->size_limit : LDAP_NO_LIMIT; if (ldap_set_option(dict_ldap->ld, LDAP_OPT_SIZELIMIT, &sizelimit) != LDAP_OPT_SUCCESS) msg_warn("%s: %s: Unable to set query result size limit to %ld.", myname, dict_ldap->ldapsource, dict_ldap->size_limit); /* * Prepare the query. */ tv.tv_sec = dict_ldap->timeout; tv.tv_usec = 0; escaped_name = vstring_alloc(20); filter_buf = vstring_alloc(30); /* * If any characters in the supplied address should be escaped per RFC * 2254, do so. Thanks to Keith Stevenson and Wietse. And thanks to * Samuel Tardieu for spotting that wildcard searches were being done in * the first place, which prompted the ill-conceived lookup_wildcards * parameter and then this more comprehensive mechanism. */ end = (char *) name + strlen((char *) name); sub = (char *) strpbrk((char *) name, "*()\\\0"); if (sub && sub != end) { if (msg_verbose) msg_info("%s: Found character(s) in %s that must be escaped", myname, name); for (sub = (char *) name; sub != end; sub++) { switch (*sub) { case '*': vstring_strcat(escaped_name, "\\2a"); break; case '(': vstring_strcat(escaped_name, "\\28"); break; case ')': vstring_strcat(escaped_name, "\\29"); break; case '\\': vstring_strcat(escaped_name, "\\5c"); break; case '\0': vstring_strcat(escaped_name, "\\00"); break; default: vstring_strncat(escaped_name, sub, 1); } } if (msg_verbose) msg_info("%s: After escaping, it's %s", myname, vstring_str(escaped_name)); } else vstring_strcpy(escaped_name, (char *) name); /* * Does the supplied query_filter even include a substitution? */ if ((char *) strchr(dict_ldap->query_filter, '%') == NULL) { /* * No, log the fact and continue. */ msg_warn("%s: %s: Fixed query_filter %s is probably useless", myname, dict_ldap->ldapsource, dict_ldap->query_filter); vstring_strcpy(filter_buf, dict_ldap->query_filter); } else { dict_ldap_expand_filter(dict_ldap->ldapsource, dict_ldap->query_filter, vstring_str(escaped_name), filter_buf); } /* * On to the search. */ if (msg_verbose) msg_info("%s: Searching with filter %s", myname, vstring_str(filter_buf)); rc = ldap_search_st(dict_ldap->ld, dict_ldap->search_base, dict_ldap->scope, vstring_str(filter_buf), dict_ldap->result_attributes->argv, 0, &tv, &res); if (rc == LDAP_SERVER_DOWN) { if (msg_verbose) msg_info("%s: Lost connection for LDAP source %s, reopening", myname, dict_ldap->ldapsource); ldap_unbind(dict_ldap->ld); dict_ldap->ld = DICT_LDAP_CONN(dict_ldap)->conn_ld = 0; dict_ldap_connect(dict_ldap); /* * if dict_ldap_connect() set dict_errno, abort. */ if (dict_errno) return (0); rc = ldap_search_st(dict_ldap->ld, dict_ldap->search_base, dict_ldap->scope, vstring_str(filter_buf), dict_ldap->result_attributes->argv, 0, &tv, &res); } if (rc == LDAP_SUCCESS) { /* * Search worked; extract the requested result_attribute. */ dict_ldap_get_values(dict_ldap, res, result); /* * OpenLDAP's ldap_next_attribute returns a bogus * LDAP_DECODING_ERROR; I'm ignoring that for now. */ rc = dict_ldap_get_errno(dict_ldap->ld); if (rc != LDAP_SUCCESS && rc != LDAP_DECODING_ERROR) msg_warn ("%s: Had some trouble with entries returned by search: %s", myname, ldap_err2string(rc)); if (msg_verbose) msg_info("%s: Search returned %s", myname, VSTRING_LEN(result) > 0 ? vstring_str(result) : "nothing"); } else { /* * Rats. The search didn't work. */ msg_warn("%s: Search error %d: %s ", myname, rc, ldap_err2string(rc)); /* * Tear down the connection so it gets set up from scratch on the * next lookup. */ ldap_unbind(dict_ldap->ld); dict_ldap->ld = DICT_LDAP_CONN(dict_ldap)->conn_ld = 0; /* * And tell the caller to try again later. */ dict_errno = DICT_ERR_RETRY; } /* * Cleanup. */ if (res != 0) ldap_msgfree(res); if (filter_buf != 0) vstring_free(filter_buf); if (escaped_name != 0) vstring_free(escaped_name); /* * If we had an error, return nothing, Otherwise, return the result, if * any. */ return (VSTRING_LEN(result) > 0 && !dict_errno ? vstring_str(result) : 0); }