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