From 42d53c49f0910df8b5288db13ca11179393424e9 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Julio=20S=C3=A1nchez=20Fern=C3=A1ndez?= Date: Wed, 30 Jun 1999 13:54:32 +0000 Subject: [PATCH] Initial version of new mail500. --- clients/mail500/README | 253 +++++- clients/mail500/mail500.m4 | 12 +- clients/mail500/main.c | 1642 +++++++++++++++++++----------------- 3 files changed, 1102 insertions(+), 805 deletions(-) diff --git a/clients/mail500/README b/clients/mail500/README index 84545df9f8..2579343e70 100644 --- a/clients/mail500/README +++ b/clients/mail500/README @@ -1,13 +1,13 @@ -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, there are several -things you will have to tailor in main.c: +*** WARNING: Preliminary *** - LDAPHOST - The host running an LDAP server +This is the README file for mail500, a mailer that does X.500 lookups +via LDAP. - base[] - The array telling mail500 where/how to search for - things. See the explanation below. +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: *** @@ -37,6 +37,16 @@ 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. @@ -71,41 +81,64 @@ 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) 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, o=University of Michigan, c=US", 0 - "uid=%s", "cn=%s", NULL, - "ou=System Groups, ou=Groups, o=University of Michigan, c=US", 1 - "(&(cn=%s)(associatedDomain=%h))", NULL, NULL, - "ou=User Groups, ou=Groups, o=University of Michigan, c=US", 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]: c=US@o=University of Michigan@ou=People +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]: c=US@o=University of Michigan@ou=People + Search (18) [3]: dc=org@dc=OpenLDAP@ou=People Search subtree (cn=name) - Search (18) [4]: c=US@o=University of Michigan@ou=Groups@ou=System Groups - Search subtree & ((cn=name)(associatedDomain=umich.edu)) - - Search (18) [5]: c=US@o=University of Michigan@ou=Groups@ou=User Groups - Search subtree & ((cn=name)(associatedDomain=umich.edu)) + Search (18) [4]: dc=org@dc=OpenLDAP@ou=Groups@ou=System Groups + Search subtree & ((cn=name)(associatedDomain=OpenLDAP.org)) -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). + 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 @@ -113,13 +146,149 @@ 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. -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. - *** 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] [] [] + +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 ] + 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 diff --git a/clients/mail500/mail500.m4 b/clients/mail500/mail500.m4 index 83b38beb50..1cf58b5506 100644 --- a/clients/mail500/mail500.m4 +++ b/clients/mail500/mail500.m4 @@ -13,8 +13,10 @@ POPDIVERT dnl ifdef(`MAIL500_HOST', - `define(`MAIL500_HOST_FLAG', `')', - `define(`MAIL500_HOST_FLAG', CONCAT(` -l ', CONCAT(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)', @@ -23,11 +25,11 @@ ifdef(`MAIL500_MAILER_FLAGS',, `define(`MAIL500_MAILER_FLAGS', `SmnXuh')') ifdef(`MAIL500_MAILER_ARGS',, `define(`MAIL500_MAILER_ARGS', - CONCAT(`mail500',CONCAT(MAIL500_HOST_FLAG,`-f $f -h $h -m $n@$w $u')))') + CONCAT(`mail500',CONCAT(` -C ',MAIL500_CONFIG_PATH,MAIL500_HOST_FLAG,`-f $f -m $n@$w $u')))') dnl MAILER_DEFINITIONS -VERSIONID(`OpenLDAP mail500 981207') +VERSIONID(`OpenLDAP mail500 990630') ######################*****############## ### MAIL500 Mailer specification ### @@ -38,5 +40,5 @@ Mmail500, P=MAIL500_MAILER_PATH, F=CONCAT(`DFM', MAIL500_MAILER_FLAGS), S=11/31, PUSHDIVERT(3) # mail500 additions -R$* < @ $=Q > $* $#mail500 $@ $2 $: <$1> domain handled by mail500 +R$* < @ $=Q > $* $#mail500 $@ $2 $: <$1@$2> domain handled by mail500 POPDIVERT diff --git a/clients/mail500/main.c b/clients/mail500/main.c index d322aa205d..45676568f1 100644 --- a/clients/mail500/main.c +++ b/clients/mail500/main.c @@ -8,6 +8,10 @@ * 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,1999 The OpenLDAP Foundation + * COPYING RESTRICTIONS APPLY. See COPYRIGHT File in top level directory + * of this package for details. */ #include "portable.h" @@ -60,7 +64,7 @@ LDAP *ld; char *vacationhost = NULL; -char *errorsfrom = NULL; +char *errorsfrom = MAIL500_BOUNCEFROM; char *mailfrom = NULL; char *host = NULL; char *ldaphost = NULL; @@ -94,63 +98,92 @@ typedef struct groupto { char *g_dn; char *g_errorsto; char **g_members; + int g_nmembers; } Group; typedef struct baseinfo { - char *b_dn; /* dn to start searching at */ + 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 */ - char *b_filter[3]; /* filter to apply - name substituted for %s */ - /* (up to three of them) */ } Base; +/* + * We should limit the search to objectclass=mailRecipient or + * objectclass=mailGroup. + */ + +/* 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}}, + {"dc=StlInter, dc=Net", + 0, 0xff, + {"mail=%s", "mailAlternateAddress=%s", NULL}}, {NULL} }; +*/ -char *sendmailargs[] = { MAIL500_SENDMAIL, "-oMrLDAP", "-odi", "-oi", "-f", NULL, NULL }; +Base **base = NULL; -static char *attrs[] = { "objectClass", "title", "postaladdress", - "telephoneNumber", "mail", "description", "owner", - "errorsTo", "rfc822ErrorsTo", "requestsTo", - "rfc822RequestsTo", "joinable", "cn", "member", - "moderator", "onVacation", "uid", - "suppressNoEmailError", 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_final; /* If true, no further expansion is tried. */ + 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 /* Unqualified mailbox name */ +#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_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_FORWARD_TO_HOST 5 /* Expand at some other host */ +#define AS_KIND_ALLOWED_SENDER 6 /* Can send to group */ +#define AS_KIND_MODERATOR 7 + +AttrSemantics **attr_semantics = NULL; + +typedef struct subst { + char sub_char; + char *sub_value; +} Subst; + +char **groupclasses = NULL; +char **def_attr = NULL; + +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 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 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 ) @@ -161,6 +194,7 @@ main ( int argc, char **argv ) Group *togroups; int numto, ngroups, numerr, nargs; int i, j; + char *conffile = NULL; if ( (myname = strrchr( argv[0], '/' )) == NULL ) myname = strdup( argv[0] ); @@ -177,12 +211,16 @@ main ( int argc, char **argv ) openlog( myname, OPENLOG_OPTIONS ); #endif - while ( (i = getopt( argc, argv, "d:f:h:l:m:v:" )) != EOF ) { + 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++ ) { @@ -225,11 +263,17 @@ 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" ); +/* 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 ); @@ -259,14 +303,18 @@ main ( int argc, char **argv ) for ( i = optind; i < argc; i++ ) { char *s; int type; + char *localpart, *domainpart; + char address[1024]; - for ( j = 0; argv[i][j] != '\0'; j++ ) { - if ( argv[i][j] == '.' || argv[i][j] == '_' ) - argv[i][j] = ' '; - } +/* TBC: Make this processing optional */ +/* for ( j = 0; argv[i][j] != '\0'; j++ ) { */ +/* if ( argv[i][j] == '.' || argv[i][j] == '_' ) */ +/* argv[i][j] = ' '; */ +/* } */ type = USER; - if ( (s = strrchr( argv[i], '-' )) != NULL ) { + split_address( argv[i], &localpart, &domainpart ); + if ( (s = strrchr( localpart, '-' )) != NULL ) { s++; if ((strcasecmp(s, ERROR) == 0) || @@ -287,7 +335,15 @@ main ( int argc, char **argv ) } } - do_address( argv[i], &tolist, &numto, &togroups, &ngroups, + 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 ); } @@ -355,6 +411,264 @@ 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 ) ); + 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, "final" ) ) { + as->as_final = 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 ( !strncasecmp( p, "search-with-filter=", 19 ) ) { + as->as_syntax = AS_SYNTAX_BOOL_FILTER; + 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 { + syslog( LOG_ALERT, + "Missing filter in %s", s ); + exit( EX_TEMPFAIL ); + } + } else if ( !strcasecmp( p, "host" ) ) { + as->as_kind = AS_SYNTAX_HOST; + } else if ( !strcasecmp( p, "forward-to-host" ) ) { + as->as_kind = AS_KIND_FORWARD_TO_HOST; + } 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 { + 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 ) ) ) { + fprintf( stderr, "Read line %d:%s\n", lineno, line ); + 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 { + syslog( LOG_ALERT, + "Unparseable config definition at line %d", + lineno ); + exit( EX_TEMPFAIL ); + } + } + fclose( cf ); +} + static int connect_to_x500( void ) { @@ -365,8 +679,10 @@ 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); @@ -378,33 +694,114 @@ connect_to_x500( void ) return( 0 ); } -static int -mailcmp( char *a, char *b ) +static Group * +new_group( char *dn, Group **list, int *nlist ) { int i; + Group *this_group; - 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 ); - - default: - return( 1 ); - } + 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; } } - return( 0 ); + if ( *nlist == 0 ) { + *list = (Group *) malloc( sizeof(Group) ); + } else { + *list = (Group *) realloc( *list, (*nlist + 1) * + sizeof(Group) ); + } + + this_group = *list; + + (*list)[*nlist].g_errorsto = NULL; + (*list)[*nlist].g_members = NULL; + (*list)[*nlist].g_nmembers = 0; + /* save the group's dn so we can check for loops above */ + (*list)[*nlist].g_dn = strdup( dn ); + + (*nlist)++; + + return( this_group ); } static void -do_address( - char *name, +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, + "(objectclass=*)", 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, @@ -414,112 +811,132 @@ do_address( int type ) { - int rc, b, f, match; + LDAPURLDesc *ludp; + char *p, *s, *d; + int i; + char filter[1024]; + char realfilter[1024]; 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; - for ( b = 0, match = 0; !match && base[b].b_dn != NULL; b++ ) { - if ( ! (base[b].b_search & type) ) { - continue; - } - for ( f = 0; base[b].b_filter[f] != NULL; f++ ) { - char *format, *p, *s, *d; - char *argv[3]; - int argc; - - 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; + rc = ldap_url_parse( url, &ludp ); + if ( rc ) { + switch ( rc ) { + case LDAP_URL_ERR_NOTLDAP: + syslog( LOG_ALERT, + "Not an LDAP URL: %s", url ); + break; + case LDAP_URL_ERR_NODN: + syslog( LOG_ALERT, + "Missing DN in URL: %s", url ); + break; + case LDAP_URL_ERR_BADSCOPE: + syslog( LOG_ALERT, + "Scope string is invalid in URL: %s", url ); + break; + case LDAP_URL_ERR_MEM: + syslog( LOG_ALERT, + "Out of memory 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; + } - default: - syslog( LOG_ALERT, - "unknown format %c", *p ); + 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; } - - argc++; } - } - - /* 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++ = '\\'; + if ( substs[i].sub_char == '\0' ) { + syslog( LOG_ALERT, + "unknown format %c", *s ); } + } 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; + } + *d = *s; + } else { + strncpy( filter, ludp->lud_filter, sizeof( filter ) - 1 ); + filter[ sizeof( filter ) - 1 ] = '\0'; + } - ldap_msgfree( res ); + for ( s = filter, d = realfilter; *s; s++, d++ ) { + if ( *s == '*' ) { + *d++ = '\\'; } + *d = *s; + } + *d = '\0'; - if ( match ) - break; + 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, + realfilter, 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 ); } - /* 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 ); + 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; + return ( resolved ); } /* more than one match - bounce with ambiguous user? */ @@ -529,9 +946,9 @@ do_address( char **xdn; /* not giving rdn preference - bounce with ambiguous user */ - if ( base[b].b_rdnpref == 0 ) { - add_error( err, nerr, E_AMBIGUOUS, name, res ); - return; + if ( rdnpref == 0 ) { + add_error( err, nerr, E_AMBIGUOUS, address, res ); + return 0; } /* @@ -546,512 +963,362 @@ do_address( xdn = ldap_explode_dn( dn, 1 ); /* XXX bad, but how else can we do it? XXX */ - if ( strcasecmp( xdn[0], name ) == 0 ) { + 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, 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, name, tmpres ); - return; - - /* 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; - } - - /* - * 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. - */ - - /* 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 ( 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 ); - - } else if ( type == GROUP_MEMBERS ) { - /* sent to group-members - expand */ - do_group_members( e, dn, to, nto, togroups, ngroups, err, - nerr ); - - } else if ( type == GROUP_OWNER ) { - /* sent to group-owner - resend to owner attr */ - do_group_owner( e, dn, to, nto, err, nerr ); - - } else if ( isgroup( e ) ) { - /* - * sent to group - resend from [rfc822]ErrorsTo if it's there, - * otherwise, expand the group - */ - - do_group( e, dn, to, nto, togroups, ngroups, err, nerr ); - - 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 { - add_error( err, nerr, E_NOEMAIL, name, res ); - } - - /* - * 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 ( (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 ); - } - } - } - - if ( onvacation != NULL ) { - ldap_value_free( onvacation ); - } - if ( uid != NULL ) { - ldap_value_free( uid ); - } - free( dn ); -} - -static int -do_group( - LDAPMessage *e, - char *dn, - char ***to, - int *nto, - Group **togroups, - int *ngroups, - Error **err, - int *nerr -) -{ - int i; - char **moderator; - - /* - * 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. - */ - - 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 */ - } - - if (strcmp(MAIL500_BOUNCEFROM, mailfrom) != 0 && - has_attributes( e, "rfc822ErrorsTo", "errorsTo" ) ) { - add_group( dn, togroups, ngroups ); - - return( 0 ); - } - - do_group_members( e, dn, to, nto, togroups, ngroups, err, nerr ); - - return( 0 ); -} - -/* ARGSUSED */ -static void -do_group_members( - LDAPMessage *e, - char *dn, - char ***to, - int *nto, - Group **togroups, - int *ngroups, - Error **err, - int *nerr -) -{ - 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 ); - } - - if ( suppress ) { - ldap_value_free( suppress ); - } - 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; - } - ldap_value_free( joinable ); - - sprintf( filter, "(memberOfGroup=%s)", dn ); - - timeout.tv_sec = MAIL500_TIMEOUT; - timeout.tv_usec = 0; - - /* 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 ); - - if ( rc == LDAP_SIZELIMIT_EXCEEDED || - rc == LDAP_TIMELIMIT_EXCEEDED ) { - syslog( LOG_ALERT, - "group search limit exceeded %d", rc ); - unbind_and_exit( EX_TEMPFAIL ); - } - - if ( rc != LDAP_SUCCESS ) { - syslog( LOG_ALERT, "group search return 0x%x", - rc ); - unbind_and_exit( EX_TEMPFAIL ); - } - - /* 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 ); - } + } - free( ndn ); + ldap_value_free( xdn ); + free( dn ); + } - continue; - } + /* nothing matched by rdn - go ahead and bounce */ + if ( tmpres == NULL ) { + add_error( err, nerr, E_AMBIGUOUS, address, res ); + return 0; - /* add them to the to list */ - if ( (mail = ldap_get_values( ld, ee, "mail" )) - != NULL ) { - add_to( to, nto, mail ); + /* 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; - ldap_value_free( mail ); + /* trouble... */ + } else if ( match < 0 ) { + syslog( LOG_ALERT, "error parsing result from X.500" ); + unbind_and_exit( EX_TEMPFAIL ); + } - /* else generate a bounce */ - } else { - ndn = ldap_get_dn( ld, ee ); + /* otherwise one matched by rdn - send to it */ + ldap_msgfree( res ); + res = tmpres; - add_error( err, nerr, - E_JOINMEMBERNOEMAIL, ndn, NULL ); + /* trouble */ + if ( (e = ldap_first_entry( ld, res )) == NULL ) { + syslog( LOG_ALERT, "error parsing entry from X.500" ); + unbind_and_exit( EX_TEMPFAIL ); + } - free( ndn ); - } - } + 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 ); } - opt = MAIL500_MAXAMBIGUOUS; - ldap_set_option(ld, LDAP_OPT_SIZELIMIT, &opt); - } - - if ( ! anymembers ) { - add_error( err, nerr, E_NOMEMBERS, dn, NULL ); } + return( resolved ); } -static void -add_member( - char *gdn, - char *dn, +static int +url_list_search( + char **urllist, + char *address, + int multi_entry, char ***to, int *nto, Group **togroups, int *ngroups, Error **err, int *nerr, - char **suppress + int type ) { - char *ndn; - char **mail; - int rc; - LDAPMessage *res, *e; - struct timeval timeout; - - 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 ); - - 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 ); - } + int i; + int resolved = 0; - free( ndn ); + for ( i = 0; urllist[i]; i++ ) { - return; - } + if ( !strncasecmp( urllist[i], "mail:", 5 ) ) { + char *vals[2]; - /* send to the member's mail attribute */ - if ( (mail = ldap_get_values( ld, e, "mail" )) != NULL ) { - add_to( to, nto, mail ); + vals[0] = urllist[i] + 5; + vals[1] = NULL; + add_to( to, nto, vals ); + resolved = 1; - ldap_value_free( mail ); + } else if ( ldap_is_ldap_url( urllist[i] ) ) { - /* else generate a bounce */ - } else { - if ( suppress == NULL || strcasecmp( suppress[0], "FALSE" ) - == 0 ) { - add_error( err, nerr, E_MEMBERNOEMAIL, ndn, NULL ); + 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; } } - - free( ndn ); + return( resolved ); } -static void -do_group_request( +/* + * 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 *nerr, + int type ) { - char **requeststo; + char **vals; + int i; + int resolved = 0; + char ***current_to = to; + int *current_nto = nto; + Group *current_group = NULL; + char buf[1024]; + char *localpart, *domainpart; + Subst substs[2]; - if ( (requeststo = get_attributes_mail_dn( e, "rfc822RequestsTo", - "requestsTo" )) != NULL ) { - add_to( to, nto, requeststo ); + for ( i=0; attr_semantics[i] != NULL; i++ ) { + AttrSemantics *as = attr_semantics[i]; + int nent; - ldap_value_free( requeststo ); - } else { - add_error( err, nerr, E_NOREQUEST, dn, NULL ); + 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: + if ( ! ( type & ( USER | GROUP_MEMBERS ) ) ) + break; + switch ( as->as_syntax ) { + case AS_SYNTAX_RFC822: + add_to( current_to, current_nto, vals ); + resolved = 1; + break; + case AS_SYNTAX_RFC822_EXT: + add_to( current_to, current_nto, vals ); + 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 + */ + 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: + /* 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: + /* 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: + /* This is a group with special processing */ + if ( type & GROUP_REQUEST ) { + add_to( current_to, current_nto, vals ); + resolved = 1; + } + break; + + default: + syslog( LOG_ALERT, + "Invalid kind %d", as->as_kind ); + /* Error, TBC */ + } + ldap_value_free( vals ); + if ( as->as_final ) { + return( resolved ); + } } + return( resolved ); } -static void -do_group_errors( - LDAPMessage *e, - char *dn, +static int +search_bases( + char *filter, + Subst *substs, + char *name, char ***to, int *nto, + Group **togroups, + int *ngroups, Error **err, - int *nerr + int *nerr, + int type ) { - char **errorsto; + int b, resolved = 0; - if ( (errorsto = get_attributes_mail_dn( e, "rfc822ErrorsTo", - "errorsTo" )) != NULL ) { - add_to( to, nto, errorsto ); + for ( b = 0; base[b] != NULL; b++ ) { - ldap_value_free( errorsto ); - } else { - add_error( err, nerr, E_NOERRORS, dn, NULL ); + 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_group_owner( - LDAPMessage *e, - char *dn, +do_address( + char *name, char ***to, int *nto, + Group **togroups, + int *ngroups, Error **err, - int *nerr + int *nerr, + int type ) { - char **owner; + struct timeval timeout; + char *localpart, *domainpart; + int resolved; + Subst substs[5]; - 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 ); + /* + * 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 ); + timeout.tv_sec = MAIL500_TIMEOUT; + timeout.tv_usec = 0; + 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 = '\0'; + substs[4].sub_value = NULL; + + resolved = search_bases( NULL, substs, name, + to, nto, togroups, ngroups, + err, nerr, type ); + + 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 ); + } } } @@ -1417,11 +1684,14 @@ 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++ ) { @@ -1469,18 +1739,42 @@ 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; + int i, j; char **oclist; + if ( !groupclasses ) { + return( 0 ); + } + oclist = ldap_get_values( ld, e, "objectClass" ); for ( i = 0; oclist[i] != NULL; i++ ) { - if ( strcasecmp( oclist[i], "rfc822MailGroup" ) == 0 ) { - ldap_value_free( oclist ); - return( 1 ); + for ( j = 0; groupclasses[j] != NULL; j++ ) { + if ( strcasecmp( oclist[i], groupclasses[j] ) == 0 ) { + ldap_value_free( oclist ); + return( 1 ); + } } } ldap_value_free( oclist ); @@ -1503,52 +1797,6 @@ 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 ) { @@ -1560,128 +1808,6 @@ 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 ); -} -- 2.39.2