Loading ...
Sorry, an error occurred while loading the content.

[PATCH] Domain RDN sequence substitution for LDAP search base.

Expand Messages
  • Viktor Dukhovni
    If anyone is using LDAP for virtual hosting with a separate search base for each hosted domain using domain component RDNs, please reply on list whether the
    Message 1 of 4 , Sep 24, 2013
    • 0 Attachment
      If anyone is using LDAP for virtual hosting with a separate search
      base for each hosted domain using domain component RDNs, please
      reply on list whether the feature below is useful, and whether you
      tested the code and found that it works for you (once a handful of
      people respond that this is useful, that'll be enough, you can
      test with a custom-built "postmap" binary if you like, without
      upgrading Postfix).

      Limitation: Because this expansion applies only to user@address
      queries, it cannot be used to define the set of domains in
      virtual_mailbox_domains or virtual_alias_domains. Rather it can
      only be used to define the valid mailboxes. To avoid confusion
      with partial lookup keys (bare user names) we'd need a different
      %<c> substitution to extract an RDN sequence from the full query.

      How would users with virtual hosting LDAP schemas like to designate
      the set of virtual domains (is there a common-practice LDAP query
      that given a domain as input will return a result if the domain is
      a hosted virtual domain when managing each domain in a separate
      subtree)?

      --- Patch below ---

      With:

      search_base = ou=People, %,
      query_filter = mail=%s

      a query for joeuser@... will use the search base:

      ou=People, dc=example, dc=com

      This is reportedly useful in virtual hosting configurations where
      each virtual domain is associated with its own LDAP subtree.

      The "%," substitution only applies to the LDAP search base and is
      not valid in any other context (query_filter, result_format, or
      non-LDAP tables).
      ---
      proto/ldap_table | 8 ++++++++
      src/global/db_common.c | 27 +++++++++++++++++++++++++++
      src/global/db_common.h | 4 ++++
      src/global/dict_ldap.c | 13 ++++++++-----
      src/global/dict_memcache.c | 2 +-
      src/global/dict_mysql.c | 5 +++--
      src/global/dict_pgsql.c | 5 +++--
      src/global/dict_sqlite.c | 5 +++--
      8 files changed, 57 insertions(+), 12 deletions(-)

      diff --git a/proto/ldap_table b/proto/ldap_table
      index 666aa28..2c17c87 100644
      --- a/proto/ldap_table
      +++ b/proto/ldap_table
      @@ -164,6 +164,14 @@
      # When the input key is an address of the form user@domain, \fB%d\fR
      # is replaced by the (RFC 2253) quoted domain part of the address.
      # Otherwise, the search is suppressed and returns no results.
      +# .IP "\fB\fB%,\fR\fR"
      +# When the input key is an address of the form user@domain, \fB%,\fR
      +# is replaced by the sequence of comma separated domain component RDNs
      +# of the domain part of the address. Otherwise, the search is
      +# suppressed and returns no results.
      +# .IP
      +# For example, with a lookup key of user@..., the
      +# replacement string is "dc=mail, dc=example, dc=com".
      # .IP "\fB\fB%[SUD]\fR\fR"
      # For the \fBsearch_base\fR parameter, the upper-case equivalents
      # of the above expansions behave identically to their lower-case
      diff --git a/src/global/db_common.c b/src/global/db_common.c
      index 6b3edbd..8bbba7e 100644
      --- a/src/global/db_common.c
      +++ b/src/global/db_common.c
      @@ -144,6 +144,7 @@
      * Utility library.
      */
      #include <mymalloc.h>
      +#include <stringops.h>
      #include <vstring.h>
      #include <msg.h>
      #include <dict.h>
      @@ -202,6 +203,15 @@ int db_common_parse(DICT *dict, void **ctxPtr, const char *format, int query
      : DB_COMMON_VALUE_USER;
      dynamic = 1;
      break;
      + case ',':
      + if (query & DB_COMMON_LDAP_BASE) {
      + ctx->flags |= DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_PARTIAL;
      + dynamic = 1;
      + break;
      + }
      + msg_fatal("db_common_parse: %s: Invalid %s template: %s",
      + ctx->dict->name, query ? "query" : "result", format);
      + break;
      case 'd':
      ctx->flags |=
      query ? DB_COMMON_KEY_DOMAIN | DB_COMMON_KEY_PARTIAL
      @@ -310,6 +320,8 @@ int db_common_expand(void *ctxArg, const char *format, const char *value,
      ARGV *parts = 0;
      int i;
      const char *cp;
      + char *save;
      + char *tmp;

      /* Skip NULL values, silently. */
      if (value == 0)
      @@ -407,6 +419,21 @@ int db_common_expand(void *ctxArg, const char *format, const char *value,
      VSTRING_ADDCH(result, '%');
      break;

      + case ',':
      + if (!(ctx->flags & DB_COMMON_KEY_DOMAIN))
      + msg_panic("%s: %s: %s: bad query template context",
      + myname, ctx->dict->name, format);
      + if (!vdomain)
      + msg_panic("%s: %s: %s: expanding domain-less key",
      + myname, ctx->dict->name, format);
      + save = tmp = mystrdup(vdomain);
      + for (i = 0; (domain = mystrtok(&tmp, ".")) != 0; i = 1) {
      + vstring_strcat(result, i ? ", dc=" : "dc=");
      + QUOTE_VAL(ctx->dict, quote_func, domain, result);
      + }
      + myfree(save);
      + break;
      +
      case 's':
      QUOTE_VAL(ctx->dict, quote_func, value, result);
      break;
      diff --git a/src/global/db_common.h b/src/global/db_common.h
      index 26ebf97..9a4afae 100644
      --- a/src/global/db_common.h
      +++ b/src/global/db_common.h
      @@ -18,6 +18,10 @@
      #include "dict.h"
      #include "string_list.h"

      +#define DB_COMMON_RESULT 0 /* Must be false */
      +#define DB_COMMON_QUERY 1
      +#define DB_COMMON_LDAP_BASE 2
      +
      typedef void (*db_quote_callback_t)(DICT *, const char *, VSTRING *);

      extern int db_common_parse(DICT *, void **, const char *, int);
      diff --git a/src/global/dict_ldap.c b/src/global/dict_ldap.c
      index 6ce6915..aa9bd84 100644
      --- a/src/global/dict_ldap.c
      +++ b/src/global/dict_ldap.c
      @@ -1453,8 +1453,9 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name)
      * On to the search.
      */
      if (msg_verbose)
      - msg_info("%s: %s: Searching with filter %s", myname,
      - dict_ldap->parser->name, vstring_str(query));
      + msg_info("%s: %s: Searching with base %s filter %s", myname,
      + dict_ldap->parser->name,
      + vstring_str(base), vstring_str(query));

      rc = search_st(dict_ldap->ld, vstring_str(base), dict_ldap->scope,
      vstring_str(query), dict_ldap->result_attributes->argv,
      @@ -1794,12 +1795,14 @@ DICT *dict_ldap_open(const char *ldapsource, int open_flags, int dict_flags)
      dict_ldap->ctx = 0;
      dict_ldap->dynamic_base =
      db_common_parse(&dict_ldap->dict, &dict_ldap->ctx,
      - dict_ldap->search_base, 1);
      - if (!db_common_parse(0, &dict_ldap->ctx, dict_ldap->query, 1)) {
      + dict_ldap->search_base, DB_COMMON_LDAP_BASE);
      + if (!db_common_parse(0, &dict_ldap->ctx, dict_ldap->query,
      + DB_COMMON_QUERY)) {
      msg_warn("%s: %s: Fixed query_filter %s is probably useless",
      myname, ldapsource, dict_ldap->query);
      }
      - (void) db_common_parse(0, &dict_ldap->ctx, dict_ldap->result_format, 0);
      + (void) db_common_parse(0, &dict_ldap->ctx, dict_ldap->result_format,
      + DB_COMMON_RESULT);
      db_common_parse_domain(dict_ldap->parser, dict_ldap->ctx);

      /*
      diff --git a/src/global/dict_memcache.c b/src/global/dict_memcache.c
      index e3ce925..c1b1f5a 100644
      --- a/src/global/dict_memcache.c
      +++ b/src/global/dict_memcache.c
      @@ -584,7 +584,7 @@ DICT *dict_memcache_open(const char *name, int open_flags, int dict_flags)
      */
      dict_mc->dbc_ctxt = 0;
      db_common_parse(&dict_mc->dict, &dict_mc->dbc_ctxt,
      - dict_mc->key_format, 1);
      + dict_mc->key_format, DB_COMMON_QUERY);
      db_common_parse_domain(dict_mc->parser, dict_mc->dbc_ctxt);
      if (db_common_dict_partial(dict_mc->dbc_ctxt))
      /* Breaks recipient delimiters */
      diff --git a/src/global/dict_mysql.c b/src/global/dict_mysql.c
      index a3e231a..ff8aa1b 100644
      --- a/src/global/dict_mysql.c
      +++ b/src/global/dict_mysql.c
      @@ -615,8 +615,9 @@ static void mysql_parse_config(DICT_MYSQL *dict_mysql, const char *mysqlcf)
      */
      dict_mysql->ctx = 0;
      (void) db_common_parse(&dict_mysql->dict, &dict_mysql->ctx,
      - dict_mysql->query, 1);
      - (void) db_common_parse(0, &dict_mysql->ctx, dict_mysql->result_format, 0);
      + dict_mysql->query, DB_COMMON_QUERY);
      + (void) db_common_parse(0, &dict_mysql->ctx, dict_mysql->result_format,
      + DB_COMMON_RESULT);
      db_common_parse_domain(p, dict_mysql->ctx);

      /*
      diff --git a/src/global/dict_pgsql.c b/src/global/dict_pgsql.c
      index b96a81f..5408fc2 100644
      --- a/src/global/dict_pgsql.c
      +++ b/src/global/dict_pgsql.c
      @@ -718,8 +718,9 @@ static void pgsql_parse_config(DICT_PGSQL *dict_pgsql, const char *pgsqlcf)
      */
      dict_pgsql->ctx = 0;
      (void) db_common_parse(&dict_pgsql->dict, &dict_pgsql->ctx,
      - dict_pgsql->query, 1);
      - (void) db_common_parse(0, &dict_pgsql->ctx, dict_pgsql->result_format, 0);
      + dict_pgsql->query, DB_COMMON_QUERY);
      + (void) db_common_parse(0, &dict_pgsql->ctx, dict_pgsql->result_format,
      + DB_COMMON_RESULT);
      db_common_parse_domain(p, dict_pgsql->ctx);

      /*
      diff --git a/src/global/dict_sqlite.c b/src/global/dict_sqlite.c
      index 8eb0da2..f12391c 100644
      --- a/src/global/dict_sqlite.c
      +++ b/src/global/dict_sqlite.c
      @@ -291,8 +291,9 @@ static void sqlite_parse_config(DICT_SQLITE *dict_sqlite, const char *sqlitecf)
      */
      dict_sqlite->ctx = 0;
      (void) db_common_parse(&dict_sqlite->dict, &dict_sqlite->ctx,
      - dict_sqlite->query, 1);
      - (void) db_common_parse(0, &dict_sqlite->ctx, dict_sqlite->result_format, 0);
      + dict_sqlite->query, DB_COMMON_QUERY);
      + (void) db_common_parse(0, &dict_sqlite->ctx, dict_sqlite->result_format,
      + DB_COMMON_RESULT);
      db_common_parse_domain(dict_sqlite->parser, dict_sqlite->ctx);

      /*
      --
      1.8.1.2
    • Quanah Gibson-Mount
      --On Wednesday, September 25, 2013 12:21 AM +0000 Viktor Dukhovni ... Hi Viktor, Well, I can only speak to what Zimbra does. ;) As you guess, all of our
      Message 2 of 4 , Oct 7, 2013
      • 0 Attachment
        --On Wednesday, September 25, 2013 12:21 AM +0000 Viktor Dukhovni
        <postfix-users@...> wrote:

        > If anyone is using LDAP for virtual hosting with a separate search
        > base for each hosted domain using domain component RDNs, please
        > reply on list whether the feature below is useful, and whether you
        > tested the code and found that it works for you (once a handful of
        > people respond that this is useful, that'll be enough, you can
        > test with a custom-built "postmap" binary if you like, without
        > upgrading Postfix).
        >
        > Limitation: Because this expansion applies only to user@address
        > queries, it cannot be used to define the set of domains in
        > virtual_mailbox_domains or virtual_alias_domains. Rather it can
        > only be used to define the valid mailboxes. To avoid confusion
        > with partial lookup keys (bare user names) we'd need a different
        > %<c> substitution to extract an RDN sequence from the full query.
        >
        > How would users with virtual hosting LDAP schemas like to designate
        > the set of virtual domains (is there a common-practice LDAP query
        > that given a domain as input will return a result if the domain is
        > a hosted virtual domain when managing each domain in a separate
        > subtree)?

        Hi Viktor,

        Well, I can only speak to what Zimbra does. ;) As you guess, all of our
        domains are in subtrees, so right now we use a search base of "". So it
        certainly seems to me like your patch would allow the LDAP queries to be
        more restrictive as far as search results go, which could be quite useful.
        I'll have our QA team test out the patch. It would have to vary by map,
        because sometimes we need the search base outside of people:

        As one example, for transport_maps, we currently do:

        search_base =
        query_filter =
        (&(|(zimbraMailDeliveryAddress=%s)(zimbraDomainName=%s))(zimbraMailStatus=enabled))
        result_attribute = zimbraMailTransport

        which we could then modify to:

        search_base = %
        query_filter =
        (&(|(zimbraMailDeliveryAddress=%s)(zimbraDomainName=%s))(zimbraMailStatus=enabled))
        result_attribute = zimbraMailTransport


        Note that I limit it to the domain, rather than ou=people, <domain> because
        of things like this:

        dn: dc=liquidsys,dc=com
        zimbraMailCatchAllAddress: @...
        zimbraMailCatchAllForwardingAddress: @...


        I.e., if someone sends mail to quanah@..., we have it redirected
        to quanah@...

        But for virtual_mailbox_maps, we could definitely limit it down to
        ou=people, etc:

        search_base = ou=people, %
        query_filter = (&(zimbraMailDeliveryAddress=%s)(zimbraMailStatus=enabled))
        result_attribute = zimbraMailDeliveryAddress


        On the virtual domain question, Zimbra has an attribute it uses to track
        alias & local domains (zimbraDomainType):

        For virtual_alias_domains, we do:

        search_base =
        query_filter =
        (&(zimbraDomainName=%s)(zimbraDomainType=alias)(zimbraMailStatus=enabled))
        result_attribute = zimbraDomainName

        For virtual_mailbox_domains, we do:
        search_base =
        query_filter =
        (&(zimbraDomainName=%s)(zimbraDomainType=local)(zimbraMailStatus=enabled))
        result_attribute = zimbraDomainName


        --Quanah


        --

        Quanah Gibson-Mount
        Architect - Server
        Zimbra Software, LLC
        --------------------
        Zimbra :: the leader in open source messaging and collaboration
      • Viktor Dukhovni
        ... Thanks, for looking into this. Looking forward to further feedback. ... Note, the new % substitution pattern for a comma-separated list of DC=
        Message 3 of 4 , Oct 7, 2013
        • 0 Attachment
          On Mon, Oct 07, 2013 at 11:02:35AM -0700, Quanah Gibson-Mount wrote:

          > Well, I can only speak to what Zimbra does. ;) As you guess, all of
          > our domains are in subtrees, so right now we use a search base of
          > "". So it certainly seems to me like your patch would allow the
          > LDAP queries to be more restrictive as far as search results go,
          > which could be quite useful. I'll have our QA team test out the
          > patch. It would have to vary by map, because sometimes we need the
          > search base outside of people:

          Thanks, for looking into this. Looking forward to further feedback.

          > which we could then modify to:
          >
          > search_base = %
          > query_filter = (&(|(zimbraMailDeliveryAddress=%s)(zimbraDomainName=%s))(zimbraMailStatus=enabled))
          > result_attribute = zimbraMailTransport

          Note, the new "%<c>" substitution pattern for a comma-separated
          list of DC= components is "%," not "%". I hope that's reasonably
          clear in the patch documentation.

          --
          Viktor.
        • Quanah Gibson-Mount
          --On Monday, October 07, 2013 6:07 PM +0000 Viktor Dukhovni ... Yeah, it is quite clear, I was just adjusting the config on the fly. ;) --Quanah -- Quanah
          Message 4 of 4 , Oct 7, 2013
          • 0 Attachment
            --On Monday, October 07, 2013 6:07 PM +0000 Viktor Dukhovni
            <postfix-users@...> wrote:

            > Note, the new "%<c>" substitution pattern for a comma-separated
            > list of DC= components is "%," not "%". I hope that's reasonably
            > clear in the patch documentation.

            Yeah, it is quite clear, I was just adjusting the config on the fly. ;)

            --Quanah


            --

            Quanah Gibson-Mount
            Architect - Server
            Zimbra Software, LLC
            --------------------
            Zimbra :: the leader in open source messaging and collaboration
          Your message has been successfully submitted and would be delivered to recipients shortly.