diff --git a/clients/maildap/Makefile.in b/clients/maildap/Makefile.in new file mode 100644 index 0000000000000000000000000000000000000000..260fefcae942a18835dac2e4737d0169db83ed30 --- /dev/null +++ b/clients/maildap/Makefile.in @@ -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 index 0000000000000000000000000000000000000000..2579343e708bcf4ace5e6bca033a4558c25920ab --- /dev/null +++ b/clients/maildap/README @@ -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 index 0000000000000000000000000000000000000000..a55e36b16c1053a01378b44ac8ca98db9c5178d4 --- /dev/null +++ b/clients/maildap/maildap.m4 @@ -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 index 0000000000000000000000000000000000000000..9a2ad1aceba02c23ddf68e96ca22cebd58b536f6 --- /dev/null +++ b/clients/maildap/main.c @@ -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 = ¤t_group->g_members; + current_nto = ¤t_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 ); +}