]> git.sur5r.net Git - openldap/commitdiff
The old mail500 is back restored as of 1999/06/30, plus a few updates
authorJulio Sánchez Fernández <jsanchez@openldap.org>
Thu, 8 Jun 2000 23:58:19 +0000 (23:58 +0000)
committerJulio Sánchez Fernández <jsanchez@openldap.org>
Thu, 8 Jun 2000 23:58:19 +0000 (23:58 +0000)
to put it in line with current practices.  Most likely I got them wrong,
so maight not even compile right now.

clients/mail500/README
clients/mail500/mail500.m4
clients/mail500/main.c

index 2579343e708bcf4ace5e6bca033a4558c25920ab..edd9b2e582b0ccc4c53cbfd29522c783cbf2f625 100644 (file)
@@ -1,13 +1,13 @@
-
-*** WARNING:  Preliminary ***
-
 This is the README file for mail500, a mailer that does X.500 lookups
 via LDAP.
 
-If you are planning to run mail500 at your site, you need to create a
-configuration file.  Previous versions required modifying the source
-code for configuration.  This is no longer necessary.
-there are several
+If you are planning to run mail500 at your site, there are several
+things you will have to tailor in main.c:
+
+       LDAPHOST - The host running an LDAP server
+
+       base[] - The array telling mail500 where/how to search for
+               things.  See the explanation below.
 
 *** WHAT mail500 DOES: ***
 
@@ -37,16 +37,6 @@ R<@umich.edu>$*:$*                 $>10<@>$1:$2
 R$+%$+<@umich.edu>                 $>10$1%$2<@>
 R$+!$+<@umich.edu>                 $>10$1!$2<@>
 
-You can also feed complete FQDN addresses to mail500.  For instance,
-you could define a class containing the list of domains you want to
-serve like this:
-
-FQ/etc/mail/mail500domains
-
-and then use a rule in rule set 0 like this:
-
-R$*<$=Q>$*             $#mail500 $@$2 $:<$1@$2>
-
 See the sample sendmail.cf in this directory for more details.
 For sendmail 8.9 (and later) users can use MAILER(mail500) if
 mail500.m4 is placed within sendmail's cf/mailer directory.
@@ -81,51 +71,24 @@ deliver the mail.
 
 *** HOW IT WORKS (from the mail500 side): ***
 
-When mail500 gets invoked with one or more names to which to deliver
-mail, it searches for each name in X.500.  Where it searches, and what
-kind(s) of search(es) is controlled by a configuration file.  There
-are a number of different approaches to handling mail and no general
-rules can be given.  We will however present some examples of what you
-can do.  The new mail500 is designed to be flexible and able to
-accommodate most scenarios.
-
-For instance, if you are following the mail distribution model that
-the old mail500 used, you need lines in the configuration file like
-these:
-
-search  ldap:///ou=People, dc=OpenLDAP, dc=org??sub?\
-       (|(uid=%25l)(cn==%25l))
-
-search  ldap:///ou=System Groups, ou=Groups, dc=OpenLDAP, dc=org??sub?\
-       (&(cn=%25l)(associatedDomain==%25h))
-
-search  ldap:///ou=User Groups, ou=Groups, dc=OpenLDAP, dc=org??sub?\
-       (&(cn=%25l)(associatedDomain==%25h))
-
-As you can see, searches are described by using LDAP URLs.  You can
-have as many searches as you want, but the first search that succeeds
-completes the processing for a recipient address.  You can provide an
-attribute list in the URL and it will be honored.  Otherwise, the
-attribute list will default as explained below.
-
-Filters can contain substitutions.  Actually, they *should* contain
-substitutions or the search result would not change with the recipient
-address.  Since the usual substitution character is % and it has
-special meaning in URLs, you have to represent it according to the URL
-syntax, that is, %25, 25 being the hex code of %.  The filter can be
-as complex as you want and you may make as many substitutions as you
-want.  Known substitutions at this time are:
-
-        %m   The recipient address we are considering now, maybe fully
-            qualified
-       %h   The host, that is, the value of the -h argument to
-            mail500
-       %l   The local part from %m
-       %d   The domain part from %m
-
-So, in the above example, if the recipient address were
-name@OpenLDAP.org, mail500 would do the the following searches,
-stopping if it found anything at any step:
+When mail500 gets invoked with one or more names to which to
+deliver mail, it searches for each name in X.500.  Where it searches,
+and what kind(s) of search(es) it does are compile-time configurable
+by changing the base array in main.c.  For example, the configuration
+we use at U-M is like this:
+
+       Base    base[] =
+               { "ou=People, dc=OpenLDAP, dc=org", 0
+                       "uid=%s", "cn=%s", NULL,
+                 "ou=System Groups, ou=Groups, dc=OpenLDAP, dc=org", 1
+                       "(&(cn=%s)(associatedDomain=%h))", NULL, NULL,
+                 "ou=User Groups, ou=Groups, dc=OpenLDAP, dc=org", 1
+                       "(&(cn=%s)(associatedDomain=%h))", NULL, NULL,
+                 NULL
+               };
+
+which means that in delivering mail to "name" mail500 would do the
+the following searches, stopping if it found anything at any step:
 
        Search (18) [2]: dc=org@dc=OpenLDAP@ou=People
        Search subtree (uid=name)
@@ -138,7 +101,11 @@ stopping if it found anything at any step:
        Search (18) [5]: dc=org@dc=OpenLDAP@ou=Groups@ou=User Groups
        Search subtree & ((cn=name)(associatedDomain=OpenLDAP.org))
 
-[Beware: Currently unimplemented]
+Notice that when specifying a filter %s is replaced by the name,
+or user portion of the address while %h is replaced by whatever is
+passed in to mail500 via the -h option (typically the host portion
+of the address).
+
 You can also specify whether you want search results that matched
 because the entry's RDN matched the search to be given preference
 or not.  At U-M, we only give such preference in the mail group
@@ -146,148 +113,12 @@ portion of the searches.  Beware with this option:  the algorithm
 used to decide whether an entry's RDN matched the search is very
 simple-minded, and may not always be correct.
 
-*** HOW IT WORKS (from the X.500 side): ***
+There is currently no limit on the number of areas searched (the base
+array can be as large as you want), and an arbitrary limit of 2 filters
+for each base.  If you want more than that, simply changing the 3 in
+the typedef for Base should do the trick.
 
-First you need to decide what attributes you will search for and what
-attributes will be used to deliver the message.  In the classical
-mail500, we would search by uid or cn and deliver to the mail
-attribute.  Another model is to search by the mail attribute and
-deliver to something else, such as the uid if determined that the user
-has a local account.
-
-*** THE CONFIGURATION FILE
-
-The configuration file is composed of lines that prescribe the
-operation of mail500.  Blank lines are ignored and lines beginning
-with # are considered comments and ignored.  Outside comments, the
-sequence '\', newline, whitespace is ignored so that long lines can be
-split for readability.
-
-Attribute Definitions
-
-Lines starting with 'attribute' define the semantics of an attribute.
-Notice that attributes will be considered in the order they are
-defined in the configuration file.  This means that the presence of
-some can preempt processing of other attributes and that attributes
-that simply collect needed information must be defined before others
-that use that information.  The format is:
-
-attribute name [multivalued] [final] [multiple-entries] [<syntax>] [<kind>]
-
-If the attribute is "multivalued", all values will be considered.  If
-it is not and several values are found the entry is declared in error.
-
-If the attribute is "final", its presence in an entry prevents further
-analysis of the entry.
-
-If the attribute is "multiple-entries" and it is of an appropriate
-syntax that can point to other entries, all such entries are
-considered, otherwise the entry is in error.
-
-The known kinds are:
-
-recipient              The value(s) of this attribute should be
-                       used as the address(es) to deliver the message
-                       to if they are in an appropriate syntax.  If
-                       they otherwise point at other entries, they
-                       should be retrieved and expanded as necessary
-                       to complete the resolution of this entry.  The
-                       process is recursive and all.
-
-errors                 The value(s) of this attribute represent the
-                       entities that should receive error messages
-                       for mail messages directed to this entry.
-                       The presence of an attribute of this kind
-                       force a change in the envelope sender address
-                       of the message.
-
-The known syntaxes are:
-
-local-native-mailbox   An unqualified mailbox name
-rfc822                 A fully qualified RFC822 mail address
-rfc822-extended                Currently identical to rfc822
-dn                     The Distinguished Name of some other entry
-url                    A URL either of the mailto: or ldap: styles,
-                       others styles, notably file:, could be added.
-                       No substitutions are supported currently.
-search-with-filter=<filter>    Do a search on all known search bases
-                       with the give filter.  The only currenty
-                       substitution available is %D, the DN of the
-                       current entry.
-
-The default attributes to search
-
-A line starting with "default-attributes" contains a comma-separated
-list of attributes to use in searches everytime a specific list is not
-known.
-
-Search bases
-
-As shown in the example above, lines starting with "search" provide
-the search bases to use to initially try to resolve each entry or when
-using attributes of syntax "search-with-filter".
-
-*** EXAMPLES
-
-A configuration file that approximates the operation of the old
-mail500 runs as follows:
-
-attribute errorsTo                             errors dn
-attribute rfc822ErrorsTo                       errors rfc822
-attribute requestsTo                           request dn
-attribute rfc822RequestsTo                     request rfc822
-attribute owner                                        owner dn
-attribute mail                 multivalued     recipient rfc822
-attribute member               multivalued     recipient dn
-attribute joinable     multiple-entries        recipient \
-         search-with-filter=(memberOfGroup=%D)
-
-default-attributes objectClass,title,postaladdress,telephoneNumber,\
-       mail,description,owner,errorsTo,rfc822ErrorsTo,requestsTo,\
-       rfc822RequestsTo,joinable,cn,member,moderator,onVacation,uid,\
-       suppressNoEmailError
-
-# Objectclasses that, when present, identify an entry as a group
-group-classes mailGroup
-
-search  ldap:///ou=People, dc=OpenLDAP, dc=org??sub?\
-       (|(uid=%25l)(cn==%25l))
-
-search  ldap:///ou=System Groups, ou=Groups, dc=OpenLDAP, dc=org??sub?\
-       (&(cn=%25l)(associatedDomain==%25h))
-
-search  ldap:///ou=User Groups, ou=Groups, dc=OpenLDAP, dc=org??sub?\
-       (&(cn=%25l)(associatedDomain==%25h))
-
-A configuration that approximates the semantics of the mailRecipient
-and mailGroup classes used by Netscape:
-
-attribute mgrpErrorsTo                         errors url
-attribute rfc822ErrorsTo                       errors rfc822
-attribute mailRoutingAddress   final           recipient rfc822
-attribute mailHost             final           host forward-to-host
-attribute uid                  final           recipient local-native-mailbox
-attribute uniqueMember         multivalued     recipient dn
-attribute mgrpRFC822MailMember multivalued     recipient rfc822-extended
-attribute mgrpDeliverTo                multivalued multiple-entries recipient url
-
-default-attributes objetcClass,mailRoutingAddress,mailHost,uid,uniqueMember,\
-       mgrpRFC822MailMember,mgrpErrorsTo,rfc822ErrorsTo
-
-# Objectclasses that, when present, identify an entry as a group
-group-classes mailGroup
-
-search  ldap://localhost/dc=OpenLDAP,dc=org?\
-       objectClass,mailRoutingAddress,mailHost,uid?\
-       sub?\
-       (&(|(mail=%25m)(mailAlternateAddress=%25m))(objectClass=mailRecipient))
-
-search  ldap://localhost/dc=OpenLDAP,dc=org?\
-       objectClass,uniqueMember,mgrpRFC822MailMember,mgrpErrorsTo,mgrpDeliverTo,rfc822ErrorsTo?\
-       sub?\
-       (&(|(mail=%25m)(mailAlternateAddress=%25m))(objectClass=mailGroup))
-
-[ The rest is from the original README and I did not rewrite it yet ]
+*** HOW IT WORKS (from the X.500 side): ***
 
 In X.500, there are several new attribute types and one new object
 class defined that mail500 makes use of.  At its most basic, for normal
