]> git.sur5r.net Git - openldap/commitdiff
The experimental mail500 has been renamed as maildap. This is an exact
authorJulio Sánchez Fernández <jsanchez@openldap.org>
Fri, 9 Jun 2000 00:03:39 +0000 (00:03 +0000)
committerJulio Sánchez Fernández <jsanchez@openldap.org>
Fri, 9 Jun 2000 00:03:39 +0000 (00:03 +0000)
copy of the code formerly in mail500, to properly track changes.  An
exception is mail500.m4 that has been committed as maildap.m4 directly
to avoid breaking history twice.

clients/maildap/Makefile.in [new file with mode: 0644]
clients/maildap/README [new file with mode: 0644]
clients/maildap/maildap.m4 [new file with mode: 0644]
clients/maildap/main.c [new file with mode: 0644]

diff --git a/clients/maildap/Makefile.in b/clients/maildap/Makefile.in
new file mode 100644 (file)
index 0000000..260fefc
--- /dev/null
@@ -0,0 +1,31 @@
+# $OpenLDAP$
+
+UNIX_PRGS = mail500
+PROGRAMS = $(@PLAT@_PRGS)
+
+SRCS=  main.c
+XSRCS= version.c
+OBJS=  main.o
+
+LDAP_INCDIR= ../../include
+LDAP_LIBDIR= ../../libraries
+
+XLIBS =  -lldap -llber -llutil
+XXLIBS = $(SECURITY_LIBS) $(LUTIL_LIBS)
+
+mail500 : version.o
+       $(LTLINK) -o $@ version.o $(OBJS) $(LIBS)
+
+version.c: ${OBJS} $(LDAP_LIBDEPEND)
+       @-$(RM) $@
+       $(MKVERSION) mail500 > $@
+
+install-local: $(PROGRAMS) FORCE
+       -$(MKDIR) $(DESTDIR)$(libexecdir)
+       @(                                                              \
+           for prg in $(PROGRAMS); do                                  \
+               $(LTINSTALL) $(INSTALLFLAGS) -s -m 755 $$prg$(EXEEXT)   \
+                   $(DESTDIR)$(libexecdir);                            \
+           done                                                        \
+       )
+
diff --git a/clients/maildap/README b/clients/maildap/README
new file mode 100644 (file)
index 0000000..2579343
--- /dev/null
@@ -0,0 +1,357 @@
+
+*** 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
+
+*** WHAT mail500 DOES: ***
+
+mail500 is designed to be invoked as a mailer (e.g., from sendmail),
+similar to the way /bin/mail works.  It takes a few required arguments
+and then a list of addresses to deliver to.  It expects to find the
+message to deliver on its standard input.  It looks up the addresses in
+X.500 to figure out where to route the mail, and then execs sendmail to
+do the actual delivery.  It supports simple aliases, groups, and
+mailing lists, the details of which are given below.
+
+*** HOW IT WORKS (from the sendmail side): ***
+
+The idea is that you might have a rule like this in your sendmail.cf
+file somewhere in rule set 0:
+
+R$*<@umich.edu>$*      $#mail500$@umich.edu$:<$1>
+
+This rule says that any address that ends in @umich.edu will cause
+the mail500 mailer to be called to deliver the mail.  You probably
+also want to do something to prevent addresses like terminator!tim@umich.edu
+or tim%terminator.rs.itd.umich.edu@umich.edu from being passed to mail500.
+At U-M, we do this by adding rules like this to rule set 9 where we
+strip off our local names:
+
+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.
+
+The mail500 mailer should be defined similar to this in the
+sendmail.cf file:
+
+Mmail500, P=/usr/local/etc/mail500, F=DFMSmnXuh, A=mail500 -f $f -h $h -m $n@$w $u
+
+This defines how mail500 will be treated by sendmail and what
+arguments it will have when it's called.  The various flags specified
+by the F=... parameter are explained in your local sendmail book (with
+any luck).  The arguments to mail500 are as follows:
+
+       -f      Who the mail is from.  This will be used as the address
+               to which any errors should be sent (unless the address
+               specifies a mailing list - see below).  Normally, sendmail
+               defines the $f macro to be the sender.
+
+       -h      The domain for which the mail is destined.  This is passed
+               in to mail500 via the $h macro, which is set by the
+               $@ metasymbol in the rule added to rule set 0 above.
+               It's normally used when searching for groups.
+
+       -m      The mailer-daemon address.  If errors have to be sent,
+               this is the address they will come from.  $n is normally
+               set to mailer-daemon and $w is normally the local host
+               name.
+
+The final argument $u is used to stand for the addresses to which to
+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:
+
+       Search (18) [2]: dc=org@dc=OpenLDAP@ou=People
+       Search subtree (uid=name)
+       Search (18) [3]: dc=org@dc=OpenLDAP@ou=People
+       Search subtree (cn=name)
+
+       Search (18) [4]: dc=org@dc=OpenLDAP@ou=Groups@ou=System Groups
+       Search subtree & ((cn=name)(associatedDomain=OpenLDAP.org))
+
+       Search (18) [5]: dc=org@dc=OpenLDAP@ou=Groups@ou=User Groups
+       Search subtree & ((cn=name)(associatedDomain=OpenLDAP.org))
+
+[Beware: Currently unimplemented]
+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
+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): ***
+
+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 ]
+
+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
+entries mail500 will deliver to the value(s) listed in the
+rfc822Mailbox attribute of the entry.  For example, at U-M my entry has
+the attribute
+
+       mail= tim@terminator.rs.itd.umich.edu
+
+So mail sent to tim@umich.edu will be delivered via mail500 to that
+address.  If there were multiple values for the mail attribute, multiple
+copies of the mail would be sent.
+
+A new object class, rfc822MailGroup, and several new attributes have
+been defined to handle email groups/mailing lists.  To use this, you
+will need to add this to your local oidtable.oc:
+
+       # object class for representing rfc 822 mailgroups
+       rfc822MailGroup:        umichObjectClass.2 : \
+               top : \
+               cn : \
+               rfc822Mailbox, member, memberOfGroup, owner, \
+               errorsTo, rfc822ErrorsTo, requestsTo, rfc822RequestsTo,
+               joinable, associatedDomain, \
+               description, multiLineDescription, \
+               userPassword, krbName, \
+               telecommunicationAttributeSet, postalAttributeSet
+
+And you will need to add these to your local oidtable.at:
+
+       # attrs for rfc822mailgroups
+       multiLineDescription:   umichAttributeType.2    : CaseIgnoreList
+       rfc822ErrorsTo:         umichAttributeType.26   : CaseIgnoreIA5String
+       rfc822RequestsTo:       umichAttributeType.27   : CaseIgnoreIA5String
+       joinable:               umichAttributeType.28   : Boolean
+       memberOfGroup:          umichAttributeType.29   : DN
+       errorsTo:               umichAttributeType.30   : DN
+       requestsTo:             umichAttributeType.31   : DN
+
+The idea was to define a kind of hybrid mail group that could handle
+people who were in X.500 or not.  So, for example, members of a group
+can be specified via the member attribute (for X.500 members) or the
+rfc822MailBox attribute (for non-X.500 members).  Similarly for the
+errorsTo and rfc822ErrorsTo, and the requestsTo and rfc822RequestsTo
+attributes.
+
+To create a real mailing list, with a list maintainer, all you have to
+do is create an rfc822MailGroup and fill in the errorsTo or
+rfc822ErrorsTo attributes (or both).  That will cause any errors
+encountered when delivering mail to the group to go to the addresses
+listed (or X.500 entry via it's mail attribute).
+
+If you fill in the requestsTo or rfc822RequestsTo (or both) attributes,
+mail sent to groupname-request will be sent to the addresses listed
+there.  mail500 does this automatically, so you don't have to explicitly
+add the groupname-request alias to your group.
+
+To allow users to join a group, there is the joinable flag.  If TRUE,
+mail500 will search for entries that have a memberOfGroup attribute
+equal to the DN of the group, using the same algorithm it used to find
+the group in the first place (i.e. the DNs and filters listed in the
+base array).  This allows people to join (or subscribe to) a group
+without having to modify the group entry directly.  If joinable is
+FALSE, the search is not done.
+
+Finally, keep in mind that this is somewhat experimental at the moment.
+We are using it in production at U-M, but your mileage may vary...
diff --git a/clients/maildap/maildap.m4 b/clients/maildap/maildap.m4
new file mode 100644 (file)
index 0000000..a55e36b
--- /dev/null
@@ -0,0 +1,53 @@
+PUSHDIVERT(-1)
+## Copyright 1998-2000 The OpenLDAP Foundation, Redwood City, California, USA
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms are permitted only
+## 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.
+
+dnl
+dnl mail500 mailer
+dnl
+dnl This file should be placed in the sendmail's cf/mailer directory.
+dnl To include this mailer in your .cf file, use the directive:
+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)')
+ifdef(`MAIL500_MAILER_PATH',,
+       `ifdef(`MAIL500_PATH',
+               `define(`MAIL500_MAILER_PATH', MAIL500_PATH)',
+               `define(`MAIL500_MAILER_PATH', /usr/local/libexec/mail500)')')
+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
+
+MAILER_DEFINITIONS
+
+######################*****##############
+###   MAIL500 Mailer specification   ###
+##################*****##################
+
+VERSIONID(`$OpenLDAP$')
+
+Mmail500,      P=MAIL500_MAILER_PATH, F=CONCAT(`DFM', MAIL500_MAILER_FLAGS), S=11/31, R=20/40, T=DNS/RFC822/X-Unix,
+               ifdef(`MAIL500_MAILER_MAX', `M=500_MAILER_MAX, ')A=MAIL500_MAILER_ARGS
+
+LOCAL_CONFIG
+# Mail500 Domains
+#CQ foo.com
+
+PUSHDIVERT(3)
+# mail500 additions
+R$* < @ $=Q > $*       $#mail500 $@ $2 $: <$1@$2>              domain handled by mail500
+POPDIVERT
diff --git a/clients/maildap/main.c b/clients/maildap/main.c
new file mode 100644 (file)
index 0000000..9a2ad1a
--- /dev/null
@@ -0,0 +1,2062 @@
+/* $OpenLDAP$ */
+/*
+ * Copyright (c) 1990 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that this notice is preserved and that due credit is given
+ * to the University of Michigan at Ann Arbor. The name of the University
+ * may not be used to endorse or promote products derived from this
+ * software without specific prior written permission. This software
+ * is provided ``as is'' without express or implied warranty.
+ *
+ * Copyright 1998-2000 The OpenLDAP Foundation
+ * COPYING RESTRICTIONS APPLY.  See COPYRIGHT File in top level directory
+ * of this package for details.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/stdlib.h>
+
+#include <ac/ctype.h>
+#include <ac/param.h>
+#include <ac/signal.h>
+#include <ac/string.h>
+#include <ac/sysexits.h>
+#include <ac/syslog.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+#include <ac/wait.h>
+
+#include <sys/stat.h>
+
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+
+#include <ldap.h>
+
+#include "ldap_defaults.h"
+
+#ifndef MAIL500_BOUNCEFROM
+#define MAIL500_BOUNCEFROM "<>"
+#endif
+
+#define USER           0x01
+#define GROUP_ERRORS   0x02
+#define GROUP_REQUEST  0x04
+#define GROUP_MEMBERS  0x08
+#define GROUP_OWNER    0x10
+
+#define ERROR          "error"
+#define ERRORS         "errors"
+#define REQUEST                "request"
+#define REQUESTS       "requests"
+#define MEMBERS                "members"
+#define OWNER          "owner"
+#define OWNERS         "owners"
+
+LDAP   *ld;
+char   *vacationhost = NULL;
+char   *errorsfrom = MAIL500_BOUNCEFROM;
+char   *mailfrom = NULL;
+char   *host = NULL;
+char   *ldaphost = NULL;
+int    hostlen = 0;
+int    debug;
+
+typedef struct errs {
+       int             e_code;
+#define E_USERUNKNOWN          1
+#define E_AMBIGUOUS            2
+#define E_NOEMAIL              3
+#define E_NOREQUEST            4
+#define E_NOERRORS             5
+#define E_BADMEMBER            6
+#define E_JOINMEMBERNOEMAIL    7
+#define E_MEMBERNOEMAIL                8
+#define E_LOOP                 9
+#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;
+               LDAPMessage     *e_u_msg;
+       } e_union;
+#define e_msg  e_union.e_u_msg
+#define e_loop e_union.e_u_loop
+} Error;
+
+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_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      */
+} Base;
+
+Base   **base = 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 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 int  connect_to_x500( void );
+
+
+int
+main ( int argc, char **argv )
+{
+       char            *myname;
+       char            **tolist;
+       Error           *errlist;
+       Group           **togroups;
+       int             numto, ngroups, numerr, nargs;
+       int             i, j;
+       char            *conffile = NULL;
+
+       if ( (myname = strrchr( argv[0], '/' )) == NULL )
+               myname = strdup( argv[0] );
+       else
+               myname = strdup( myname + 1 );
+
+#ifdef SIGPIPE
+       (void) SIGNAL( SIGPIPE, SIG_IGN );
+#endif
+
+#ifdef LOG_MAIL
+       openlog( myname, OPENLOG_OPTIONS, LOG_MAIL );
+#elif LOG_DEBUG
+       openlog( myname, OPENLOG_OPTIONS );
+#endif
+
+       while ( (i = getopt( argc, argv, "d:C: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 );
+                       break;
+
+               case 'f':       /* who it's from & where errors should go */
+                       mailfrom = strdup( optarg );
+                       for ( j = 0; sendmailargs[j] != NULL; j++ ) {
+                               if ( strcmp( sendmailargs[j], "-f" ) == 0 ) {
+                                       sendmailargs[j+1] = mailfrom;
+                                       break;
+                               }
+                       }
+                       break;
+
+               case 'h':       /* hostname */
+                       host = strdup( optarg );
+                       hostlen = strlen(host);
+                       break;
+
+               case 'l':       /* ldap host */
+                       ldaphost = strdup( optarg );
+                       break;
+
+                               /* mailer-daemon address - who we should */
+               case 'm':       /* say errors come from */
+                       errorsfrom = strdup( optarg );
+                       break;
+
+               case 'v':       /* vacation host */
+                       vacationhost = strdup( optarg );
+                       break;
+
+               default:
+                       syslog( LOG_ALERT, "unknown option" );
+                       break;
+               }
+       }
+
+       if ( mailfrom == NULL ) {
+               syslog( LOG_ALERT, "required argument -f not present" );
+               exit( EX_TEMPFAIL );
+       }
+       if ( errorsfrom == NULL ) {
+               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" );
+               exit( EX_TEMPFAIL );
+       }
+
+       load_config( conffile );
+
+       if ( connect_to_x500() != 0 )
+               exit( EX_TEMPFAIL );
+
+       setuid( geteuid() );
+
+       if ( debug ) {
+               char    buf[1024];
+               int     i;
+
+               syslog( LOG_ALERT, "running as %d", geteuid() );
+               strcpy( buf, argv[0] );
+               for ( i = 1; i < argc; i++ ) {
+                       strcat( buf, " " );
+                       strcat( buf, argv[i] );
+               }
+
+               syslog( LOG_ALERT, "args: (%s)", buf );
+       }
+
+       tolist = NULL;
+       numto = 0;
+       add_to( &tolist, &numto, sendmailargs );
+       nargs = numto;
+       ngroups = numerr = 0;
+       togroups = NULL;
+       errlist = NULL;
+       for ( i = optind; i < argc; i++ ) {
+               char    *s;
+               int     type;
+               char    *localpart = NULL, *domainpart = NULL;
+               char    address[1024];
+
+               type = USER;
+               split_address( argv[i], &localpart, &domainpart );
+               if ( (s = strrchr( localpart, '-' )) != NULL ) {
+                       s++;
+
+                       if ((strcasecmp(s, ERROR) == 0) ||
+                               (strcasecmp(s, ERRORS) == 0)) {
+                               type = GROUP_ERRORS;
+                               *(--s) = '\0';
+                       } else if ((strcasecmp(s, REQUEST) == 0) ||
+                               (strcasecmp(s, REQUESTS) == 0)) {
+                               type = GROUP_REQUEST;
+                               *(--s) = '\0';
+                       } else if ( strcasecmp( s, MEMBERS ) == 0 ) {
+                               type = GROUP_MEMBERS;
+                               *(--s) = '\0';
+                       } else if ((strcasecmp(s, OWNER) == 0) ||
+                               (strcasecmp(s, OWNERS) == 0)) {
+                               type = GROUP_OWNER;
+                               *(--s) = '\0';
+                       }
+               }
+
+               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,
+                   &errlist, &numerr, type );
+       }
+
+       /*
+        * If we have both errors and successful deliveries to make or if
+        * if there are any groups to deliver to, we basically need to read
+        * the message twice.  So, we have to put it in a tmp file.
+        */
+
+       if ( numerr > 0 && numto > nargs || ngroups > 0 ) {
+               FILE    *fp;
+               char    buf[BUFSIZ];
+
+               umask( 077 );
+               if ( (fp = tmpfile()) == NULL ) {
+                       syslog( LOG_ALERT, "could not open tmp file" );
+                       unbind_and_exit( EX_TEMPFAIL );
+               }
+
+               /* copy the message to a temp file */
+               while ( fgets( buf, sizeof(buf), stdin ) != NULL ) {
+                       if ( fputs( buf, fp ) == EOF ) {
+                               syslog( LOG_ALERT, "error writing tmpfile" );
+                               unbind_and_exit( EX_TEMPFAIL );
+                       }
+               }
+
+               if ( dup2( fileno( fp ), 0 ) == -1 ) {
+                       syslog( LOG_ALERT, "could not dup2 tmpfile" );
+                       unbind_and_exit( EX_TEMPFAIL );
+               }
+
+               fclose( fp );
+       }
+
+       /* deal with errors */
+       if ( numerr > 0 ) {
+               if ( debug ) {
+                       syslog( LOG_ALERT, "sending errors" );
+               }
+               (void) rewind( stdin );
+               send_errors( errlist, numerr );
+       }
+
+       (void) ldap_unbind( ld );
+
+       /* send to groups with errorsTo */
+       if ( ngroups > 0 ) {
+               if ( debug ) {
+                       syslog( LOG_ALERT, "sending to groups with errorsto" );
+               }
+               (void) rewind( stdin );
+               send_group( togroups, ngroups );
+       }
+
+       /* send to expanded aliases and groups w/o errorsTo */
+       if ( numto > nargs ) {
+               if ( debug ) {
+                       syslog( LOG_ALERT, "sending to aliases and groups" );
+               }
+               (void) rewind( stdin );
+               send_message( tolist );
+       }
+
+       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 )
+{
+       int opt;
+
+       if ( (ld = ldap_init( ldaphost, 0 )) == NULL ) {
+               syslog( LOG_ALERT, "ldap_init failed" );
+               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);
+
+       if ( ldap_simple_bind_s( ld, NULL, NULL ) != LDAP_SUCCESS ) {
+               syslog( LOG_ALERT, "ldap_simple_bind_s failed" );
+               return( -1 );
+       }
+
+       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
+)
+{
+       int             rc;
+       int             i;
+       int             resolved = 0;
+       LDAPMessage     *res, *e;
+       struct timeval  timeout;
+
+       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 );
+
+                               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;
+                       }
+               }
+       }
+       return( resolved );
+}
+
+static int
+search_ldap_url(
+       char    *url,
+       Subst   *substs,
+       char    *address,
+       int     rdnpref,
+       int     multi_entry,
+       char    ***to,
+       int     *nto,
+       Group   ***togroups,
+       int     *ngroups,
+       Error   **err,
+       int     *nerr,
+       int     type
+)
+{
+       LDAPURLDesc     *ludp;
+       char            *p, *s, *d;
+       int             i;
+       char            filter[1024];
+       LDAPMessage     *e, *res;
+       int             rc;
+       char            **attrlist;
+       struct timeval  timeout;
+       int             match;
+       int             resolved = 0;
+       char            *dn;
+
+       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;
+               }
+               add_error( err, nerr, E_BADMEMBER,
+                          url, NULL );
+               return 0;
+       }
+
+       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--;
+                                               break;
+                                       }
+                               }
+                               if ( substs[i].sub_char == '\0' ) {
+                                       syslog( LOG_ALERT,
+                                               "unknown format %c", *s );
+                               }
+                       } else {
+                               *d = *s;
+                       }
+               }
+               *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 );
+       }
+
+       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 );
+                       }
+               }
+               return ( resolved );
+       }
+
+       /* more than one match - bounce with ambiguous user? */
+       if ( match > 1 ) {
+               LDAPMessage     *next, *tmpres = NULL;
+               char            *dn;
+               char            **xdn;
+
+               /* not giving rdn preference - bounce with ambiguous user */
+               if ( rdnpref == 0 ) {
+                       add_error( err, nerr, E_AMBIGUOUS, address, res );
+                       return 0;
+               }
+
+               /*
+                * giving rdn preference - see if any entries were matched
+                * because of their rdn.  If so, collect them to deal with
+                * later (== 1 we deliver, > 1 we bounce).
+                */
+
+               for ( e = ldap_first_entry( ld, res ); e != NULL; e = next ) {
+                       next = ldap_next_entry( ld, e );
+                       dn = ldap_get_dn( ld, e );
+                       xdn = ldap_explode_dn( dn, 1 );
+
+                       /* XXX bad, but how else can we do it? XXX */
+                       if ( strcasecmp( xdn[0], address ) == 0 ) {
+                               ldap_delete_result_entry( &res, e );
+                               ldap_add_result_entry( &tmpres, e );
+                       }
+
+                       ldap_value_free( xdn );
+                       free( dn );
+               }
+
+               /* nothing matched by rdn - go ahead and bounce */
+               if ( tmpres == NULL ) {
+                       add_error( err, nerr, E_AMBIGUOUS, address, res );
+                       return 0;
+
+               /* 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;
+
+               /* trouble... */
+               } else if ( match < 0 ) {
+                       syslog( LOG_ALERT, "error parsing result from X.500" );
+                       unbind_and_exit( EX_TEMPFAIL );
+               }
+
+               /* 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 );
+
+               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 );
+               }
+       }
+       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;
+
+       for ( i = 0; urllist[i]; i++ ) {
+
+               if ( !strncasecmp( urllist[i], "mail:", 5 ) ) {
+                       char    *vals[2];
+
+                       vals[0] = urllist[i] + 5;
+                       vals[1] = NULL;
+                       add_to( to, nto, vals );
+                       resolved = 1;
+
+               } else if ( ldap_is_ldap_url( urllist[i] ) ) {
+
+                       resolved = search_ldap_url( urllist[i], NULL,
+                                                   address, 0, multi_entry,
+                                                   to, nto, togroups, ngroups,
+                                                   err, nerr, type );
+               } else {
+                       /* Produce some sensible error here */
+                       resolved = 0;
+               }
+       }
+       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 ( myhosts == NULL )
+               return 0;
+       for ( d = myhosts; *d; d++ ) {
+               if ( !strcasecmp(*d,host) ) {
+                       return 1;
+               }
+       }
+       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;
+               }
+       }
+       return 0;
+}
+
+static void
+do_addresses(
+       char    **addresses,
+       char    ***to,
+       int     *nto,
+       Group   ***togroups,
+       int     *ngroups,
+       Error   **err,
+       int     *nerr,
+       int     type
+)
+{
+       int     i, j;
+       int     n;
+
+       /*
+        * 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.
+        */
+       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++;
+               }
+       }
+       addresses[j] = NULL;
+       if ( addresses[0] ) {
+               add_to( to, nto, addresses );
+       }
+}
+
+/*
+ * 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.
+ */
+
+static int
+entry_engine(
+       LDAPMessage *e,
+       char    *dn,
+       char    *address,
+       char    ***to,
+       int     *nto,
+       Group   ***togroups,
+       int     *ngroups,
+       Error   **err,
+       int     *nerr,
+       int     type
+)
+{
+       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;
+               }
+               vals = ldap_get_values( ld, e, as->as_name );
+               if ( !vals || vals[0] == NULL ) {
+                       continue;
+               }
+               nent = count_values( vals );
+               if ( nent > 1 && !as->as_m_valued ) {
+                       add_error( err, nerr, E_AMBIGUOUS, address, e );
+                       return( 0 );
+               }
+               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;
+
+                       case AS_SYNTAX_DN:
+                               if ( dn_search( vals, address,
+                                               current_to, current_nto,
+                                               togroups, ngroups,
+                                               err, nerr ) ) {
+                                       resolved = 1;
+                               }
+                               break;
+
+                       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;
+
+                       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;
+
+                       default:
+                               syslog( LOG_ALERT,
+                                       "Invalid syntax %d for kind %d",
+                                       as->as_syntax, as->as_kind );
+                               break;
+                       }
+                       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 );
+                       }
+                       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;
+
+               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;
+
+               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] );
+                               }
+                       }
+                       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;
+                       }
+
+               case AS_KIND_OWN_ADDR:
+                       add_to( &own_addresses, &own_addresses_total, vals );
+                       cur_priority = as->as_priority;
+                       break;
+
+               case AS_KIND_DELIVERY_TYPE:
+                       add_to( &delivery_types, &delivery_types_total, vals );
+                       cur_priority = as->as_priority;
+                       break;
+
+               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 );
+                       }
+                       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 = '%';
+               }
+               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 );
+       }
+       if ( delivery_types ) {
+               ldap_value_free( delivery_types );
+       }
+                 
+       return( resolved );
+}
+
+static int
+search_bases(
+       char    *filter,
+       Subst   *substs,
+       char    *name,
+       char    ***to,
+       int     *nto,
+       Group   ***togroups,
+       int     *ngroups,
+       Error   **err,
+       int     *nerr,
+       int     type
+)
+{
+       int             b, resolved = 0;
+
+       for ( b = 0; base[b] != NULL; b++ ) {
+
+               if ( ! (base[b]->b_search & type) ) {
+                       continue;
+               }
+
+               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;
+       }
+       return( resolved );
+}
+
+static void
+do_address(
+       char    *name,
+       char    ***to,
+       int     *nto,
+       Group   ***togroups,
+       int     *ngroups,
+       Error   **err,
+       int     *nerr,
+       int     type
+)
+{
+       char            *localpart = NULL, *domainpart = NULL;
+       char            *synthname = NULL;
+       int             resolved;
+       int             i;
+       Subst           substs[6];
+
+       /*
+        * 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.
+        */
+
+       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 );
+       }
+       if ( synthname ) {
+               free( synthname );
+       }
+
+       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
+send_message( char **to )
+{
+       int     pid;
+#ifndef HAVE_WAITPID
+       WAITSTATUSTYPE  status;
+#endif
+
+       if ( debug ) {
+               char    buf[1024];
+               int     i;
+
+               strcpy( buf, to[0] );
+               for ( i = 1; to[i] != NULL; i++ ) {
+                       strcat( buf, " " );
+                       strcat( buf, to[i] );
+               }
+
+               syslog( LOG_ALERT, "send_message execing sendmail: (%s)", buf );
+       }
+
+       /* parent */
+       if ( (pid = fork()) != 0 ) {
+#ifdef HAVE_WAITPID
+               waitpid( pid, (int *) NULL, 0 );
+#else
+               wait4( pid, &status, WAIT_FLAGS, 0 );
+#endif
+       /* child */
+       } else {
+               /* to includes sendmailargs */
+               execv( MAIL500_SENDMAIL, to );
+
+               syslog( LOG_ALERT, "execv failed" );
+
+               exit( EX_TEMPFAIL );
+       }
+}
+
+static void
+send_group( Group **group, int ngroup )
+{
+       int     i, pid;
+       char    **argv;
+       int     argc;
+       char    *iargv[7];
+#ifndef HAVE_WAITPID
+       WAITSTATUSTYPE  status;
+#endif
+
+       for ( i = 0; i < ngroup; i++ ) {
+               (void) rewind( stdin );
+
+               iargv[0] = MAIL500_SENDMAIL;
+               iargv[1] = "-f";
+               iargv[2] = group[i]->g_errorsto;
+               iargv[3] = "-oMrX.500";
+               iargv[4] = "-odi";
+               iargv[5] = "-oi";
+               iargv[6] = NULL;
+
+               argv = NULL;
+               argc = 0;
+               add_to( &argv, &argc, iargv );
+               add_to( &argv, &argc, group[i]->g_members );
+
+               if ( debug ) {
+                       char    buf[1024];
+                       int     i;
+
+                       strcpy( buf, argv[0] );
+                       for ( i = 1; i < argc; i++ ) {
+                               strcat( buf, " " );
+                               strcat( buf, argv[i] );
+                       }
+
+                       syslog( LOG_ALERT, "execing sendmail: (%s)", buf );
+               }
+
+               /* parent */
+               if ( (pid = fork()) != 0 ) {
+#ifdef HAVE_WAITPID
+                       waitpid( pid, (int *) NULL, 0 );
+#else
+                       wait4( pid, &status, WAIT_FLAGS, 0 );
+#endif
+               /* child */
+               } else {
+                       execv( MAIL500_SENDMAIL, argv );
+
+                       syslog( LOG_ALERT, "execv failed" );
+
+                       exit( EX_TEMPFAIL );
+               }
+       }
+}
+
+static void
+send_errors( Error *err, int nerr )
+{
+       int     pid, i, namelen;
+       FILE    *fp;
+       int     fd[2];
+       char    *argv[8];
+       char    buf[1024];
+#ifndef HAVE_WAITPID
+       WAITSTATUSTYPE  status;
+#endif
+
+       if ( strcmp( MAIL500_BOUNCEFROM, mailfrom ) == 0 ) {
+           mailfrom = errorsfrom;
+       }
+
+       argv[0] = MAIL500_SENDMAIL;
+       argv[1] = "-oMrX.500";
+       argv[2] = "-odi";
+       argv[3] = "-oi";
+       argv[4] = "-f";
+       argv[5] = MAIL500_BOUNCEFROM;
+       argv[6] = mailfrom;
+       argv[7] = NULL;
+
+       if ( debug ) {
+               int     i;
+
+               strcpy( buf, argv[0] );
+               for ( i = 1; argv[i] != NULL; i++ ) {
+                       strcat( buf, " " );
+                       strcat( buf, argv[i] );
+               }
+
+               syslog( LOG_ALERT, "execing sendmail: (%s)", buf );
+       }
+
+       if ( pipe( fd ) == -1 ) {
+               syslog( LOG_ALERT, "cannot create pipe" );
+               exit( EX_TEMPFAIL );
+       }
+
+       if ( (pid = fork()) != 0 ) {
+               if ( (fp = fdopen( fd[1], "w" )) == NULL ) {
+                       syslog( LOG_ALERT, "cannot fdopen pipe" );
+                       exit( EX_TEMPFAIL );
+               }
+
+               fprintf( fp, "To: %s\n", mailfrom );
+               fprintf( fp, "From: %s\n", errorsfrom );
+               fprintf( fp, "Subject: undeliverable mail\n" );
+               fprintf( fp, "\n" );
+               fprintf( fp, "The following errors occurred when trying to deliver the attached mail:\n" );
+               for ( i = 0; i < nerr; i++ ) {
+                       namelen = strlen( err[i].e_addr );
+                       fprintf( fp, "\n" );
+
+                       switch ( err[i].e_code ) {
+                       case E_USERUNKNOWN:
+                               fprintf( fp, "%s: User unknown\n", err[i].e_addr );
+                               break;
+
+                       case E_GROUPUNKNOWN:
+                               fprintf( fp, "%s: Group unknown\n", err[i].e_addr );
+                               break;
+
+                       case E_BADMEMBER:
+                               fprintf( fp, "%s: Group member does not exist\n",
+                                   err[i].e_addr );
+                               fprintf( fp, "This could be because the distinguished name of the person has changed\n" );
+                               fprintf( fp, "If this is the case, the problem can be solved by removing and\n" );
+                               fprintf( fp, "then re-adding the person to the group.\n" );
+                               break;
+
+                       case E_NOREQUEST:
+                               fprintf( fp, "%s: Group exists but has no request address\n",
+                                   err[i].e_addr );
+                               break;
+
+                       case E_NOERRORS:
+                               fprintf( fp, "%s: Group exists but has no errors-to address\n",
+                                   err[i].e_addr );
+                               break;
+
+                       case E_NOOWNER:
+                               fprintf( fp, "%s: Group exists but has no owner\n",
+                                   err[i].e_addr );
+                               break;
+
+                       case E_AMBIGUOUS:
+                               do_ambiguous( fp, &err[i], namelen );
+                               break;
+
+                       case E_NOEMAIL:
+                               do_noemail( fp, &err[i], namelen );
+                               break;
+
+                       case E_MEMBERNOEMAIL:
+                               fprintf( fp, "%s: Group member exists but does not have an email address\n",
+                                   err[i].e_addr );
+                               break;
+
+                       case E_JOINMEMBERNOEMAIL:
+                               fprintf( fp, "%s: User has joined group but does not have an email address\n",
+                                   err[i].e_addr );
+                               break;
+
+                       case E_LOOP:
+                               fprintf( fp, "%s: User has created a mail loop by adding address %s to their X.500 entry\n",
+                                   err[i].e_addr, err[i].e_loop );
+                               break;
+
+                       case E_NOMEMBERS:
+                               fprintf( fp, "%s: Group has no members\n",
+                                   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 );
+                               break;
+                       }
+               }
+
+               fprintf( fp, "\n------- The original message sent:\n\n" );
+
+               while ( fgets( buf, sizeof(buf), stdin ) != NULL ) {
+                       fputs( buf, fp );
+               }
+               fclose( fp );
+
+#ifdef HAVE_WAITPID
+               waitpid( pid, (int *) NULL, 0 );
+#else
+               wait4( pid, &status, WAIT_FLAGS, 0 );
+#endif
+       } else {
+               dup2( fd[0], 0 );
+
+               execv( MAIL500_SENDMAIL, argv );
+
+               syslog( LOG_ALERT, "execv failed" );
+
+               exit( EX_TEMPFAIL );
+       }
+}
+
+static void
+do_noemail( FILE *fp, Error *err, int namelen )
+{
+       int             i, last;
+       char            *dn, *rdn;
+       char            **ufn, **vals;
+
+       fprintf(fp, "%s: User has no email address registered.\n",
+           err->e_addr );
+       fprintf( fp, "%*s  Name, title, postal address and phone for '%s':\n\n",
+           namelen, " ", err->e_addr );
+
+       /* name */
+       dn = ldap_get_dn( ld, err->e_msg );
+       ufn = ldap_explode_dn( dn, 1 );
+       rdn = strdup( ufn[0] );
+       if ( strcasecmp( rdn, err->e_addr ) == 0 ) {
+               if ( (vals = ldap_get_values( ld, err->e_msg, "cn" ))
+                   != NULL ) {
+                       for ( i = 0; vals[i]; i++ ) {
+                               last = strlen( vals[i] ) - 1;
+                               if ( isdigit((unsigned char) vals[i][last]) ) {
+                                       rdn = strdup( vals[i] );
+                                       break;
+                               }
+                       }
+
+                       ldap_value_free( vals );
+               }
+       }
+       fprintf( fp, "%*s  %s\n", namelen, " ", rdn );
+       free( dn );
+       free( rdn );
+       ldap_value_free( ufn );
+
+       /* titles or descriptions */
+       if ( (vals = ldap_get_values( ld, err->e_msg, "title" )) == NULL &&
+           (vals = ldap_get_values( ld, err->e_msg, "description" ))
+           == NULL ) {
+               fprintf( fp, "%*s  No title or description registered\n",
+                   namelen, " " );
+       } else {
+               for ( i = 0; vals[i] != NULL; i++ ) {
+                       fprintf( fp, "%*s  %s\n", namelen, " ", vals[i] );
+               }
+
+               ldap_value_free( vals );
+       }
+
+       /* postal address */
+       if ( (vals = ldap_get_values( ld, err->e_msg, "postalAddress" ))
+           == NULL ) {
+               fprintf( fp, "%*s  No postal address registered\n", namelen,
+                   " " );
+       } else {
+               fprintf( fp, "%*s  ", namelen, " " );
+               for ( i = 0; vals[0][i] != '\0'; i++ ) {
+                       if ( vals[0][i] == '$' ) {
+                               fprintf( fp, "\n%*s  ", namelen, " " );
+                               while ( isspace((unsigned char) vals[0][i+1]) )
+                                       i++;
+                       } else {
+                               fprintf( fp, "%c", vals[0][i] );
+                       }
+               }
+               fprintf( fp, "\n" );
+
+               ldap_value_free( vals );
+       }
+
+       /* telephone number */
+       if ( (vals = ldap_get_values( ld, err->e_msg, "telephoneNumber" ))
+           == NULL ) {
+               fprintf( fp, "%*s  No phone number registered\n", namelen,
+                   " " );
+       } else {
+               for ( i = 0; vals[i] != NULL; i++ ) {
+                       fprintf( fp, "%*s  %s\n", namelen, " ", vals[i] );
+               }
+
+               ldap_value_free( vals );
+       }
+}
+
+/* ARGSUSED */
+static void
+do_ambiguous( FILE *fp, Error *err, int namelen )
+{
+       int             i, last;
+       char            *dn, *rdn;
+       char            **ufn, **vals;
+       LDAPMessage     *e;
+
+       i = ldap_result2error( ld, err->e_msg, 0 );
+
+       fprintf( fp, "%s: Ambiguous user.  %s%d matches found:\n\n",
+           err->e_addr, i == LDAP_SIZELIMIT_EXCEEDED ? "First " : "",
+           ldap_count_entries( ld, err->e_msg ) );
+
+       for ( e = ldap_first_entry( ld, err->e_msg ); e != NULL;
+           e = ldap_next_entry( ld, e ) ) {
+               dn = ldap_get_dn( ld, e );
+               ufn = ldap_explode_dn( dn, 1 );
+               rdn = strdup( ufn[0] );
+               if ( strcasecmp( rdn, err->e_addr ) == 0 ) {
+                       if ( (vals = ldap_get_values( ld, e, "cn" )) != NULL ) {
+                               for ( i = 0; vals[i]; i++ ) {
+                                       last = strlen( vals[i] ) - 1;
+                                       if (isdigit((unsigned char) vals[i][last])) {
+                                               rdn = strdup( vals[i] );
+                                               break;
+                                       }
+                               }
+
+                               ldap_value_free( vals );
+                       }
+               }
+
+               /* 
+               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++ ) {
+                       fprintf( fp, "                         %s\n", vals[i] );
+               }
+
+               free( dn );
+               free( rdn );
+               ldap_value_free( ufn );
+               if ( vals != NULL )
+                       ldap_value_free( vals );
+       }
+}
+
+static int
+count_values( char **list )
+{
+       int     i;
+
+       for ( i = 0; list && list[i] != NULL; i++ )
+               ;       /* NULL */
+
+       return( i );
+}
+
+static void
+add_to( char ***list, int *nlist, char **new )
+{
+       int     i, nnew, oldnlist;
+
+       nnew = count_values( new );
+
+       oldnlist = *nlist;
+       if ( *list == NULL || *nlist == 0 ) {
+               *list = (char **) malloc( (nnew + 1) * sizeof(char *) );
+               *nlist = nnew;
+       } else {
+               *list = (char **) realloc( *list, *nlist * sizeof(char *) +
+                   nnew * sizeof(char *) + sizeof(char *) );
+               *nlist += nnew;
+       }
+
+       for ( i = 0; i < nnew; i++ )
+               (*list)[i + oldnlist] = strdup( new[i] );
+       (*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;
+       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 );
+                       }
+               }
+       }
+       ldap_value_free( oclist );
+
+       return( 0 );
+}
+
+static void
+add_error( Error **err, int *nerr, int code, char *addr, LDAPMessage *msg )
+{
+       if ( *nerr == 0 ) {
+               *err = (Error *) malloc( sizeof(Error) );
+       } else {
+               *err = (Error *) realloc( *err, (*nerr + 1) * sizeof(Error) );
+       }
+
+       (*err)[*nerr].e_code = code;
+       (*err)[*nerr].e_addr = strdup( addr );
+       (*err)[*nerr].e_msg = msg;
+       (*nerr)++;
+}
+
+static void
+unbind_and_exit( int rc )
+{
+       int     i;
+
+       if ( (i = ldap_unbind( ld )) != LDAP_SUCCESS )
+               syslog( LOG_ALERT, "ldap_unbind failed %d\n", i );
+
+       exit( rc );
+}