index a55e36b16c1053a01378b44ac8ca98db9c5178d4..c5cb36b4d604f46c1513ff435c37307358c936e8 100644 (file)
@@ -6,6 +6,7 @@ PUSHDIVERT(-1)
 ## as authorized by the OpenLDAP Public License.  A copy of this
 ## license is available at http://www.OpenLDAP.org/license.html or
 ## in file LICENSE in the top-level directory of the distribution.
+POPDIVERT
 
 dnl
 dnl mail500 mailer
@@ -16,10 +17,8 @@ dnl  MAILER(mail500)
 dnl
 
 ifdef(`MAIL500_HOST',
-       `define(`MAIL500_HOST_FLAG', CONCAT(` -l ', CONCAT(MAIL500_HOST,` ')))',
-       `define(`MAIL500_HOST_FLAG', `')')
-ifdef(`MAIL500_CONFIG_PATH',,
-       `define(`MAIL500_CONFIG_PATH', /etc/mail/mail500.conf)')
+       `define(`MAIL500_HOST_FLAG', `')',
+       `define(`MAIL500_HOST_FLAG', CONCAT(` -l ', CONCAT(MAIL500_HOST,` ')))')
 ifdef(`MAIL500_MAILER_PATH',,
        `ifdef(`MAIL500_PATH',
                `define(`MAIL500_MAILER_PATH', MAIL500_PATH)',
@@ -28,9 +27,8 @@ ifdef(`MAIL500_MAILER_FLAGS',,
        `define(`MAIL500_MAILER_FLAGS', `SmnXuh')')
 ifdef(`MAIL500_MAILER_ARGS',,
        `define(`MAIL500_MAILER_ARGS',
-               CONCAT(`mail500',CONCAT(` -C ',MAIL500_CONFIG_PATH,MAIL500_HOST_FLAG,`-f $f -m $n@$w $u')))')
-
-POPDIVERT
+               CONCAT(`mail500',CONCAT(MAIL500_HOST_FLAG,`-f $f -h $h -m $n@$w $u')))')
+dnl
 
 MAILER_DEFINITIONS
 
@@ -49,5 +47,5 @@ LOCAL_CONFIG
 
 PUSHDIVERT(3)
 # mail500 additions
-R$* < @ $=Q > $*       $#mail500 $@ $2 $: <$1@$2>              domain handled by mail500
+R$* < @ $=Q > $*       $#mail500 $@ $2 $: <$1>         domain handled by mail500
 POPDIVERT
index 9a2ad1aceba02c23ddf68e96ca22cebd58b536f6..3d0c538a24585ba2b184eb122f88f47cd59b9537 100644 (file)
@@ -61,7 +61,7 @@
 
 LDAP   *ld;
 char   *vacationhost = NULL;
-char   *errorsfrom = MAIL500_BOUNCEFROM;
+char   *errorsfrom = NULL;
 char   *mailfrom = NULL;
 char   *host = NULL;
 char   *ldaphost = NULL;
@@ -82,7 +82,6 @@ typedef struct errs {
 #define E_NOMEMBERS            10
 #define        E_NOOWNER               11
 #define E_GROUPUNKNOWN         12
-#define E_NOOWNADDRESS         13
        char            *e_addr;
        union e_union_u {
                char            *e_u_loop;
@@ -96,89 +95,63 @@ typedef struct groupto {
        char    *g_dn;
        char    *g_errorsto;
        char    **g_members;
-       int     g_nmembers;
 } Group;
 
 typedef struct baseinfo {
-       char    *b_url;
-       int     b_m_entries;
+       char    *b_dn;          /* dn to start searching at */
        char    b_rdnpref;      /* give rdn's preference when searching? */
        int     b_search;       /* ORed with the type of thing the address */
                                /*  looks like (USER, GROUP_ERRORS, etc.)  */
                                /*  to see if this should be searched      */
+       char    *b_filter[3];   /* filter to apply - name substituted for %s */
+                               /* (up to three of them) */
 } Base;
 
-Base   **base = NULL;
+Base   base[] = {
+       {"ou=People, dc=OpenLDAP, dc=org",
+               0, USER,
+               {"uid=%s", "cn=%s", NULL}},
+       {"ou=System Groups, ou=Groups, dc=OpenLDAP, dc=org",
+               1, 0xff,
+               {"(&(cn=%s)(associatedDomain=%h))", NULL, NULL}},
+       {"ou=User Groups, ou=Groups, dc=OpenLDAP, dc=org",
+               1, 0xff,
+               {"(&(cn=%s)(associatedDomain=%h))", NULL, NULL}},
+       {NULL}
+};
 
 char   *sendmailargs[] = { MAIL500_SENDMAIL, "-oMrLDAP", "-odi", "-oi", "-f", NULL, NULL };
 
-typedef struct attr_semantics {
-       char    *as_name;
-       int     as_m_valued;    /* Is multivalued? */
-       int     as_priority;    /* Priority level of this attribut type */
-       int     as_syntax;      /* How to interpret values */
-       int     as_m_entries;   /* Can resolve to several entries? */
-       int     as_kind;        /* Recipient, sender, etc. */
-       char    *as_param;      /* Extra info for filters and things alike */
-} AttrSemantics;
-
-#define AS_SYNTAX_UNKNOWN      0
-#define AS_SYNTAX_NATIVE_MB    1       /* Unqualified mailbox name */
-#define AS_SYNTAX_RFC822       2       /* RFC822 mail address */
-#define AS_SYNTAX_HOST         3
-#define AS_SYNTAX_DN           4       /* A directory entry */
-#define AS_SYNTAX_RFC822_EXT   5
-#define AS_SYNTAX_URL          6       /* mailto: or ldap: URL */
-#define AS_SYNTAX_BOOL_FILTER  7       /* For joinable, filter in as_param */
-#define AS_SYNTAX_PRESENT      8       /* Value irrelevant, only presence is
-                                        * considered. */
-
-#define AS_KIND_UNKNOWN                0
-#define AS_KIND_RECIPIENT      1
-#define AS_KIND_ERRORS         2       /* For ErrorsTo and similar */
-#define AS_KIND_REQUEST                3
-#define AS_KIND_OWNER          4
-#define AS_KIND_ROUTE_TO_HOST  5       /* Expand at some other host */
-#define AS_KIND_ALLOWED_SENDER 6       /* Can send to group */
-#define AS_KIND_MODERATOR      7
-#define AS_KIND_ROUTE_TO_ADDR  8       /* Rewrite recipient address as */
-#define AS_KIND_OWN_ADDR       9       /* RFC822 name of this entry */
-#define AS_KIND_DELIVERY_TYPE  10      /* How to deliver mail to this entry */
-
-AttrSemantics **attr_semantics = NULL;
-int current_priority = 0;
-
-typedef struct subst {
-       char    sub_char;
-       char    *sub_value;
-} Subst;
-
-char   **groupclasses = NULL;
-char   **def_attr = NULL;
-char   **myhosts = NULL;               /* FQDNs not to route elsewhere */
-char   **mydomains = NULL;             /* If an RFC822 address points to one
-                                          of these domains, search it in the
-                                          directory instead of returning it
-                                          to hte MTA */
-
-static void load_config( char *filespec );
-static void split_address( char *address, char **localpart, char **domainpart);
-static int entry_engine( LDAPMessage *e, char *dn, char *address, char ***to, int *nto, Group ***togroups, int *ngroups, Error **err, int *nerr, int type );
-static void do_address( char *name, char ***to, int *nto, Group ***togroups, int *ngroups, Error **err, int *nerr, int type );
+static char    *attrs[] = { "objectClass", "title", "postaladdress",
+                       "telephoneNumber", "mail", "description", "owner",
+                       "errorsTo", "rfc822ErrorsTo", "requestsTo",
+                       "rfc822RequestsTo", "joinable", "cn", "member",
+                       "moderator", "onVacation", "uid",
+                       "suppressNoEmailError", NULL };
+
+static void do_address( char *name, char ***to, int *nto, Group **togroups, int *ngroups, Error **err, int *nerr, int type );
+static int  do_group( LDAPMessage *e, char *dn, char ***to, int *nto, Group **togroups, int *ngroups, Error **err, int *nerr );
+static void do_group_members( LDAPMessage *e, char *dn, char ***to, int *nto, Group **togroups, int *ngroups, Error **err, int *nerr );
 static void send_message( char **to );
 static void send_errors( Error *err, int nerr );
 static void do_noemail( FILE *fp, Error *err, int namelen );
 static void do_ambiguous( FILE *fp, Error *err, int namelen );
-static int count_values( char **list );
 static void add_to( char ***list, int *nlist, char **new );
-static void add_single_to( char ***list, char *new );
 static int  isgroup( LDAPMessage *e );
 static void add_error( Error **err, int *nerr, int code, char *addr, LDAPMessage *msg );
-static void unbind_and_exit( int rc ) LDAP_GCCATTR((noreturn));
-static void send_group( Group **group, int ngroup );
-
+static void add_group( char *dn, Group **list, int *nlist );
+static void unbind_and_exit( int rc );
+static int  group_loop( char *dn );
+static void send_group( Group *group, int ngroup );
+static int  has_attributes( LDAPMessage *e, char *attr1, char *attr2 );
+static char **get_attributes_mail_dn( LDAPMessage *e, char *attr1, char *attr2 );
+static char *canonical( char *s );
 static int  connect_to_x500( void );
 
+static void do_group_errors( LDAPMessage *e, char *dn, char ***to, int *nto, Error **err, int *nerr );
+static void do_group_request( LDAPMessage *e, char *dn, char ***to, int *nto, Error **err, int *nerr );
+static void do_group_owner( LDAPMessage *e, char *dn, char ***to, int *nto, Error **err, int *nerr );
+static void add_member( char *gdn, char *dn, char ***to, int *nto, Group **togroups, int *ngroups, Error **err, int *nerr, char **suppress );
 
 int
 main ( int argc, char **argv )
@@ -186,10 +159,9 @@ main ( int argc, char **argv )
        char            *myname;
        char            **tolist;
        Error           *errlist;
-       Group           **togroups;
+       Group           *togroups;
        int             numto, ngroups, numerr, nargs;
        int             i, j;
-       char            *conffile = NULL;
 
        if ( (myname = strrchr( argv[0], '/' )) == NULL )
                myname = strdup( argv[0] );
@@ -206,14 +178,10 @@ main ( int argc, char **argv )
        openlog( myname, OPENLOG_OPTIONS );
 #endif
 
-       while ( (i = getopt( argc, argv, "d:C:f:h:l:m:v:" )) != EOF ) {
+       while ( (i = getopt( argc, argv, "d:f:h:l:m:v:" )) != EOF ) {
                switch( i ) {
                case 'd':       /* turn on debugging */
-                       debug |= atoi( optarg );
-                       break;
-
-               case 'C':       /* path to configuration file */
-                       conffile = strdup( optarg );
+                       debug = atoi( optarg );
                        break;
 
                case 'f':       /* who it's from & where errors should go */
@@ -258,17 +226,11 @@ main ( int argc, char **argv )
                syslog( LOG_ALERT, "required argument -m not present" );
                exit( EX_TEMPFAIL );
        }
-/*     if ( host == NULL ) { */
-/*             syslog( LOG_ALERT, "required argument -h not present" ); */
-/*             exit( EX_TEMPFAIL ); */
-/*     } */
-       if ( conffile == NULL ) {
-               syslog( LOG_ALERT, "required argument -C not present" );
+       if ( host == NULL ) {
+               syslog( LOG_ALERT, "required argument -h not present" );
                exit( EX_TEMPFAIL );
        }
 
-       load_config( conffile );
-
        if ( connect_to_x500() != 0 )
                exit( EX_TEMPFAIL );
 
@@ -298,12 +260,14 @@ main ( int argc, char **argv )
        for ( i = optind; i < argc; i++ ) {
                char    *s;
                int     type;
-               char    *localpart = NULL, *domainpart = NULL;
-               char    address[1024];
+
+               for ( j = 0; argv[i][j] != '\0'; j++ ) {
+                       if ( argv[i][j] == '.' || argv[i][j] == '_' )
+                               argv[i][j] = ' ';
+               }
 
                type = USER;
-               split_address( argv[i], &localpart, &domainpart );
-               if ( (s = strrchr( localpart, '-' )) != NULL ) {
+               if ( (s = strrchr( argv[i], '-' )) != NULL ) {
                        s++;
 
                        if ((strcasecmp(s, ERROR) == 0) ||
@@ -324,15 +288,7 @@ main ( int argc, char **argv )
                        }
                }
 
-               if ( domainpart ) {
-                       sprintf( address, "%s@%s", localpart, domainpart );
-                       free( localpart );
-                       free( domainpart );
-               } else {
-                       sprintf( address, "%s@%s", localpart, domainpart );
-                       free( localpart );
-               }
-               do_address( address, &tolist, &numto, &togroups, &ngroups,
+               do_address( argv[i], &tolist, &numto, &togroups, &ngroups,
                    &errlist, &numerr, type );
        }
 
@@ -400,276 +356,6 @@ main ( int argc, char **argv )
        return( EX_OK );
 }
 
-static char *
-get_config_line( FILE *cf, int *lineno)
-{
-       static char     buf[2048];
-       int             len;
-       int             pos;
-       int             room;
-
-       pos = 0;
-       room = sizeof( buf );
-       while ( fgets( &buf[pos], room, cf ) ) {
-               (*lineno)++;
-               if ( pos > 0 ) {
-                       /* Delete whitespace at the beginning of new data */
-                       if ( isspace( buf[pos] ) ) {
-                               char *s, *d;
-                               for ( s = buf+pos; isspace(*s); s++ )
-                                       ;
-                               for ( d = buf+pos; *s; s++, d++ ) {
-                                       *d = *s;
-                               }
-                               *d = *s;
-                       }
-               }
-               len = strlen( buf );
-               if ( buf[len-1] != '\n' ) {
-                       syslog( LOG_ALERT, "Definition too long at line %d",
-                               *lineno );
-                       exit( EX_TEMPFAIL );
-               }
-               if ( buf[0] == '#' )
-                       continue;
-               if ( strspn( buf, " \t\n" ) == len )
-                       continue;
-               if ( buf[len-2] == '\\' ) {
-                       pos = len - 2;
-                       room = sizeof(buf) - pos;
-                       continue;
-               }
-               /* We have a real line, we will exit the loop */
-               buf[len-1] = '\0';
-               return( buf );
-       }
-       return( NULL );
-}
-
-static void
-add_url ( char *url, int rdnpref, int typemask )
-{
-       Base            **list_temp;
-       int             size;
-       Base            *b;
-
-       b = calloc(1, sizeof(Base));
-       if ( !b ) {
-               syslog( LOG_ALERT, "Out of memory" );
-               exit( EX_TEMPFAIL );
-       }
-       b->b_url = strdup( url );
-       b->b_rdnpref = rdnpref;
-       b->b_search   = typemask;
-
-       if ( base == NULL ) {
-               base = calloc(2, sizeof(LDAPURLDesc *));
-               if ( !base ) {
-                       syslog( LOG_ALERT, "Out of memory" );
-                       exit( EX_TEMPFAIL );
-               }
-               base[0] = b;
-       } else {
-               for ( size = 0; base[size]; size++ )
-                       ;
-               size += 2;
-               list_temp = realloc( base, size*sizeof(LDAPURLDesc *) );
-               if ( !list_temp ) {
-                       syslog( LOG_ALERT, "Out of memory" );
-                       exit( EX_TEMPFAIL );
-               }
-               base = list_temp;
-               base[size-2] = b;
-               base[size-1] = NULL;
-       }
-}
-
-static void
-add_def_attr( char *s )
-{
-       char *p, *q;
-
-       p = s;
-       while ( *p ) {
-               p += strspn( p, "\t," );
-               q = strpbrk( p, " \t," );
-               if ( q ) {
-                       *q = '\0';
-                       add_single_to( &def_attr, p );
-               } else {
-                       add_single_to( &def_attr, p );
-                       break;
-               }
-               p = q + 1;
-       }
-}
-
-static void
-add_attr_semantics( char *s )
-{
-       char *p, *q;
-       AttrSemantics *as;
-
-       as = calloc( 1, sizeof( AttrSemantics ) );
-       as->as_priority = current_priority;
-       p = s;
-       while ( isspace ( *p ) )
-               p++;
-       q = p;
-       while ( !isspace ( *q ) && *q != '\0' )
-               q++;
-       *q = '\0';
-       as->as_name = strdup( p );
-       p = q + 1;
-
-       while ( *p ) {
-               while ( isspace ( *p ) )
-                       p++;
-               q = p;
-               while ( !isspace ( *q ) && *q != '\0' )
-                       q++;
-               *q = '\0';
-               if ( !strcasecmp( p, "multivalued" ) ) {
-                       as->as_m_valued = 1;
-               } else if ( !strcasecmp( p, "multiple-entries" ) ) {
-                       as->as_m_entries = 1;
-               } else if ( !strcasecmp( p, "local-native-mailbox" ) ) {
-                       as->as_syntax = AS_SYNTAX_NATIVE_MB;
-               } else if ( !strcasecmp( p, "rfc822" ) ) {
-                       as->as_syntax = AS_SYNTAX_RFC822;
-               } else if ( !strcasecmp( p, "rfc822-extended" ) ) {
-                       as->as_syntax = AS_SYNTAX_RFC822_EXT;
-               } else if ( !strcasecmp( p, "dn" ) ) {
-                       as->as_syntax = AS_SYNTAX_DN;
-               } else if ( !strcasecmp( p, "url" ) ) {
-                       as->as_syntax = AS_SYNTAX_URL;
-               } else if ( !strcasecmp( p, "search-with-filter" ) ) {
-                       as->as_syntax = AS_SYNTAX_BOOL_FILTER;
-               } else if ( !strncasecmp( p, "param=", 6 ) ) {
-                       q = strchr( p, '=' );
-                       if ( q ) {
-                               p = q + 1;
-                               while ( *q && !isspace( *q ) ) {
-                                       q++;
-                               }
-                               if ( *q ) {
-                                       *q = '\0';
-                                       as->as_param = strdup( p );
-                                       p = q + 1;
-                               } else {
-                                       as->as_param = strdup( p );
-                                       p = q;
-                               }
-                       }
-               } else if ( !strcasecmp( p, "host" ) ) {
-                       as->as_kind = AS_SYNTAX_HOST;
-               } else if ( !strcasecmp( p, "present" ) ) {
-                       as->as_kind = AS_SYNTAX_PRESENT;
-               } else if ( !strcasecmp( p, "route-to-host" ) ) {
-                       as->as_kind = AS_KIND_ROUTE_TO_HOST;
-               } else if ( !strcasecmp( p, "route-to-address" ) ) {
-                       as->as_kind = AS_KIND_ROUTE_TO_ADDR;
-               } else if ( !strcasecmp( p, "own-address" ) ) {
-                       as->as_kind = AS_KIND_OWN_ADDR;
-               } else if ( !strcasecmp( p, "recipient" ) ) {
-                       as->as_kind = AS_KIND_RECIPIENT;
-               } else if ( !strcasecmp( p, "errors" ) ) {
-                       as->as_kind = AS_KIND_ERRORS;
-               } else if ( !strcasecmp( p, "request" ) ) {
-                       as->as_kind = AS_KIND_REQUEST;
-               } else if ( !strcasecmp( p, "owner" ) ) {
-                       as->as_kind = AS_KIND_OWNER;
-               } else if ( !strcasecmp( p, "delivery-type" ) ) {
-                       as->as_kind = AS_KIND_DELIVERY_TYPE;
-               } else {
-                       syslog( LOG_ALERT,
-                               "Unknown semantics word %s", p );
-                       exit( EX_TEMPFAIL );
-               }
-               p = q + 1;
-       }
-       if ( attr_semantics == NULL ) {
-               attr_semantics = calloc(2, sizeof(AttrSemantics *));
-               if ( !attr_semantics ) {
-                       syslog( LOG_ALERT, "Out of memory" );
-                       exit( EX_TEMPFAIL );
-               }
-               attr_semantics[0] = as;
-       } else {
-               int size;
-               AttrSemantics **list_temp;
-               for ( size = 0; attr_semantics[size]; size++ )
-                       ;
-               size += 2;
-               list_temp = realloc( attr_semantics,
-                                    size*sizeof(AttrSemantics *) );
-               if ( !list_temp ) {
-                       syslog( LOG_ALERT, "Out of memory" );
-                       exit( EX_TEMPFAIL );
-               }
-               attr_semantics = list_temp;
-               attr_semantics[size-2] = as;
-               attr_semantics[size-1] = NULL;
-       }
-}
-
-static void
-load_config( char *filespec )
-{
-       FILE            *cf;
-       char            *line;
-       int             lineno = 0;
-       char            *p;
-       int             rdnpref;
-       int             typemask;
-
-       cf = fopen( filespec, "r" );
-       if ( !cf ) {
-               perror( "Opening config file" );
-               exit( EX_TEMPFAIL );
-       }
-
-       while ( ( line = get_config_line( cf,&lineno ) ) ) {
-               p = strpbrk( line, " \t" );
-               if ( !p ) {
-                       syslog( LOG_ALERT,
-                               "Missing space at line %d", lineno );
-                       exit( EX_TEMPFAIL );
-               }
-               if ( !strncmp( line, "search", p-line ) ) {
-                       p += strspn( p, " \t" );
-                       /* TBC, get these */
-                       rdnpref = 0;
-                       typemask = 0xFF;
-                       add_url( p, rdnpref, typemask );
-               } else if ( !strncmp(line, "attribute", p-line) ) {
-                       p += strspn(p, " \t");
-                       add_attr_semantics( p );
-               } else if ( !strncmp(line, "default-attributes", p-line) ) {
-                       p += strspn(p, " \t");
-                       add_def_attr( p );
-               } else if ( !strncmp(line, "group-classes", p-line) ) {
-                       p += strspn(p, " \t");
-                       add_single_to( &groupclasses, p );
-               } else if ( !strncmp(line, "priority", p-line) ) {
-                       p += strspn(p, " \t");
-                       current_priority = atoi(p);
-               } else if ( !strncmp(line, "domain", p-line) ) {
-                       p += strspn(p, " \t");
-                       add_single_to( &mydomains, p );
-               } else if ( !strncmp(line, "host", p-line) ) {
-                       p += strspn(p, " \t");
-                       add_single_to( &myhosts, p );
-               } else {
-                       syslog( LOG_ALERT,
-                               "Unparseable config definition at line %d",
-                               lineno );
-                       exit( EX_TEMPFAIL );
-               }
-       }
-       fclose( cf );
-}
-
 static int
 connect_to_x500( void )
 {
@@ -680,10 +366,8 @@ connect_to_x500( void )
                return( -1 );
        }
 
-       /*  TBC: Set this only when it makes sense
        opt = MAIL500_MAXAMBIGUOUS;
        ldap_set_option(ld, LDAP_OPT_SIZELIMIT, &opt);
-       */
        opt = LDAP_DEREF_ALWAYS;
        ldap_set_option(ld, LDAP_OPT_DEREF, &opt);
 
@@ -695,265 +379,148 @@ connect_to_x500( void )
        return( 0 );
 }
 
-static Group *
-new_group( char *dn, Group ***list, int *nlist )
-{
-       int     i;
-       Group   *this_group;
-
-       for ( i = 0; i < *nlist; i++ ) {
-               if ( strcmp( dn, (*list)[i]->g_dn ) == 0 ) {
-                       syslog( LOG_ALERT, "group loop 2 detected (%s)", dn );
-                       return NULL;
-               }
-       }
-
-       this_group = (Group *) malloc( sizeof(Group) );
-
-       if ( *nlist == 0 ) {
-               *list = (Group **) malloc( sizeof(Group *) );
-       } else {
-               *list = (Group **) realloc( *list, (*nlist + 1) *
-                   sizeof(Group *) );
-       }
-
-       this_group->g_errorsto = NULL;
-       this_group->g_members = NULL;
-       this_group->g_nmembers = 0;
-       /* save the group's dn so we can check for loops above */
-       this_group->g_dn = strdup( dn );
-
-       (*list)[*nlist] = this_group;
-       (*nlist)++;
-
-       return( this_group );
-}
-
-static void
-split_address(
-       char    *address,
-       char    **localpart,
-       char    **domainpart
-)
-{
-       char            *p;
-
-       if ( ( p = strrchr( address, '@' ) ) == NULL ) {
-               *localpart = strdup( address );
-               *domainpart = NULL;
-       } else {
-               *localpart = malloc( p - address + 1 );
-               strncpy( *localpart, address, p - address );
-               (*localpart)[p - address] = '\0';
-               p++;
-               *domainpart = strdup( p );
-       }
-}
-
 static int
-dn_search(
-       char    **dnlist, 
-       char    *address,
-       char    ***to,
-       int     *nto,
-       Group   ***togroups,
-       int     *ngroups,
-       Error   **err,
-       int     *nerr
-)
+mailcmp( char *a, char *b )
 {
-       int             rc;
-       int             i;
-       int             resolved = 0;
-       LDAPMessage     *res, *e;
-       struct timeval  timeout;
+       int     i;
 
-       timeout.tv_sec = MAIL500_TIMEOUT;
-       timeout.tv_usec = 0;
-       for ( i = 0; dnlist[i]; i++ ) {
-               if ( (rc = ldap_search_st( ld, dnlist[i], LDAP_SCOPE_BASE,
-                       NULL, def_attr, 0,
-                        &timeout, &res )) != LDAP_SUCCESS ) {
-                       if ( rc == LDAP_NO_SUCH_OBJECT ) {
-                               add_error( err, nerr, E_BADMEMBER, dnlist[i], NULL );
-                               continue;
-                       } else {
-                               syslog( LOG_ALERT, "member search return 0x%x", rc );
+       for ( i = 0; a[i] != '\0'; i++ ) {
+               if ( a[i] != b[i] ) {
+                       switch ( a[i] ) {
+                       case ' ':
+                       case '.':
+                       case '_':
+                               if ( b[i] == ' ' || b[i] == '.' || b[i] == '_' )
+                                       break;
+                               return( 1 );
 
-                               unbind_and_exit( EX_TEMPFAIL );
-                       }
-               } else {
-                       if ( (e = ldap_first_entry( ld, res )) == NULL ) {
-                               syslog( LOG_ALERT, "member search error parsing entry" );
-                               unbind_and_exit( EX_TEMPFAIL );
-                       }
-                       if ( entry_engine( e, dnlist[i], address, to, nto,
-                                          togroups, ngroups, err, nerr,
-                                          USER | GROUP_MEMBERS ) ) {
-                               resolved = 1;
+                       default:
+                               return( 1 );
                        }
                }
        }
-       return( resolved );
+
+       return( 0 );
 }
 
-static int
-search_ldap_url(
-       char    *url,
-       Subst   *substs,
-       char    *address,
-       int     rdnpref,
-       int     multi_entry,
+static void
+do_address(
+       char    *name,
        char    ***to,
        int     *nto,
-       Group   ***togroups,
+       Group   **togroups,
        int     *ngroups,
        Error   **err,
        int     *nerr,
        int     type
 )
 {
-       LDAPURLDesc     *ludp;
-       char            *p, *s, *d;
-       int             i;
-       char            filter[1024];
+       int             rc, b, f, match;
        LDAPMessage     *e, *res;
-       int             rc;
-       char            **attrlist;
        struct timeval  timeout;
-       int             match;
-       int             resolved = 0;
        char            *dn;
+       char            filter[1024];
+       char            realfilter[1024];
+       char            **mail, **onvacation = NULL, **uid = NULL;
+
+       /*
+        * Look up the name in X.500, add the appropriate addresses found
+        * to the to list, or to the err list in case of error.  Groups are
+        * handled by the do_group routine, individuals are handled here.
+        * When looking up name, we follow the bases hierarchy, looking
+        * in base[0] first, then base[1], etc.  For each base, there is
+        * a set of search filters to try, in order.  If something goes
+        * wrong here trying to contact X.500, we exit with EX_TEMPFAIL.
+        * If the b_rdnpref flag is set, then we give preference to entries
+        * that matched name because it's their rdn, otherwise not.
+        */
 
        timeout.tv_sec = MAIL500_TIMEOUT;
        timeout.tv_usec = 0;
-
-       rc = ldap_url_parse( url, &ludp );
-       if ( rc ) {
-               switch ( rc ) {
-               case LDAP_URL_ERR_BADSCHEME:
-                       syslog( LOG_ALERT,
-                               "Not an LDAP URL: %s", url );
-                       break;
-               case LDAP_URL_ERR_BADENCLOSURE:
-                       syslog( LOG_ALERT,
-                               "Bad Enclosure in URL: %s", url );
-                       break;
-               case LDAP_URL_ERR_BADURL:
-                       syslog( LOG_ALERT,
-                               "Bad URL: %s", url );
-                       break;
-               case LDAP_URL_ERR_BADHOST:
-                       syslog( LOG_ALERT,
-                               "Host is invalid in URL: %s", url );
-                       break;
-               case LDAP_URL_ERR_BADATTRS:
-                       syslog( LOG_ALERT,
-                               "Attributes are invalid in URL: %s", url );
-                       break;
-               case LDAP_URL_ERR_BADSCOPE:
-                       syslog( LOG_ALERT,
-                               "Scope is invalid in URL: %s", url );
-                       break;
-               case LDAP_URL_ERR_BADFILTER:
-                       syslog( LOG_ALERT,
-                               "Filter is invalid in URL: %s", url );
-                       break;
-               case LDAP_URL_ERR_BADEXTS:
-                       syslog( LOG_ALERT,
-                               "Extensions are invalid in URL: %s", url );
-                       break;
-               case LDAP_URL_ERR_MEM:
-                       syslog( LOG_ALERT,
-                               "Out of memory parsing URL: %s", url );
-                       break;
-               case LDAP_URL_ERR_PARAM:
-                       syslog( LOG_ALERT,
-                               "bad parameter parsing URL: %s", url );
-                       break;
-               default:
-                       syslog( LOG_ALERT,
-                               "Unknown error %d parsing URL: %s",
-                               rc, url );
-                       break;
+       for ( b = 0, match = 0; !match && base[b].b_dn != NULL; b++ ) {
+               if ( ! (base[b].b_search & type) ) {
+                       continue;
                }
-               add_error( err, nerr, E_BADMEMBER,
-                          url, NULL );
-               return 0;
-       }
+               for ( f = 0; base[b].b_filter[f] != NULL; f++ ) {
+                       char    *format, *p, *s, *d;
+                       char    *argv[3];
+                       int     argc;
 
-       if ( substs ) {
-               for ( s = ludp->lud_filter, d = filter; *s; s++,d++ ) {
-                       if ( *s == '%' ) {
-                               s++;
-                               if ( *s == '%' ) {
-                                       *d = '%';
-                                       continue;
-                               }
-                               for ( i = 0; substs[i].sub_char != '\0';
-                                     i++ ) {
-                                       if ( *s == substs[i].sub_char ) {
-                                               for ( p = substs[i].sub_value;
-                                                     *p; p++,d++ ) {
-                                                       *d = *p;
-                                               }
-                                               d--;
+                       for ( argc = 0; argc < 3; argc++ ) {
+                               argv[argc] = NULL;
+                       }
+
+                       format = strdup( base[b].b_filter[f] );
+                       for ( argc = 0, p = format; *p; p++ ) {
+                               if ( *p == '%' ) {
+                                       switch ( *++p ) {
+                                       case 's':       /* %s is the name */
+                                               argv[argc] = name;
+                                               break;
+
+                                       case 'h':       /* %h is the host */
+                                               *p = 's';
+                                               argv[argc] = host;
+                                               break;
+
+                                       default:
+                                               syslog( LOG_ALERT,
+                                                   "unknown format %c", *p );
                                                break;
                                        }
+
+                                       argc++;
                                }
-                               if ( substs[i].sub_char == '\0' ) {
-                                       syslog( LOG_ALERT,
-                                               "unknown format %c", *s );
+                       }
+
+                       /* three names ought to do... */
+                       sprintf( filter, format, argv[0], argv[1], argv[2] );
+                       free( format );
+                       for ( s = filter, d = realfilter; *s; s++, d++ ) {
+                               if ( *s == '*' ) {
+                                       *d++ = '\\';
                                }
-                       } else {
                                *d = *s;
                        }
+                       *d = '\0';
+
+                       res = NULL;
+                       rc = ldap_search_st( ld, base[b].b_dn,
+                           LDAP_SCOPE_SUBTREE, realfilter, attrs, 0, &timeout,
+                           &res );
+
+                       /* some other trouble - try again later */
+                       if ( rc != LDAP_SUCCESS &&
+                           rc != LDAP_SIZELIMIT_EXCEEDED ) {
+                               syslog( LOG_ALERT, "return 0x%x from X.500",
+                                   rc );
+                               unbind_and_exit( EX_TEMPFAIL );
+                       }
+
+                       if ( (match = ldap_count_entries( ld, res )) != 0 )
+                               break;
+
+                       ldap_msgfree( res );
                }
-               *d = *s;
-       } else {
-               strncpy( filter, ludp->lud_filter, sizeof( filter ) - 1 );
-               filter[ sizeof( filter ) - 1 ] = '\0';
-       }
 
-       if ( ludp->lud_attrs ) {
-               attrlist = ludp->lud_attrs;
-       } else {
-               attrlist = def_attr;
-       }
-       res = NULL;
-       /* TBC: we don't read the host, dammit */
-       rc = ldap_search_st( ld, ludp->lud_dn, ludp->lud_scope,
-                            filter, attrlist, 0,
-                            &timeout, &res );
-
-       /* some other trouble - try again later */
-       if ( rc != LDAP_SUCCESS &&
-            rc != LDAP_SIZELIMIT_EXCEEDED ) {
-               syslog( LOG_ALERT, "return 0x%x from X.500",
-                       rc );
-               unbind_and_exit( EX_TEMPFAIL );
+               if ( match )
+                       break;
        }
 
-       match = ldap_count_entries( ld, res );
-
        /* trouble - try again later */
        if ( match == -1 ) {
                syslog( LOG_ALERT, "error parsing result from X.500" );
                unbind_and_exit( EX_TEMPFAIL );
        }
 
-       if ( match == 1 || multi_entry ) {
-               for ( e = ldap_first_entry( ld, res ); e != NULL;
-                     e = ldap_next_entry( ld, e ) ) {
-                       dn = ldap_get_dn( ld, e );
-                       resolved = entry_engine( e, dn, address, to, nto,
-                                                togroups, ngroups,
-                                                err, nerr, type );
-                       if ( !resolved ) {
-                               add_error( err, nerr, E_NOEMAIL, address, res );
-                       }
+       /* no matches - bounce with user unknown */
+       if ( match == 0 ) {
+               if ( type == USER ) {
+                       add_error( err, nerr, E_USERUNKNOWN, name, NULL );
+               } else {
+                       add_error( err, nerr, E_GROUPUNKNOWN, name, NULL );
                }
-               return ( resolved );
+               return;
        }
 
        /* more than one match - bounce with ambiguous user? */
@@ -963,9 +530,9 @@ search_ldap_url(
                char            **xdn;
 
                /* not giving rdn preference - bounce with ambiguous user */
-               if ( rdnpref == 0 ) {
-                       add_error( err, nerr, E_AMBIGUOUS, address, res );
-                       return 0;
+               if ( base[b].b_rdnpref == 0 ) {
+                       add_error( err, nerr, E_AMBIGUOUS, name, res );
+                       return;
                }
 
                /*
@@ -980,7 +547,7 @@ search_ldap_url(
                        xdn = ldap_explode_dn( dn, 1 );
 
                        /* XXX bad, but how else can we do it? XXX */
-                       if ( strcasecmp( xdn[0], address ) == 0 ) {
+                       if ( strcasecmp( xdn[0], name ) == 0 ) {
                                ldap_delete_result_entry( &res, e );
                                ldap_add_result_entry( &tmpres, e );
                        }
@@ -991,13 +558,13 @@ search_ldap_url(
 
                /* nothing matched by rdn - go ahead and bounce */
                if ( tmpres == NULL ) {
-                       add_error( err, nerr, E_AMBIGUOUS, address, res );
-                       return 0;
+                       add_error( err, nerr, E_AMBIGUOUS, name, res );
+                       return;
 
                /* more than one matched by rdn - bounce with rdn matches */
                } else if ( (match = ldap_count_entries( ld, tmpres )) > 1 ) {
-                       add_error( err, nerr, E_AMBIGUOUS, address, tmpres );
-                       return 0;
+                       add_error( err, nerr, E_AMBIGUOUS, name, tmpres );
+                       return;
 
                /* trouble... */
                } else if ( match < 0 ) {
@@ -1008,565 +575,484 @@ search_ldap_url(
                /* otherwise one matched by rdn - send to it */
                ldap_msgfree( res );
                res = tmpres;
+       }
 
-               /* trouble */
-               if ( (e = ldap_first_entry( ld, res )) == NULL ) {
-                       syslog( LOG_ALERT, "error parsing entry from X.500" );
-                       unbind_and_exit( EX_TEMPFAIL );
-               }
-
-               dn = ldap_get_dn( ld, e );
+       /*
+        * if we get this far, it means that we found a single match for
+        * name.  for a user, we deliver to the mail attribute or bounce
+        * with address and phone if no mail attr.  for a group, we
+        * deliver to all members or bounce to rfc822ErrorsTo if no members.
+        */
 
-               resolved = entry_engine( e, dn, address, to, nto,
-                                        togroups, ngroups,
-                                        err, nerr, type );
-               if ( !resolved ) {
-                       add_error( err, nerr, E_NOEMAIL, address, res );
-                       /* Don't free res if we passed it to add_error */
-               } else {
-                       ldap_msgfree( res );
-               }
+       /* trouble */
+       if ( (e = ldap_first_entry( ld, res )) == NULL ) {
+               syslog( LOG_ALERT, "error parsing entry from X.500" );
+               unbind_and_exit( EX_TEMPFAIL );
        }
-       return( resolved );
-}
 
-static int
-url_list_search(
-       char    **urllist, 
-       char    *address,
-       int     multi_entry,
-       char    ***to,
-       int     *nto,
-       Group   ***togroups,
-       int     *ngroups,
-       Error   **err,
-       int     *nerr,
-       int     type
-)
-{
-       int             i;
-       int             resolved = 0;
+       dn = ldap_get_dn( ld, e );
+
+       if ( type == GROUP_ERRORS ) {
+               /* sent to group-errors - resend to [rfc822]ErrorsTo attr */
+               do_group_errors( e, dn, to, nto, err, nerr );
+
+       } else if ( type == GROUP_REQUEST ) {
+               /* sent to group-request - resend to [rfc822]RequestsTo attr */
+               do_group_request( e, dn, to, nto, err, nerr );
 
-       for ( i = 0; urllist[i]; i++ ) {
+       } else if ( type == GROUP_MEMBERS ) {
+               /* sent to group-members - expand */
+               do_group_members( e, dn, to, nto, togroups, ngroups, err,
+                   nerr );
 
-               if ( !strncasecmp( urllist[i], "mail:", 5 ) ) {
-                       char    *vals[2];
+       } else if ( type == GROUP_OWNER ) {
+               /* sent to group-owner - resend to owner attr */
+               do_group_owner( e, dn, to, nto, err, nerr );
 
-                       vals[0] = urllist[i] + 5;
-                       vals[1] = NULL;
-                       add_to( to, nto, vals );
-                       resolved = 1;
+       } else if ( isgroup( e ) ) {
+               /* 
+                * sent to group - resend from [rfc822]ErrorsTo if it's there,
+                * otherwise, expand the group
+                */
 
-               } else if ( ldap_is_ldap_url( urllist[i] ) ) {
+               do_group( e, dn, to, nto, togroups, ngroups, err, nerr );
 
-                       resolved = search_ldap_url( urllist[i], NULL,
-                                                   address, 0, multi_entry,
-                                                   to, nto, togroups, ngroups,
-                                                   err, nerr, type );
+               ldap_msgfree( res );
+
+       } else {
+               /*
+                * sent to user - mail attribute => add it to the to list,
+                * otherwise bounce
+                */
+               if ( (mail = ldap_get_values( ld, e, "mail" )) != NULL ) {
+                       char    buf[1024];
+                       char    *h;
+                       int     i, j;
+
+                       /* try to detect simple mail loops */
+                       sprintf( buf, "%s@%s", name, host );
+                       for ( i = 0; mail[i] != NULL; i++ ) {
+                               /*
+                                * address is the same as the one we're
+                                * sending to - mail loop.  syslog the
+                                * problem, bounce a message back to the
+                                * sender (who else?), and delete the bogus
+                                * addr from the list.
+                                */
+
+                               if ( (h = strchr( mail[i], '@' )) != NULL ) {
+                                       h++;
+                                       if ( strcasecmp( h, host ) == 0 ) {
+                                               syslog( LOG_ALERT,
+                                           "potential loop detected (%s)",
+                                                   mail[i] );
+                                       }
+                               }
+
+                               if ( mailcmp( buf, mail[i] ) == 0 ) {
+                                       syslog( LOG_ALERT,
+                                           "loop detected (%s)", mail[i] );
+
+                                       /* remove the bogus address */
+                                       for ( j = i; mail[j] != NULL; j++ ) {
+                                               mail[j] = mail[j+1];
+                                       }
+                               }
+                       }
+                       if ( mail[0] != NULL ) {
+                               add_to( to, nto, mail );
+                       } else {
+                               add_error( err, nerr, E_NOEMAIL, name, res );
+                       }
+
+                       ldap_value_free( mail );
                } else {
-                       /* Produce some sensible error here */
-                       resolved = 0;
+                       add_error( err, nerr, E_NOEMAIL, name, res );
                }
-       }
-       return( resolved );
-}
 
-/*
- * We should probably take MX records into account to cover all bases,
- * but really, routing belongs in the MTA.
- */
-static int
-is_my_host(
-       char * host
-)
-{
-       char **d;
+               /*
+                * If the user is on vacation, send a copy of the mail to
+                * the vacation server.  The address is constructed from
+                * the vacationhost (set in a command line argument) and
+                * the uid (XXX this should be more general XXX).
+                */
+
+               if ( vacationhost != NULL && (onvacation = ldap_get_values( ld,
+                   e, "onVacation" )) != NULL && strcasecmp( onvacation[0],
+                   "TRUE" ) == 0 ) {
+                       char    buf[1024];
+                       char    *vaddr[2];
 
-       if ( myhosts == NULL )
-               return 0;
-       for ( d = myhosts; *d; d++ ) {
-               if ( !strcasecmp(*d,host) ) {
-                       return 1;
+                       if ( (uid = ldap_get_values( ld, e, "uid" )) != NULL ) {
+                               sprintf( buf, "%s@%s", uid[0], vacationhost );
+
+                               vaddr[0] = buf;
+                               vaddr[1] = NULL;
+
+                               add_to( to, nto, vaddr );
+                       } else {
+                               syslog( LOG_ALERT,
+                                   "user without a uid on vacation (%s)",
+                                   name );
+                       }
                }
        }
-       return 0;
-}
 
-static int
-is_my_domain(
-       char * address
-)
-{
-       char **d;
-       char *p;
-
-       if ( mydomains == NULL )
-               return 0;
-       p = strchr( address, '@' );
-       if ( p == NULL)
-               return 0;
-       for ( d = mydomains; *d; d++ ) {
-               if ( !strcasecmp(*d,p+1) ) {
-                       return 1;
-               }
+       if ( onvacation != NULL ) {
+               ldap_value_free( onvacation );
        }
-       return 0;
+       if ( uid != NULL ) {
+               ldap_value_free( uid );
+       }
+       free( dn );
 }
 
-static void
-do_addresses(
-       char    **addresses,
+static int
+do_group(
+       LDAPMessage *e,
+       char    *dn,
        char    ***to,
        int     *nto,
-       Group   ***togroups,
+       Group   **togroups,
        int     *ngroups,
        Error   **err,
-       int     *nerr,
-       int     type
+       int     *nerr
 )
 {
-       int     i, j;
-       int     n;
+       int     i;
+       char    **moderator;
 
        /*
-        * Well, this is tricky, every address in my_addresses will be
-        * removed from the list while we shift the other values down
-        * and we do it in a single scan of the address list and
-        * without using additional memory.  We are going to be
-        * modifying the value list in a way that the later
-        * ldap_value_free works.
+        * If this group has an rfc822ErrorsTo attribute, we need to
+        * arrange for errors involving this group to go there, not
+        * to the sender.  Since sendmail only has the concept of a
+        * single sender, we arrange for errors to go to groupname-errors,
+        * which we then handle specially when (if) it comes back to us
+        * by expanding to all the rfc822ErrorsTo addresses.  If it has no
+        * rfc822ErrorsTo attribute, we call do_group_members() to expand
+        * the group.
         */
-       j = 0;
-       for ( i = 0; addresses[i]; i++ ) {
-               if ( is_my_domain(addresses[i]) ) {
-                       do_address( addresses[i], to, nto, togroups, ngroups,
-                                   err, nerr, type );
-                       ldap_memfree( addresses[i] );
-               } else {
-                       if ( j < i ) {
-                               addresses[j] = addresses[i];
-                       }
-                       j++;
+
+       if ( group_loop( dn ) ) {
+               return( -1 );
+       }
+
+       /*
+        * check for moderated groups - if the group has a moderator
+        * attribute, we check to see if the from address is one of
+        * the moderator values.  if so, continue on.  if not, arrange
+        * to send the mail to the moderator(s).  need to do this before
+        * we change the from below.
+        */
+
+       if ( (moderator = ldap_get_values( ld, e, "moderator" )) != NULL ) {
+               /* check if it came from any of the group's moderators */
+               for ( i = 0; moderator[i] != NULL; i++ ) {
+                       if ( strcasecmp( moderator[i], mailfrom ) == 0 )
+                               break;
+               }
+
+               /* not from the moderator? */
+               if ( moderator[i] == NULL ) {
+                       add_to( to, nto, moderator );
+                       ldap_value_free( moderator );
+
+                       return( 0 );
                }
+               /* else from the moderator - fall through and deliver it */
        }
-       addresses[j] = NULL;
-       if ( addresses[0] ) {
-               add_to( to, nto, addresses );
+
+       if (strcmp(MAIL500_BOUNCEFROM, mailfrom) != 0 &&
+           has_attributes( e, "rfc822ErrorsTo", "errorsTo" ) ) {
+               add_group( dn, togroups, ngroups );
+
+               return( 0 );
        }
-}
 
-/*
- * The entry engine processes an entry.  Normally, each entry will resolve
- * to one or more values that will be added to the 'to' argument.  This
- * argument needs not be the global 'to' list, it may be the g_to field
- * in a group.  Groups have no special treatment, unless they require
- * a special sender.
- */
+       do_group_members( e, dn, to, nto, togroups, ngroups, err, nerr );
 
-static int
-entry_engine(
+       return( 0 );
+}
+
+/* ARGSUSED */
+static void
+do_group_members(
        LDAPMessage *e,
        char    *dn,
-       char    *address,
        char    ***to,
        int     *nto,
-       Group   ***togroups,
+       Group   **togroups,
        int     *ngroups,
        Error   **err,
-       int     *nerr,
-       int     type
+       int     *nerr
 )
 {
-       char    **vals;
-       int     i;
-       int     resolved = 0;
-       char    ***current_to = to;
-       int     *current_nto = nto;
-       Group   *current_group = NULL;
-       char    buf[1024];
-       char    *localpart = NULL, *domainpart = NULL;
-       Subst   substs[2];
-       int     cur_priority = 0;
-       char    *route_to_host = NULL;
-       char    *route_to_address = NULL;
-       int     needs_mta_routing = 0;
-       char    **own_addresses = NULL;
-       int     own_addresses_total = 0;
-       char    **delivery_types = NULL;
-       int     delivery_types_total = 0;
-       char    *nvals[2];
-
-       for ( i=0; attr_semantics[i] != NULL; i++ ) {
-               AttrSemantics   *as = attr_semantics[i];
-               int             nent;
-               int             j;
-
-               if ( as->as_priority < cur_priority ) {
-                       /*
-                        * We already got higher priority information,
-                        * so no further work to do, ignore the rest.
-                        */
-                       break;
+       int             i, rc, anymembers;
+       char            *ndn;
+       char            **mail, **member, **joinable, **suppress;
+       char            filter[1024];
+       LDAPMessage     *ee, *res;
+       struct timeval  timeout;
+       int             opt;
+
+       /*
+        * if all has gone according to plan, we've already arranged for
+        * errors to go to the [rfc822]ErrorsTo attributes (if they exist),
+        * so all we have to do here is arrange to send to the
+        * rfc822Mailbox attribute, the member attribute, and anyone who
+        * has joined the group by setting memberOfGroup equal to the
+        * group dn.
+        */
+
+       /* add members in the group itself - mail attribute */
+       anymembers = 0;
+       if ( (mail = ldap_get_values( ld, e, "mail" )) != NULL ) {
+               anymembers = 1;
+               add_to( to, nto, mail );
+
+               ldap_value_free( mail );
+       }
+
+       /* add members in the group itself - member attribute */
+       if ( (member = ldap_get_values( ld, e, "member" )) != NULL ) {
+               suppress = ldap_get_values( ld, e, "suppressNoEmailError" );
+               anymembers = 1;
+               for ( i = 0; member[i] != NULL; i++ ) {
+                       if ( strcasecmp( dn, member[i] ) == 0 ) {
+                               syslog( LOG_ALERT, "group (%s) contains itself",
+                                   dn );
+                               continue;
+                       }
+                       add_member( dn, member[i], to, nto, togroups,
+                           ngroups, err, nerr, suppress );
                }
-               vals = ldap_get_values( ld, e, as->as_name );
-               if ( !vals || vals[0] == NULL ) {
-                       continue;
+
+               if ( suppress ) {
+                       ldap_value_free( suppress );
                }
-               nent = count_values( vals );
-               if ( nent > 1 && !as->as_m_valued ) {
-                       add_error( err, nerr, E_AMBIGUOUS, address, e );
-                       return( 0 );
+               ldap_value_free( member );
+       }
+
+       /* add members who have joined by setting memberOfGroup */
+       if ( (joinable = ldap_get_values( ld, e, "joinable" )) != NULL ) {
+               if ( strcasecmp( joinable[0], "FALSE" ) == 0 ) {
+                       if ( ! anymembers ) {
+                               add_error( err, nerr, E_NOMEMBERS, dn,
+                                   NULL );
+                       }
+
+                       ldap_value_free( joinable );
+                       return;
                }
-               switch ( as->as_kind ) {
-               case AS_KIND_RECIPIENT:
-                       cur_priority = as->as_priority;
-                       if ( ! ( type & ( USER | GROUP_MEMBERS ) ) )
-                               break;
-                       switch ( as->as_syntax ) {
-                       case AS_SYNTAX_RFC822:
-                               do_addresses( vals, current_to, current_nto,
-                                             togroups, ngroups, err, nerr,
-                                             USER );
-                               resolved = 1;
-                               break;
-                       case AS_SYNTAX_RFC822_EXT:
-                               do_addresses( vals, current_to, current_nto,
-                                             togroups, ngroups, err, nerr,
-                                             USER );
-                               resolved = 1;
-                               break;
-                       case AS_SYNTAX_NATIVE_MB:
-                               /* We used to concatenate mailHost if set here */
-                               /*
-                                * We used to send a copy to the vacation host
-                                * if onVacation to uid@vacationhost
-                                */
-                               if ( as->as_param ) {
-                                       for ( j=0; j<delivery_types_total; j++ ) {
-                                               if ( !strcasecmp( as->as_param, delivery_types[j] ) ) {
-                                                       add_to( current_to, current_nto, vals );
-                                                       resolved = 1;
-                                                       break;
-                                               }
-                                       }
-                               } else {
-                                       add_to( current_to, current_nto, vals );
-                                       resolved = 1;
-                               }
-                               break;
+               ldap_value_free( joinable );
 
-                       case AS_SYNTAX_DN:
-                               if ( dn_search( vals, address,
-                                               current_to, current_nto,
-                                               togroups, ngroups,
-                                               err, nerr ) ) {
-                                       resolved = 1;
-                               }
-                               break;
+               sprintf( filter, "(memberOfGroup=%s)", dn );
 
-                       case AS_SYNTAX_URL:
-                               if ( url_list_search( vals, address,
-                                                as->as_m_entries,
-                                                current_to, current_nto,
-                                                togroups, ngroups,
-                                                err, nerr, type ) ) {
-                                       resolved = 1;
-                               }
-                               break;
+               timeout.tv_sec = MAIL500_TIMEOUT;
+               timeout.tv_usec = 0;
 
-                       case AS_SYNTAX_BOOL_FILTER:
-                               if ( strcasecmp( vals[0], "true" ) ) {
-                                       break;
-                               }
-                               substs[0].sub_char = 'D';
-                               substs[0].sub_value = dn;
-                               substs[1].sub_char = '\0';
-                               substs[1].sub_value = NULL;
-                               if ( url_list_search( vals, address,
-                                                as->as_m_entries,
-                                                current_to, current_nto,
-                                                togroups, ngroups,
-                                                err, nerr, type ) ) {
-                                       resolved = 1;
-                               }
-                               break;
+               /* for each subtree to look in... */
+               opt = MAIL500_MAXAMBIGUOUS;
+               ldap_set_option(ld, LDAP_OPT_SIZELIMIT, &opt);
+               for ( i = 0; base[i].b_dn != NULL; i++ ) {
+                       /* find entries that have joined this group... */
+                       rc = ldap_search_st( ld, base[i].b_dn,
+                           LDAP_SCOPE_SUBTREE, filter, attrs, 0, &timeout,
+                           &res );
 
-                       default:
+                       if ( rc == LDAP_SIZELIMIT_EXCEEDED ||
+                           rc == LDAP_TIMELIMIT_EXCEEDED ) {
                                syslog( LOG_ALERT,
-                                       "Invalid syntax %d for kind %d",
-                                       as->as_syntax, as->as_kind );
-                               break;
+                                   "group search limit exceeded %d", rc );
+                               unbind_and_exit( EX_TEMPFAIL );
                        }
-                       break;
 
-               case AS_KIND_ERRORS:
-                       cur_priority = as->as_priority;
-                       /* This is a group with special processing */
-                       if ( type & GROUP_ERRORS ) {
-                               switch (as->as_kind) {
-                               case AS_SYNTAX_RFC822:
-                                       add_to( current_to, current_nto, vals );
-                                       resolved = 1;
-                                       break;
-                               case AS_SYNTAX_URL:
-                               default:
-                                       syslog( LOG_ALERT,
-                                               "Invalid syntax %d for kind %d",
-                                               as->as_syntax, as->as_kind );
-                               }
-                       } else {
-                               current_group = new_group( dn, togroups,
-                                                          ngroups );
-                               current_to = &current_group->g_members;
-                               current_nto = &current_group->g_nmembers;
-                               split_address( address,
-                                              &localpart, &domainpart );
-                               if ( domainpart ) {
-                                       sprintf( buf, "%s-%s@%s",
-                                                localpart, ERRORS,
-                                                domainpart );
-                                       free( localpart );
-                                       free( domainpart );
-                               } else {
-                                       sprintf( buf, "%s-%s@%s",
-                                                localpart, ERRORS,
-                                                host );
-                                       free( localpart );
-                               }
-                               current_group->g_errorsto = strdup( buf );
+                       if ( rc != LDAP_SUCCESS ) {
+                               syslog( LOG_ALERT, "group search return 0x%x",
+                                   rc );
+                               unbind_and_exit( EX_TEMPFAIL );
                        }
-                       break;
 
-               case AS_KIND_REQUEST:
-                       cur_priority = as->as_priority;
-                       /* This is a group with special processing */
-                       if ( type & GROUP_REQUEST ) {
-                               add_to( current_to, current_nto, vals );
-                               resolved = 1;
-                       }
-                       break;
+                       /* for each entry that has joined... */
+                       for ( ee = ldap_first_entry( ld, res ); ee != NULL;
+                           ee = ldap_next_entry( ld, ee ) ) {
+                               anymembers = 1;
+                               if ( isgroup( ee ) ) {
+                                       ndn = ldap_get_dn( ld, ee );
+
+                                       if ( do_group( e, ndn, to, nto,
+                                           togroups, ngroups, err, nerr )
+                                           == -1 ) {
+                                               syslog( LOG_ALERT,
+                                                   "group loop (%s) (%s)",
+                                                   dn, ndn );
+                                       }
 
-               case AS_KIND_OWNER:
-                       cur_priority = as->as_priority;
-                       /* This is a group with special processing */
-                       if ( type & GROUP_REQUEST ) {
-                               add_to( current_to, current_nto, vals );
-                               resolved = 1;
-                       }
-                       break;
+                                       free( ndn );
 
-               case AS_KIND_ROUTE_TO_HOST:
-                       if ( !is_my_host( vals[0] ) ) {
-                               cur_priority = as->as_priority;
-                               if ( as->as_syntax == AS_SYNTAX_PRESENT ) {
-                                       needs_mta_routing = 1;
-                               } else {
-                                       route_to_host = strdup( vals[0] );
+                                       continue;
                                }
-                       }
-                       break;
 
-               case AS_KIND_ROUTE_TO_ADDR:
-                       for ( j=0; j<own_addresses_total; j++ ) {
-                               if ( strcasecmp( vals[0], own_addresses[j] ) ) {
-                                       cur_priority = as->as_priority;
-                                       if ( as->as_syntax == AS_SYNTAX_PRESENT ) {
-                                               needs_mta_routing = 1;
-                                       } else {
-                                               route_to_address = strdup( vals[0] );
-                                       }
-                               }
-                               break;
-                       }
+                               /* add them to the to list */
+                               if ( (mail = ldap_get_values( ld, ee, "mail" ))
+                                   != NULL ) {
+                                       add_to( to, nto, mail );
 
-               case AS_KIND_OWN_ADDR:
-                       add_to( &own_addresses, &own_addresses_total, vals );
-                       cur_priority = as->as_priority;
-                       break;
+                                       ldap_value_free( mail );
 
-               case AS_KIND_DELIVERY_TYPE:
-                       add_to( &delivery_types, &delivery_types_total, vals );
-                       cur_priority = as->as_priority;
-                       break;
+                               /* else generate a bounce */
+                               } else {
+                                       ndn = ldap_get_dn( ld, ee );
 
-               default:
-                       syslog( LOG_ALERT,
-                               "Invalid kind %d", as->as_kind );
-                       /* Error, TBC */
-               }
-               ldap_value_free( vals );
-       }
-       /*
-        * Now check if we are dealing with mail routing.  We support
-        * two modes.
-        *
-        * The first mode and by far the most robust method is doing
-        * routing at the MTA.  In this case, we just checked if the
-        * routing attributes were present and did not seem like
-        * pointing to ourselves.  The only thing we have to do here
-        * is adding to the recipient list any of the RFC822 addresses
-        * of this entry.  That means we needed to retrieve them from
-        * the entry itself because we might have arrived here through
-        * some directory search.  The address received as argument is
-        * not the address of the entry we are processing, but rather
-        * the RFC822 address we are expanding now.  Unfortunately,
-        * this requires an MTA that understands LDAP routing.
-        * Sendmail 8.10.0 does, if compiled properly.
-        *
-        * The second method, that is most emphatically not recommended
-        * is routing in mail500.  This is going to require using the
-        * percent hack.  Moreover, this may occasionally loop.
-        */
-       if ( needs_mta_routing ) {
-               if ( !own_addresses ) {
-                       add_error( err, nerr, E_NOOWNADDRESS, address, e );
-                       return( 0 );
-               }
-               nvals[0] = own_addresses[0];    /* Anyone will do */
-               nvals[1] = NULL;
-               add_to( current_to, current_nto, nvals );
-               resolved = 1;
-       } else if ( route_to_host ) {
-               char *p;
-               if ( !route_to_address ) {
-                       if ( !own_addresses ) {
-                               add_error( err, nerr, E_NOOWNADDRESS, address, e );
-                               return( 0 );
+                                       add_error( err, nerr,
+                                           E_JOINMEMBERNOEMAIL, ndn, NULL );
+
+                                       free( ndn );
+                               }
                        }
-                       route_to_address = strdup( own_addresses[0] );
-               }
-               /* This makes use of the percent hack, but there's no choice */
-               p = strchr( route_to_address, '@' );
-               if ( p ) {
-                       *p = '%';
+
+                       ldap_msgfree( res );
                }
-               sprintf( buf, "%s@%s", route_to_address, route_to_host );
-               nvals[0] = buf;
-               nvals[1] = NULL;
-               add_to( current_to, current_nto, nvals );
-               resolved = 1;
-               free( route_to_host );
-               free( route_to_address );
-       } else if ( route_to_address ) {
-               nvals[0] = route_to_address;
-               nvals[1] = NULL;
-               add_to( current_to, current_nto, nvals );
-               resolved = 1;
-               free( route_to_address );
-       }
-       if ( own_addresses ) {
-               ldap_value_free( own_addresses );
+               opt = MAIL500_MAXAMBIGUOUS;
+               ldap_set_option(ld, LDAP_OPT_SIZELIMIT, &opt);
        }
-       if ( delivery_types ) {
-               ldap_value_free( delivery_types );
+
+       if ( ! anymembers ) {
+               add_error( err, nerr, E_NOMEMBERS, dn, NULL );
        }
-                 
-       return( resolved );
 }
 
-static int
-search_bases(
-       char    *filter,
-       Subst   *substs,
-       char    *name,
+static void
+add_member(
+       char    *gdn,
+       char    *dn,
        char    ***to,
        int     *nto,
-       Group   ***togroups,
+       Group   **togroups,
        int     *ngroups,
        Error   **err,
        int     *nerr,
-       int     type
+       char    **suppress
 )
 {
-       int             b, resolved = 0;
+       char            *ndn;
+       char            **mail;
+       int             rc;
+       LDAPMessage     *res, *e;
+       struct timeval  timeout;
 
-       for ( b = 0; base[b] != NULL; b++ ) {
+       timeout.tv_sec = MAIL500_TIMEOUT;
+       timeout.tv_usec = 0;
+       if ( (rc = ldap_search_st( ld, dn, LDAP_SCOPE_BASE, "(objectclass=*)",
+           attrs, 0, &timeout, &res )) != LDAP_SUCCESS ) {
+               if ( rc == LDAP_NO_SUCH_OBJECT ) {
+                       add_error( err, nerr, E_BADMEMBER, dn, NULL );
 
-               if ( ! (base[b]->b_search & type) ) {
-                       continue;
+                       return;
+               } else {
+                       syslog( LOG_ALERT, "member search return 0x%x", rc );
+
+                       unbind_and_exit( EX_TEMPFAIL );
+               }
+       }
+
+       if ( (e = ldap_first_entry( ld, res )) == NULL ) {
+               syslog( LOG_ALERT, "member search error parsing entry" );
+
+               unbind_and_exit( EX_TEMPFAIL );
+       }
+       ndn = ldap_get_dn( ld, e );
+
+       /* allow groups within groups */
+       if ( isgroup( e ) ) {
+               if ( do_group( e, ndn, to, nto, togroups, ngroups, err, nerr )
+                   == -1 ) {
+                       syslog( LOG_ALERT, "group loop (%s) (%s)", gdn, ndn );
                }
 
-               resolved = search_ldap_url( base[b]->b_url, substs, name,
-                                           base[b]->b_rdnpref,
-                                           base[b]->b_m_entries,
-                                           to, nto, togroups, ngroups,
-                                           err, nerr, type );
-               if ( resolved )
-                       break;
+               free( ndn );
+
+               return;
        }
-       return( resolved );
+
+       /* send to the member's mail attribute */
+       if ( (mail = ldap_get_values( ld, e, "mail" )) != NULL ) {
+               add_to( to, nto, mail );
+
+               ldap_value_free( mail );
+
+       /* else generate a bounce */
+       } else {
+               if ( suppress == NULL || strcasecmp( suppress[0], "FALSE" )
+                   == 0 ) {
+                       add_error( err, nerr, E_MEMBERNOEMAIL, ndn, NULL );
+               }
+       }
+
+       free( ndn );
 }
 
 static void
-do_address(
-       char    *name,
+do_group_request(
+       LDAPMessage *e,
+       char    *dn,
        char    ***to,
        int     *nto,
-       Group   ***togroups,
-       int     *ngroups,
        Error   **err,
-       int     *nerr,
-       int     type
+       int     *nerr
 )
 {
-       char            *localpart = NULL, *domainpart = NULL;
-       char            *synthname = NULL;
-       int             resolved;
-       int             i;
-       Subst           substs[6];
+       char            **requeststo;
 
-       /*
-        * Look up the name in X.500, add the appropriate addresses found
-        * to the to list, or to the err list in case of error.  Groups are
-        * handled by the do_group routine, individuals are handled here.
-        * When looking up name, we follow the bases hierarchy, looking
-        * in base[0] first, then base[1], etc.  For each base, there is
-        * a set of search filters to try, in order.  If something goes
-        * wrong here trying to contact X.500, we exit with EX_TEMPFAIL.
-        * If the b_rdnpref flag is set, then we give preference to entries
-        * that matched name because it's their rdn, otherwise not.
-        */
+       if ( (requeststo = get_attributes_mail_dn( e, "rfc822RequestsTo",
+           "requestsTo" )) != NULL ) {
+               add_to( to, nto, requeststo );
 
-       split_address( name, &localpart, &domainpart );
-       synthname = strdup( localpart );
-       for ( i = 0; synthname[i] != '\0'; i++ ) {
-               if ( synthname[i] == '.' || synthname[i] == '_' )
-                       synthname[i] = ' ';
-       }
-       substs[0].sub_char = 'm';
-       substs[0].sub_value = name;
-       substs[1].sub_char = 'h';
-       substs[1].sub_value = host;
-       substs[2].sub_char = 'l';
-       substs[2].sub_value = localpart;
-       substs[3].sub_char = 'd';
-       substs[3].sub_value = domainpart;
-       substs[4].sub_char = 's';
-       substs[4].sub_value = synthname;
-       substs[5].sub_char = '\0';
-       substs[5].sub_value = NULL;
-
-       resolved = search_bases( NULL, substs, name,
-                                to, nto, togroups, ngroups,
-                                err, nerr, type );
-
-       if ( localpart ) {
-               free( localpart );
-       }
-       if ( domainpart ) {
-               free( domainpart );
+               ldap_value_free( requeststo );
+       } else {
+               add_error( err, nerr, E_NOREQUEST, dn, NULL );
        }
-       if ( synthname ) {
-               free( synthname );
+}
+
+static void
+do_group_errors(
+       LDAPMessage *e,
+       char    *dn,
+       char    ***to,
+       int     *nto,
+       Error   **err,
+       int     *nerr
+)
+{
+       char            **errorsto;
+
+       if ( (errorsto = get_attributes_mail_dn( e, "rfc822ErrorsTo",
+           "errorsTo" )) != NULL ) {
+               add_to( to, nto, errorsto );
+
+               ldap_value_free( errorsto );
+       } else {
+               add_error( err, nerr, E_NOERRORS, dn, NULL );
        }
+}
 
-       if ( !resolved ) {
-               /* not resolved - bounce with user unknown */
-               if ( type == USER ) {
-                       add_error( err, nerr, E_USERUNKNOWN, name, NULL );
-               } else {
-                       add_error( err, nerr, E_GROUPUNKNOWN, name, NULL );
-               }
+static void
+do_group_owner(
+       LDAPMessage *e,
+       char    *dn,
+       char    ***to,
+       int     *nto,
+       Error   **err,
+       int     *nerr
+)
+{
+       char            **owner;
+
+       if ( (owner = get_attributes_mail_dn( e, "", "owner" )) != NULL ) {
+               add_to( to, nto, owner );
+               ldap_value_free( owner );
+       } else {
+               add_error( err, nerr, E_NOOWNER, dn, NULL );
        }
 }
 
@@ -1610,7 +1096,7 @@ send_message( char **to )
 }
 
 static void
-send_group( Group **group, int ngroup )
+send_group( Group *group, int ngroup )
 {
        int     i, pid;
        char    **argv;
@@ -1625,7 +1111,7 @@ send_group( Group **group, int ngroup )
 
                iargv[0] = MAIL500_SENDMAIL;
                iargv[1] = "-f";
-               iargv[2] = group[i]->g_errorsto;
+               iargv[2] = group[i].g_errorsto;
                iargv[3] = "-oMrX.500";
                iargv[4] = "-odi";
                iargv[5] = "-oi";
@@ -1634,7 +1120,7 @@ send_group( Group **group, int ngroup )
                argv = NULL;
                argc = 0;
                add_to( &argv, &argc, iargv );
-               add_to( &argv, &argc, group[i]->g_members );
+               add_to( &argv, &argc, group[i].g_members );
 
                if ( debug ) {
                        char    buf[1024];
@@ -1784,11 +1270,6 @@ send_errors( Error *err, int nerr )
                                    err[i].e_addr );
                                break;
 
-                       case E_NOOWNADDRESS:
-                               fprintf( fp, "%s: Not enough information to perform required routing\n",
-                                   err[i].e_addr );
-                               break;
-
                        default:
                                syslog( LOG_ALERT, "unknown error %d", err[i].e_code );
                                unbind_and_exit( EX_TEMPFAIL );
@@ -1937,14 +1418,11 @@ do_ambiguous( FILE *fp, Error *err, int namelen )
                        }
                }
 
-               /* 
                if ( isgroup( e ) ) {
                        vals = ldap_get_values( ld, e, "description" );
                } else {
                        vals = ldap_get_values( ld, e, "title" );
                }
-               */
-               vals = ldap_get_values( ld, e, "description" );
 
                fprintf( fp, "    %-20s %s\n", rdn, vals ? vals[0] : "" );
                for ( i = 1; vals && vals[i] != NULL; i++ ) {
@@ -1992,42 +1470,18 @@ add_to( char ***list, int *nlist, char **new )
        (*list)[*nlist] = NULL;
 }
 
-static void
-add_single_to( char ***list, char *new )
-{
-       int     nlist;
-
-       if ( *list == NULL ) {
-               nlist = 0;
-               *list = (char **) malloc( 2 * sizeof(char *) );
-       } else {
-               nlist = count_values( *list );
-               *list = (char **) realloc( *list,
-                                          ( nlist + 2 ) * sizeof(char *) );
-       }
-
-       (*list)[nlist] = strdup( new );
-       (*list)[nlist+1] = NULL;
-}
-
 static int
 isgroup( LDAPMessage *e )
 {
-       int     i, j;
+       int     i;
        char    **oclist;
 
-       if ( !groupclasses ) {
-               return( 0 );
-       }
-
        oclist = ldap_get_values( ld, e, "objectClass" );
 
        for ( i = 0; oclist[i] != NULL; i++ ) {
-               for ( j = 0; groupclasses[j] != NULL; j++ ) {
-                       if ( strcasecmp( oclist[i], groupclasses[j] ) == 0 ) {
-                               ldap_value_free( oclist );
-                               return( 1 );
-                       }
+               if ( strcasecmp( oclist[i], "rfc822MailGroup" ) == 0 ) {
+                       ldap_value_free( oclist );
+                       return( 1 );
                }
        }
        ldap_value_free( oclist );
@@ -2050,6 +1504,52 @@ add_error( Error **err, int *nerr, int code, char *addr, LDAPMessage *msg )
        (*nerr)++;
 }
 
+static void
+add_group( char *dn, Group **list, int *nlist )
+{
+       int     i, namelen;
+       char    **ufn;
+
+       for ( i = 0; i < *nlist; i++ ) {
+               if ( strcmp( dn, (*list)[i].g_dn ) == 0 ) {
+                       syslog( LOG_ALERT, "group loop 2 detected (%s)", dn );
+                       return;
+               }
+       }
+
+       ufn = ldap_explode_dn( dn, 1 );
+       namelen = strlen( ufn[0] );
+
+       if ( *nlist == 0 ) {
+               *list = (Group *) malloc( sizeof(Group) );
+       } else {
+               *list = (Group *) realloc( *list, (*nlist + 1) *
+                   sizeof(Group) );
+       }
+
+       /* send errors to groupname-errors@host */
+       (*list)[*nlist].g_errorsto = (char *) malloc( namelen + sizeof(ERRORS)
+           + hostlen + 2 );
+       sprintf( (*list)[*nlist].g_errorsto, "%s-%s@%s", ufn[0], ERRORS, host );
+       (void) canonical( (*list)[*nlist].g_errorsto );
+
+       /* send to groupname-members@host - make it a list for send_group */
+       (*list)[*nlist].g_members = (char **) malloc( 2 * sizeof(char *) );
+       (*list)[*nlist].g_members[0] = (char *) malloc( namelen +
+           sizeof(MEMBERS) + hostlen + 2 );
+       sprintf( (*list)[*nlist].g_members[0], "%s-%s@%s", ufn[0], MEMBERS,
+           host );
+       (void) canonical( (*list)[*nlist].g_members[0] );
+       (*list)[*nlist].g_members[1] = NULL;
+
+       /* save the group's dn so we can check for loops above */
+       (*list)[*nlist].g_dn = strdup( dn );
+
+       (*nlist)++;
+
+       ldap_value_free( ufn );
+}
+
 static void
 unbind_and_exit( int rc )
 {
@@ -2060,3 +1560,129 @@ unbind_and_exit( int rc )
 
        exit( rc );
 }
+
+static char *
+canonical( char *s )
+{
+       char    *saves = s;
+
+       for ( ; *s != '\0'; s++ ) {
+               if ( *s == ' ' )
+                       *s = '.';
+       }
+
+       return( saves );
+}
+
+static int
+group_loop( char *dn )
+{
+       int             i;
+       static char     **groups;
+       static int      ngroups;
+
+       for ( i = 0; i < ngroups; i++ ) {
+               if ( strcmp( dn, groups[i] ) == 0 )
+                       return( 1 );
+       }
+
+       if ( ngroups == 0 )
+               groups = (char **) malloc( sizeof(char *) );
+       else
+               groups = (char **) realloc( groups,
+                   (ngroups + 1) * sizeof(char *) );
+
+       groups[ngroups++] = strdup( dn );
+
+       return( 0 );
+}
+
+static int
+has_attributes( LDAPMessage *e, char *attr1, char *attr2 )
+{
+       char    **attr;
+
+       if ( (attr = ldap_get_values( ld, e, attr1 )) != NULL ) {
+               ldap_value_free( attr );
+               return( 1 );
+       }
+
+       if ( (attr = ldap_get_values( ld, e, attr2 )) != NULL ) {
+               ldap_value_free( attr );
+               return( 1 );
+       }
+
+       return( 0 );
+}
+
+static char **
+get_attributes_mail_dn(
+    LDAPMessage *e,
+    char *attr1,
+    char *attr2                        /* this one is dn-valued */
+)
+{
+       LDAPMessage     *ee, *res;
+       char            **vals, **dnlist, **mail, **grname;
+        char            *dn;
+       int             nto = 0, i, rc;
+       struct timeval  timeout;
+
+       dn = ldap_get_dn( ld, e );
+
+       vals = ldap_get_values( ld, e, attr1 );
+       for ( nto = 0; vals != NULL && vals[nto] != NULL; nto++ )
+               ;       /* NULL */
+
+       if ( (dnlist = ldap_get_values( ld, e, attr2 )) != NULL ) {
+               timeout.tv_sec = MAIL500_TIMEOUT;
+               timeout.tv_usec = 0;
+
+               for ( i = 0; dnlist[i] != NULL; i++ ) {
+                       if ( (rc = ldap_search_st( ld, dnlist[i],
+                           LDAP_SCOPE_BASE, "(objectclass=*)", attrs, 0,
+                           &timeout, &res )) != LDAP_SUCCESS ) {
+                               if ( rc != LDAP_NO_SUCH_OBJECT ) {
+                                       unbind_and_exit( EX_TEMPFAIL );
+                               }
+
+                               syslog( LOG_ALERT, "bad (%s) dn (%s)", attr2,
+                                   dnlist[i] );
+
+                               continue;
+                       }
+
+                       if ( (ee = ldap_first_entry( ld, res )) == NULL ) {
+                               syslog( LOG_ALERT, "error parsing x500 entry" );
+                               continue;
+                       }
+
+                       if ( isgroup(ee) ) {
+                               char    *graddr[2];
+
+                               grname = ldap_explode_dn( dnlist[i], 1 );
+
+                               /* groupname + host + @ + null */
+                               graddr[0] = (char *) malloc( strlen( grname[0] )
+                                   + strlen( host ) + 2 );
+                               graddr[1] = NULL;
+                               sprintf( graddr[0], "%s@%s", grname[0], host);
+                               (void) canonical( graddr[0] );
+
+                               add_to( &vals, &nto, graddr );
+
+                               free( graddr[0] );
+                               ldap_value_free( grname );
+                       } else if ( (mail = ldap_get_values( ld, ee, "mail" ))
+                           != NULL ) {
+                               add_to( &vals, &nto, mail );
+
+                               ldap_value_free( mail );
+                       }
+
+                       ldap_msgfree( res );
+               }
+       }
+
+       return( vals );
+}