]> git.sur5r.net Git - openldap/blob - clients/mail500/main.c
Added support for doing routing at the MTA, that is the right thing
[openldap] / clients / mail500 / main.c
1 /* $OpenLDAP$ */
2 /*
3  * Copyright (c) 1990 Regents of the University of Michigan.
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms are permitted
7  * provided that this notice is preserved and that due credit is given
8  * to the University of Michigan at Ann Arbor. The name of the University
9  * may not be used to endorse or promote products derived from this
10  * software without specific prior written permission. This software
11  * is provided ``as is'' without express or implied warranty.
12  *
13  * Copyright 1998,1999 The OpenLDAP Foundation
14  * COPYING RESTRICTIONS APPLY.  See COPYRIGHT File in top level directory
15  * of this package for details.
16  */
17
18 #include "portable.h"
19
20 #include <stdio.h>
21
22 #include <ac/stdlib.h>
23
24 #include <ac/ctype.h>
25 #include <ac/param.h>
26 #include <ac/signal.h>
27 #include <ac/string.h>
28 #include <ac/sysexits.h>
29 #include <ac/syslog.h>
30 #include <ac/time.h>
31 #include <ac/unistd.h>
32 #include <ac/wait.h>
33
34 #include <sys/stat.h>
35
36 #ifdef HAVE_SYS_RESOURCE_H
37 #include <sys/resource.h>
38 #endif
39
40 #include <ldap.h>
41
42 #include "ldap_defaults.h"
43
44 #ifndef MAIL500_BOUNCEFROM
45 #define MAIL500_BOUNCEFROM "<>"
46 #endif
47
48 #define USER            0x01
49 #define GROUP_ERRORS    0x02
50 #define GROUP_REQUEST   0x04
51 #define GROUP_MEMBERS   0x08
52 #define GROUP_OWNER     0x10
53
54 #define ERROR           "error"
55 #define ERRORS          "errors"
56 #define REQUEST         "request"
57 #define REQUESTS        "requests"
58 #define MEMBERS         "members"
59 #define OWNER           "owner"
60 #define OWNERS          "owners"
61
62 LDAP    *ld;
63 char    *vacationhost = NULL;
64 char    *errorsfrom = MAIL500_BOUNCEFROM;
65 char    *mailfrom = NULL;
66 char    *host = NULL;
67 char    *ldaphost = NULL;
68 int     hostlen = 0;
69 int     debug;
70
71 typedef struct errs {
72         int             e_code;
73 #define E_USERUNKNOWN           1
74 #define E_AMBIGUOUS             2
75 #define E_NOEMAIL               3
76 #define E_NOREQUEST             4
77 #define E_NOERRORS              5
78 #define E_BADMEMBER             6
79 #define E_JOINMEMBERNOEMAIL     7
80 #define E_MEMBERNOEMAIL         8
81 #define E_LOOP                  9
82 #define E_NOMEMBERS             10
83 #define E_NOOWNER               11
84 #define E_GROUPUNKNOWN          12
85 #define E_NOOWNADDRESS          13
86         char            *e_addr;
87         union e_union_u {
88                 char            *e_u_loop;
89                 LDAPMessage     *e_u_msg;
90         } e_union;
91 #define e_msg   e_union.e_u_msg
92 #define e_loop  e_union.e_u_loop
93 } Error;
94
95 typedef struct groupto {
96         char    *g_dn;
97         char    *g_errorsto;
98         char    **g_members;
99         int     g_nmembers;
100 } Group;
101
102 typedef struct baseinfo {
103         char    *b_url;
104         int     b_m_entries;
105         char    b_rdnpref;      /* give rdn's preference when searching? */
106         int     b_search;       /* ORed with the type of thing the address */
107                                 /*  looks like (USER, GROUP_ERRORS, etc.)  */
108                                 /*  to see if this should be searched      */
109 } Base;
110
111 /*
112  * We should limit the search to objectclass=mailRecipient or
113  * objectclass=mailGroup.
114  */
115
116 /*
117 Base    base[] = {
118         {"dc=StlInter, dc=Net",
119                 0, 0xff,
120                 {"mail=%s", "mailAlternateAddress=%s", NULL}},
121         {NULL}
122 };
123 */
124
125 Base    **base = NULL;
126
127 char    *sendmailargs[] = { MAIL500_SENDMAIL, "-oMrLDAP", "-odi", "-oi", "-f", NULL, NULL };
128
129 typedef struct attr_semantics {
130         char    *as_name;
131         int     as_m_valued;    /* Is multivalued? */
132         int     as_priority;    /* Priority level of this attribut type */
133         int     as_syntax;      /* How to interpret values */
134         int     as_m_entries;   /* Can resolve to several entries? */
135         int     as_kind;        /* Recipient, sender, etc. */
136         char    *as_param;      /* Extra info for filters and things alike */
137 } AttrSemantics;
138
139 #define AS_SYNTAX_UNKNOWN       0
140 #define AS_SYNTAX_NATIVE_MB     1       /* Unqualified mailbox name */
141 #define AS_SYNTAX_RFC822        2       /* RFC822 mail address */
142 #define AS_SYNTAX_HOST          3
143 #define AS_SYNTAX_DN            4       /* A directory entry */
144 #define AS_SYNTAX_RFC822_EXT    5
145 #define AS_SYNTAX_URL           6       /* mailto: or ldap: URL */
146 #define AS_SYNTAX_BOOL_FILTER   7       /* For joinable, filter in as_param */
147 #define AS_SYNTAX_PRESENT       8       /* Value irrelevant, only presence is
148                                          * considered. */
149
150 #define AS_KIND_UNKNOWN         0
151 #define AS_KIND_RECIPIENT       1
152 #define AS_KIND_ERRORS          2       /* For ErrorsTo and similar */
153 #define AS_KIND_REQUEST         3
154 #define AS_KIND_OWNER           4
155 #define AS_KIND_ROUTE_TO_HOST   5       /* Expand at some other host */
156 #define AS_KIND_ALLOWED_SENDER  6       /* Can send to group */
157 #define AS_KIND_MODERATOR       7
158 #define AS_KIND_ROUTE_TO_ADDR   8       /* Rewrite recipient address as */
159 #define AS_KIND_OWN_ADDR        9       /* RFC822 name of this entry */
160
161 AttrSemantics **attr_semantics = NULL;
162 int current_priority = 0;
163
164 typedef struct subst {
165         char    sub_char;
166         char    *sub_value;
167 } Subst;
168
169 char    **groupclasses = NULL;
170 char    **def_attr = NULL;
171 char    **mydomains = NULL;             /* FQDNs not to route elsewhere */
172
173 static void load_config( char *filespec );
174 static void split_address( char *address, char **localpart, char **domainpart);
175 static int entry_engine( LDAPMessage *e, char *dn, char *address, char ***to, int *nto, Group ***togroups, int *ngroups, Error **err, int *nerr, int type );
176 static void do_address( char *name, char ***to, int *nto, Group ***togroups, int *ngroups, Error **err, int *nerr, int type );
177 static void send_message( char **to );
178 static void send_errors( Error *err, int nerr );
179 static void do_noemail( FILE *fp, Error *err, int namelen );
180 static void do_ambiguous( FILE *fp, Error *err, int namelen );
181 static int count_values( char **list );
182 static void add_to( char ***list, int *nlist, char **new );
183 static void add_single_to( char ***list, char *new );
184 static int  isgroup( LDAPMessage *e );
185 static void add_error( Error **err, int *nerr, int code, char *addr, LDAPMessage *msg );
186 static void unbind_and_exit( int rc ) LDAP_GCCATTR((noreturn));
187 static void send_group( Group **group, int ngroup );
188
189 static int  connect_to_x500( void );
190
191
192 int
193 main ( int argc, char **argv )
194 {
195         char            *myname;
196         char            **tolist;
197         Error           *errlist;
198         Group           **togroups;
199         int             numto, ngroups, numerr, nargs;
200         int             i, j;
201         char            *conffile = NULL;
202
203         if ( (myname = strrchr( argv[0], '/' )) == NULL )
204                 myname = strdup( argv[0] );
205         else
206                 myname = strdup( myname + 1 );
207
208 #ifdef SIGPIPE
209         (void) SIGNAL( SIGPIPE, SIG_IGN );
210 #endif
211
212 #ifdef LOG_MAIL
213         openlog( myname, OPENLOG_OPTIONS, LOG_MAIL );
214 #elif LOG_DEBUG
215         openlog( myname, OPENLOG_OPTIONS );
216 #endif
217
218         while ( (i = getopt( argc, argv, "d:C:f:h:l:m:v:" )) != EOF ) {
219                 switch( i ) {
220                 case 'd':       /* turn on debugging */
221                         debug = atoi( optarg );
222                         break;
223
224                 case 'C':       /* path to configuration file */
225                         conffile = strdup( optarg );
226                         break;
227
228                 case 'f':       /* who it's from & where errors should go */
229                         mailfrom = strdup( optarg );
230                         for ( j = 0; sendmailargs[j] != NULL; j++ ) {
231                                 if ( strcmp( sendmailargs[j], "-f" ) == 0 ) {
232                                         sendmailargs[j+1] = mailfrom;
233                                         break;
234                                 }
235                         }
236                         break;
237
238                 case 'h':       /* hostname */
239                         host = strdup( optarg );
240                         hostlen = strlen(host);
241                         break;
242
243                 case 'l':       /* ldap host */
244                         ldaphost = strdup( optarg );
245                         break;
246
247                                 /* mailer-daemon address - who we should */
248                 case 'm':       /* say errors come from */
249                         errorsfrom = strdup( optarg );
250                         break;
251
252                 case 'v':       /* vacation host */
253                         vacationhost = strdup( optarg );
254                         break;
255
256                 default:
257                         syslog( LOG_ALERT, "unknown option" );
258                         break;
259                 }
260         }
261
262         if ( mailfrom == NULL ) {
263                 syslog( LOG_ALERT, "required argument -f not present" );
264                 exit( EX_TEMPFAIL );
265         }
266         if ( errorsfrom == NULL ) {
267                 syslog( LOG_ALERT, "required argument -m not present" );
268                 exit( EX_TEMPFAIL );
269         }
270 /*      if ( host == NULL ) { */
271 /*              syslog( LOG_ALERT, "required argument -h not present" ); */
272 /*              exit( EX_TEMPFAIL ); */
273 /*      } */
274         if ( conffile == NULL ) {
275                 syslog( LOG_ALERT, "required argument -C not present" );
276                 exit( EX_TEMPFAIL );
277         }
278
279         load_config( conffile );
280
281         if ( connect_to_x500() != 0 )
282                 exit( EX_TEMPFAIL );
283
284         setuid( geteuid() );
285
286         if ( debug ) {
287                 char    buf[1024];
288                 int     i;
289
290                 syslog( LOG_ALERT, "running as %d", geteuid() );
291                 strcpy( buf, argv[0] );
292                 for ( i = 1; i < argc; i++ ) {
293                         strcat( buf, " " );
294                         strcat( buf, argv[i] );
295                 }
296
297                 syslog( LOG_ALERT, "args: (%s)", buf );
298         }
299
300         tolist = NULL;
301         numto = 0;
302         add_to( &tolist, &numto, sendmailargs );
303         nargs = numto;
304         ngroups = numerr = 0;
305         togroups = NULL;
306         errlist = NULL;
307         for ( i = optind; i < argc; i++ ) {
308                 char    *s;
309                 int     type;
310                 char    *localpart, *domainpart;
311                 char    address[1024];
312
313 /*  TBC: Make this processing optional */
314 /*              for ( j = 0; argv[i][j] != '\0'; j++ ) { */
315 /*                      if ( argv[i][j] == '.' || argv[i][j] == '_' ) */
316 /*                              argv[i][j] = ' '; */
317 /*              } */
318
319                 type = USER;
320                 split_address( argv[i], &localpart, &domainpart );
321                 if ( (s = strrchr( localpart, '-' )) != NULL ) {
322                         s++;
323
324                         if ((strcasecmp(s, ERROR) == 0) ||
325                                 (strcasecmp(s, ERRORS) == 0)) {
326                                 type = GROUP_ERRORS;
327                                 *(--s) = '\0';
328                         } else if ((strcasecmp(s, REQUEST) == 0) ||
329                                 (strcasecmp(s, REQUESTS) == 0)) {
330                                 type = GROUP_REQUEST;
331                                 *(--s) = '\0';
332                         } else if ( strcasecmp( s, MEMBERS ) == 0 ) {
333                                 type = GROUP_MEMBERS;
334                                 *(--s) = '\0';
335                         } else if ((strcasecmp(s, OWNER) == 0) ||
336                                 (strcasecmp(s, OWNERS) == 0)) {
337                                 type = GROUP_OWNER;
338                                 *(--s) = '\0';
339                         }
340                 }
341
342                 if ( domainpart ) {
343                         sprintf( address, "%s@%s", localpart, domainpart );
344                         free( localpart );
345                         free( domainpart );
346                 } else {
347                         sprintf( address, "%s@%s", localpart, domainpart );
348                         free( localpart );
349                 }
350                 do_address( address, &tolist, &numto, &togroups, &ngroups,
351                     &errlist, &numerr, type );
352         }
353
354         /*
355          * If we have both errors and successful deliveries to make or if
356          * if there are any groups to deliver to, we basically need to read
357          * the message twice.  So, we have to put it in a tmp file.
358          */
359
360         if ( numerr > 0 && numto > nargs || ngroups > 0 ) {
361                 FILE    *fp;
362                 char    buf[BUFSIZ];
363
364                 umask( 077 );
365                 if ( (fp = tmpfile()) == NULL ) {
366                         syslog( LOG_ALERT, "could not open tmp file" );
367                         unbind_and_exit( EX_TEMPFAIL );
368                 }
369
370                 /* copy the message to a temp file */
371                 while ( fgets( buf, sizeof(buf), stdin ) != NULL ) {
372                         if ( fputs( buf, fp ) == EOF ) {
373                                 syslog( LOG_ALERT, "error writing tmpfile" );
374                                 unbind_and_exit( EX_TEMPFAIL );
375                         }
376                 }
377
378                 if ( dup2( fileno( fp ), 0 ) == -1 ) {
379                         syslog( LOG_ALERT, "could not dup2 tmpfile" );
380                         unbind_and_exit( EX_TEMPFAIL );
381                 }
382
383                 fclose( fp );
384         }
385
386         /* deal with errors */
387         if ( numerr > 0 ) {
388                 if ( debug ) {
389                         syslog( LOG_ALERT, "sending errors" );
390                 }
391                 (void) rewind( stdin );
392                 send_errors( errlist, numerr );
393         }
394
395         (void) ldap_unbind( ld );
396
397         /* send to groups with errorsTo */
398         if ( ngroups > 0 ) {
399                 if ( debug ) {
400                         syslog( LOG_ALERT, "sending to groups with errorsto" );
401                 }
402                 (void) rewind( stdin );
403                 send_group( togroups, ngroups );
404         }
405
406         /* send to expanded aliases and groups w/o errorsTo */
407         if ( numto > nargs ) {
408                 if ( debug ) {
409                         syslog( LOG_ALERT, "sending to aliases and groups" );
410                 }
411                 (void) rewind( stdin );
412                 send_message( tolist );
413         }
414
415         return( EX_OK );
416 }
417
418 static char *
419 get_config_line( FILE *cf, int *lineno)
420 {
421         static char     buf[2048];
422         int             len;
423         int             pos;
424         int             room;
425
426         pos = 0;
427         room = sizeof( buf );
428         while ( fgets( &buf[pos], room, cf ) ) {
429                 (*lineno)++;
430                 if ( pos > 0 ) {
431                         /* Delete whitespace at the beginning of new data */
432                         if ( isspace( buf[pos] ) ) {
433                                 char *s, *d;
434                                 for ( s = buf+pos; isspace(*s); s++ )
435                                         ;
436                                 for ( d = buf+pos; *s; s++, d++ ) {
437                                         *d = *s;
438                                 }
439                                 *d = *s;
440                         }
441                 }
442                 len = strlen( buf );
443                 if ( buf[len-1] != '\n' ) {
444                         syslog( LOG_ALERT, "Definition too long at line %d",
445                                 *lineno );
446                         exit( EX_TEMPFAIL );
447                 }
448                 if ( buf[0] == '#' )
449                         continue;
450                 if ( strspn( buf, " \t\n" ) == len )
451                         continue;
452                 if ( buf[len-2] == '\\' ) {
453                         pos = len - 2;
454                         room = sizeof(buf) - pos;
455                         continue;
456                 }
457                 /* We have a real line, we will exit the loop */
458                 buf[len-1] = '\0';
459                 return( buf );
460         }
461         return( NULL );
462 }
463
464 static void
465 add_url ( char *url, int rdnpref, int typemask )
466 {
467         Base            **list_temp;
468         int             size;
469         Base            *b;
470
471         b = calloc(1, sizeof(Base));
472         if ( !b ) {
473                 syslog( LOG_ALERT, "Out of memory" );
474                 exit( EX_TEMPFAIL );
475         }
476         b->b_url = strdup( url );
477         b->b_rdnpref = rdnpref;
478         b->b_search   = typemask;
479
480         if ( base == NULL ) {
481                 base = calloc(2, sizeof(LDAPURLDesc *));
482                 if ( !base ) {
483                         syslog( LOG_ALERT, "Out of memory" );
484                         exit( EX_TEMPFAIL );
485                 }
486                 base[0] = b;
487         } else {
488                 for ( size = 0; base[size]; size++ )
489                         ;
490                 size += 2;
491                 list_temp = realloc( base, size*sizeof(LDAPURLDesc *) );
492                 if ( !list_temp ) {
493                         syslog( LOG_ALERT, "Out of memory" );
494                         exit( EX_TEMPFAIL );
495                 }
496                 base = list_temp;
497                 base[size-2] = b;
498                 base[size-1] = NULL;
499         }
500 }
501
502 static void
503 add_def_attr( char *s )
504 {
505         char *p, *q;
506
507         p = s;
508         while ( *p ) {
509                 p += strspn( p, "\t," );
510                 q = strpbrk( p, " \t," );
511                 if ( q ) {
512                         *q = '\0';
513                         add_single_to( &def_attr, p );
514                 } else {
515                         add_single_to( &def_attr, p );
516                         break;
517                 }
518                 p = q + 1;
519         }
520 }
521
522 static void
523 add_attr_semantics( char *s )
524 {
525         char *p, *q;
526         AttrSemantics *as;
527
528         as = calloc( 1, sizeof( AttrSemantics ) );
529         as->as_priority = current_priority;
530         p = s;
531         while ( isspace ( *p ) )
532                 p++;
533         q = p;
534         while ( !isspace ( *q ) && *q != '\0' )
535                 q++;
536         *q = '\0';
537         as->as_name = strdup( p );
538         p = q + 1;
539
540         while ( *p ) {
541                 while ( isspace ( *p ) )
542                         p++;
543                 q = p;
544                 while ( !isspace ( *q ) && *q != '\0' )
545                         q++;
546                 *q = '\0';
547                 if ( !strcasecmp( p, "multivalued" ) ) {
548                         as->as_m_valued = 1;
549                 } else if ( !strcasecmp( p, "multiple-entries" ) ) {
550                         as->as_m_entries = 1;
551                 } else if ( !strcasecmp( p, "local-native-mailbox" ) ) {
552                         as->as_syntax = AS_SYNTAX_NATIVE_MB;
553                 } else if ( !strcasecmp( p, "rfc822" ) ) {
554                         as->as_syntax = AS_SYNTAX_RFC822;
555                 } else if ( !strcasecmp( p, "rfc822-extended" ) ) {
556                         as->as_syntax = AS_SYNTAX_RFC822_EXT;
557                 } else if ( !strcasecmp( p, "dn" ) ) {
558                         as->as_syntax = AS_SYNTAX_DN;
559                 } else if ( !strcasecmp( p, "url" ) ) {
560                         as->as_syntax = AS_SYNTAX_URL;
561                 } else if ( !strncasecmp( p, "search-with-filter=", 19 ) ) {
562                         as->as_syntax = AS_SYNTAX_BOOL_FILTER;
563                         q = strchr( p, '=' );
564                         if ( q ) {
565                                 p = q + 1;
566                                 while ( *q && !isspace( *q ) ) {
567                                         q++;
568                                 }
569                                 if ( *q ) {
570                                         *q = '\0';
571                                         as->as_param = strdup( p );
572                                         p = q + 1;
573                                 } else {
574                                         as->as_param = strdup( p );
575                                         p = q;
576                                 }
577                         } else {
578                                 syslog( LOG_ALERT,
579                                         "Missing filter in %s", s );
580                                 exit( EX_TEMPFAIL );
581                         }
582                 } else if ( !strcasecmp( p, "host" ) ) {
583                         as->as_kind = AS_SYNTAX_HOST;
584                 } else if ( !strcasecmp( p, "present" ) ) {
585                         as->as_kind = AS_SYNTAX_PRESENT;
586                 } else if ( !strcasecmp( p, "route-to-host" ) ) {
587                         as->as_kind = AS_KIND_ROUTE_TO_HOST;
588                 } else if ( !strcasecmp( p, "route-to-address" ) ) {
589                         as->as_kind = AS_KIND_ROUTE_TO_ADDR;
590                 } else if ( !strcasecmp( p, "own-address" ) ) {
591                         as->as_kind = AS_KIND_OWN_ADDR;
592                 } else if ( !strcasecmp( p, "recipient" ) ) {
593                         as->as_kind = AS_KIND_RECIPIENT;
594                 } else if ( !strcasecmp( p, "errors" ) ) {
595                         as->as_kind = AS_KIND_ERRORS;
596                 } else if ( !strcasecmp( p, "request" ) ) {
597                         as->as_kind = AS_KIND_REQUEST;
598                 } else if ( !strcasecmp( p, "owner" ) ) {
599                         as->as_kind = AS_KIND_OWNER;
600                 } else {
601                         syslog( LOG_ALERT,
602                                 "Unknown semantics word %s", p );
603                         exit( EX_TEMPFAIL );
604                 }
605                 p = q + 1;
606         }
607         if ( attr_semantics == NULL ) {
608                 attr_semantics = calloc(2, sizeof(AttrSemantics *));
609                 if ( !attr_semantics ) {
610                         syslog( LOG_ALERT, "Out of memory" );
611                         exit( EX_TEMPFAIL );
612                 }
613                 attr_semantics[0] = as;
614         } else {
615                 int size;
616                 AttrSemantics **list_temp;
617                 for ( size = 0; attr_semantics[size]; size++ )
618                         ;
619                 size += 2;
620                 list_temp = realloc( attr_semantics,
621                                      size*sizeof(AttrSemantics *) );
622                 if ( !list_temp ) {
623                         syslog( LOG_ALERT, "Out of memory" );
624                         exit( EX_TEMPFAIL );
625                 }
626                 attr_semantics = list_temp;
627                 attr_semantics[size-2] = as;
628                 attr_semantics[size-1] = NULL;
629         }
630 }
631
632 static void
633 load_config( char *filespec )
634 {
635         FILE            *cf;
636         char            *line;
637         int             lineno = 0;
638         char            *p;
639         int             rdnpref;
640         int             typemask;
641
642         cf = fopen( filespec, "r" );
643         if ( !cf ) {
644                 perror( "Opening config file" );
645                 exit( EX_TEMPFAIL );
646         }
647
648         while ( ( line = get_config_line( cf,&lineno ) ) ) {
649                 p = strpbrk( line, " \t" );
650                 if ( !p ) {
651                         syslog( LOG_ALERT,
652                                 "Missing space at line %d", lineno );
653                         exit( EX_TEMPFAIL );
654                 }
655                 if ( !strncmp( line, "search", p-line ) ) {
656                         p += strspn( p, " \t" );
657                         /* TBC, get these */
658                         rdnpref = 0;
659                         typemask = 0xFF;
660                         add_url( p, rdnpref, typemask );
661                 } else if ( !strncmp(line, "attribute", p-line) ) {
662                         p += strspn(p, " \t");
663                         add_attr_semantics( p );
664                 } else if ( !strncmp(line, "default-attributes", p-line) ) {
665                         p += strspn(p, " \t");
666                         add_def_attr( p );
667                 } else if ( !strncmp(line, "group-classes", p-line) ) {
668                         p += strspn(p, " \t");
669                         add_single_to( &groupclasses, p );
670                 } else if ( !strncmp(line, "priority", p-line) ) {
671                         p += strspn(p, " \t");
672                         current_priority = atoi(p);
673                 } else if ( !strncmp(line, "domain", p-line) ) {
674                         p += strspn(p, " \t");
675                         add_single_to( &mydomains, p );
676                 } else {
677                         syslog( LOG_ALERT,
678                                 "Unparseable config definition at line %d",
679                                 lineno );
680                         exit( EX_TEMPFAIL );
681                 }
682         }
683         fclose( cf );
684 }
685
686 static int
687 connect_to_x500( void )
688 {
689         int opt;
690
691         if ( (ld = ldap_init( ldaphost, 0 )) == NULL ) {
692                 syslog( LOG_ALERT, "ldap_init failed" );
693                 return( -1 );
694         }
695
696         /*  TBC: Set this only when it makes sense
697         opt = MAIL500_MAXAMBIGUOUS;
698         ldap_set_option(ld, LDAP_OPT_SIZELIMIT, &opt);
699         */
700         opt = LDAP_DEREF_ALWAYS;
701         ldap_set_option(ld, LDAP_OPT_DEREF, &opt);
702
703         if ( ldap_simple_bind_s( ld, NULL, NULL ) != LDAP_SUCCESS ) {
704                 syslog( LOG_ALERT, "ldap_simple_bind_s failed" );
705                 return( -1 );
706         }
707
708         return( 0 );
709 }
710
711 static Group *
712 new_group( char *dn, Group ***list, int *nlist )
713 {
714         int     i;
715         Group   *this_group;
716
717         for ( i = 0; i < *nlist; i++ ) {
718                 if ( strcmp( dn, (*list)[i]->g_dn ) == 0 ) {
719                         syslog( LOG_ALERT, "group loop 2 detected (%s)", dn );
720                         return NULL;
721                 }
722         }
723
724         this_group = (Group *) malloc( sizeof(Group) );
725
726         if ( *nlist == 0 ) {
727                 *list = (Group **) malloc( sizeof(Group *) );
728         } else {
729                 *list = (Group **) realloc( *list, (*nlist + 1) *
730                     sizeof(Group *) );
731         }
732
733         this_group->g_errorsto = NULL;
734         this_group->g_members = NULL;
735         this_group->g_nmembers = 0;
736         /* save the group's dn so we can check for loops above */
737         this_group->g_dn = strdup( dn );
738
739         (*list)[*nlist] = this_group;
740         (*nlist)++;
741
742         return( this_group );
743 }
744
745 static void
746 split_address(
747         char    *address,
748         char    **localpart,
749         char    **domainpart
750 )
751 {
752         char            *p;
753
754         if ( ( p = strrchr( address, '@' ) ) == NULL ) {
755                 *localpart = strdup( address );
756                 *domainpart = NULL;
757         } else {
758                 *localpart = malloc( p - address + 1 );
759                 strncpy( *localpart, address, p - address );
760                 (*localpart)[p - address] = '\0';
761                 p++;
762                 *domainpart = strdup( p );
763         }
764 }
765
766 static int
767 dn_search(
768         char    **dnlist, 
769         char    *address,
770         char    ***to,
771         int     *nto,
772         Group   ***togroups,
773         int     *ngroups,
774         Error   **err,
775         int     *nerr
776 )
777 {
778         int             rc;
779         int             i;
780         int             resolved = 0;
781         LDAPMessage     *res, *e;
782         struct timeval  timeout;
783
784         timeout.tv_sec = MAIL500_TIMEOUT;
785         timeout.tv_usec = 0;
786         for ( i = 0; dnlist[i]; i++ ) {
787                 if ( (rc = ldap_search_st( ld, dnlist[i], LDAP_SCOPE_BASE,
788                         "(objectclass=*)", def_attr, 0,
789                          &timeout, &res )) != LDAP_SUCCESS ) {
790                         if ( rc == LDAP_NO_SUCH_OBJECT ) {
791                                 add_error( err, nerr, E_BADMEMBER, dnlist[i], NULL );
792                                 continue;
793                         } else {
794                                 syslog( LOG_ALERT, "member search return 0x%x", rc );
795
796                                 unbind_and_exit( EX_TEMPFAIL );
797                         }
798                 } else {
799                         if ( (e = ldap_first_entry( ld, res )) == NULL ) {
800                                 syslog( LOG_ALERT, "member search error parsing entry" );
801                                 unbind_and_exit( EX_TEMPFAIL );
802                         }
803                         if ( entry_engine( e, dnlist[i], address, to, nto,
804                                            togroups, ngroups, err, nerr,
805                                            USER | GROUP_MEMBERS ) ) {
806                                 resolved = 1;
807                         }
808                 }
809         }
810         return( resolved );
811 }
812
813 static int
814 search_ldap_url(
815         char    *url,
816         Subst   *substs,
817         char    *address,
818         int     rdnpref,
819         int     multi_entry,
820         char    ***to,
821         int     *nto,
822         Group   ***togroups,
823         int     *ngroups,
824         Error   **err,
825         int     *nerr,
826         int     type
827 )
828 {
829         LDAPURLDesc     *ludp;
830         char            *p, *s, *d;
831         int             i;
832         char            filter[1024];
833         LDAPMessage     *e, *res;
834         int             rc;
835         char            **attrlist;
836         struct timeval  timeout;
837         int             match;
838         int             resolved = 0;
839         char            *dn;
840
841         timeout.tv_sec = MAIL500_TIMEOUT;
842         timeout.tv_usec = 0;
843
844         rc = ldap_url_parse( url, &ludp );
845         if ( rc ) {
846                 switch ( rc ) {
847                 case LDAP_URL_ERR_NOTLDAP:
848                         syslog( LOG_ALERT,
849                                 "Not an LDAP URL: %s", url );
850                         break;
851                 case LDAP_URL_ERR_BADENCLOSURE:
852                         syslog( LOG_ALERT,
853                                 "Bad Enclosure in URL: %s", url );
854                         break;
855                 case LDAP_URL_ERR_BADURL:
856                         syslog( LOG_ALERT,
857                                 "Bad URL: %s", url );
858                         break;
859                 case LDAP_URL_ERR_BADHOST:
860                         syslog( LOG_ALERT,
861                                 "Host is invalid in URL: %s", url );
862                         break;
863                 case LDAP_URL_ERR_BADATTRS:
864                         syslog( LOG_ALERT,
865                                 "Attributes are invalid in URL: %s", url );
866                         break;
867                 case LDAP_URL_ERR_BADSCOPE:
868                         syslog( LOG_ALERT,
869                                 "Scope is invalid in URL: %s", url );
870                         break;
871                 case LDAP_URL_ERR_BADFILTER:
872                         syslog( LOG_ALERT,
873                                 "Filter is invalid in URL: %s", url );
874                         break;
875                 case LDAP_URL_ERR_BADEXTS:
876                         syslog( LOG_ALERT,
877                                 "Extensions are invalid in URL: %s", url );
878                         break;
879                 case LDAP_URL_ERR_MEM:
880                         syslog( LOG_ALERT,
881                                 "Out of memory parsing URL: %s", url );
882                         break;
883                 case LDAP_URL_ERR_PARAM:
884                         syslog( LOG_ALERT,
885                                 "bad parameter parsing URL: %s", url );
886                         break;
887                 default:
888                         syslog( LOG_ALERT,
889                                 "Unknown error %d parsing URL: %s",
890                                 rc, url );
891                         break;
892                 }
893                 add_error( err, nerr, E_BADMEMBER,
894                            url, NULL );
895                 return 0;
896         }
897
898         if ( substs ) {
899                 for ( s = ludp->lud_filter, d = filter; *s; s++,d++ ) {
900                         if ( *s == '%' ) {
901                                 s++;
902                                 if ( *s == '%' ) {
903                                         *d = '%';
904                                         continue;
905                                 }
906                                 for ( i = 0; substs[i].sub_char != '\0';
907                                       i++ ) {
908                                         if ( *s == substs[i].sub_char ) {
909                                                 for ( p = substs[i].sub_value;
910                                                       *p; p++,d++ ) {
911                                                         *d = *p;
912                                                 }
913                                                 d--;
914                                                 break;
915                                         }
916                                 }
917                                 if ( substs[i].sub_char == '\0' ) {
918                                         syslog( LOG_ALERT,
919                                                 "unknown format %c", *s );
920                                 }
921                         } else {
922                                 *d = *s;
923                         }
924                 }
925                 *d = *s;
926         } else {
927                 strncpy( filter, ludp->lud_filter, sizeof( filter ) - 1 );
928                 filter[ sizeof( filter ) - 1 ] = '\0';
929         }
930
931         if ( ludp->lud_attrs ) {
932                 attrlist = ludp->lud_attrs;
933         } else {
934                 attrlist = def_attr;
935         }
936         res = NULL;
937         /* TBC: we don't read the host, dammit */
938         rc = ldap_search_st( ld, ludp->lud_dn, ludp->lud_scope,
939                              filter, attrlist, 0,
940                              &timeout, &res );
941
942         /* some other trouble - try again later */
943         if ( rc != LDAP_SUCCESS &&
944              rc != LDAP_SIZELIMIT_EXCEEDED ) {
945                 syslog( LOG_ALERT, "return 0x%x from X.500",
946                         rc );
947                 unbind_and_exit( EX_TEMPFAIL );
948         }
949
950         match = ldap_count_entries( ld, res );
951
952         /* trouble - try again later */
953         if ( match == -1 ) {
954                 syslog( LOG_ALERT, "error parsing result from X.500" );
955                 unbind_and_exit( EX_TEMPFAIL );
956         }
957
958         if ( match == 1 || multi_entry ) {
959                 for ( e = ldap_first_entry( ld, res ); e != NULL;
960                       e = ldap_next_entry( ld, e ) ) {
961                         dn = ldap_get_dn( ld, e );
962                         resolved = entry_engine( e, dn, address, to, nto,
963                                                  togroups, ngroups,
964                                                  err, nerr, type );
965                         if ( !resolved ) {
966                                 add_error( err, nerr, E_NOEMAIL, address, res );
967                         }
968                 }
969                 return ( resolved );
970         }
971
972         /* more than one match - bounce with ambiguous user? */
973         if ( match > 1 ) {
974                 LDAPMessage     *next, *tmpres = NULL;
975                 char            *dn;
976                 char            **xdn;
977
978                 /* not giving rdn preference - bounce with ambiguous user */
979                 if ( rdnpref == 0 ) {
980                         add_error( err, nerr, E_AMBIGUOUS, address, res );
981                         return 0;
982                 }
983
984                 /*
985                  * giving rdn preference - see if any entries were matched
986                  * because of their rdn.  If so, collect them to deal with
987                  * later (== 1 we deliver, > 1 we bounce).
988                  */
989
990                 for ( e = ldap_first_entry( ld, res ); e != NULL; e = next ) {
991                         next = ldap_next_entry( ld, e );
992                         dn = ldap_get_dn( ld, e );
993                         xdn = ldap_explode_dn( dn, 1 );
994
995                         /* XXX bad, but how else can we do it? XXX */
996                         if ( strcasecmp( xdn[0], address ) == 0 ) {
997                                 ldap_delete_result_entry( &res, e );
998                                 ldap_add_result_entry( &tmpres, e );
999                         }
1000
1001                         ldap_value_free( xdn );
1002                         free( dn );
1003                 }
1004
1005                 /* nothing matched by rdn - go ahead and bounce */
1006                 if ( tmpres == NULL ) {
1007                         add_error( err, nerr, E_AMBIGUOUS, address, res );
1008                         return 0;
1009
1010                 /* more than one matched by rdn - bounce with rdn matches */
1011                 } else if ( (match = ldap_count_entries( ld, tmpres )) > 1 ) {
1012                         add_error( err, nerr, E_AMBIGUOUS, address, tmpres );
1013                         return 0;
1014
1015                 /* trouble... */
1016                 } else if ( match < 0 ) {
1017                         syslog( LOG_ALERT, "error parsing result from X.500" );
1018                         unbind_and_exit( EX_TEMPFAIL );
1019                 }
1020
1021                 /* otherwise one matched by rdn - send to it */
1022                 ldap_msgfree( res );
1023                 res = tmpres;
1024
1025                 /* trouble */
1026                 if ( (e = ldap_first_entry( ld, res )) == NULL ) {
1027                         syslog( LOG_ALERT, "error parsing entry from X.500" );
1028                         unbind_and_exit( EX_TEMPFAIL );
1029                 }
1030
1031                 dn = ldap_get_dn( ld, e );
1032
1033                 resolved = entry_engine( e, dn, address, to, nto,
1034                                          togroups, ngroups,
1035                                          err, nerr, type );
1036                 if ( !resolved ) {
1037                         add_error( err, nerr, E_NOEMAIL, address, res );
1038                         /* Don't free res if we passed it to add_error */
1039                 } else {
1040                         ldap_msgfree( res );
1041                 }
1042         }
1043         return( resolved );
1044 }
1045
1046 static int
1047 url_list_search(
1048         char    **urllist, 
1049         char    *address,
1050         int     multi_entry,
1051         char    ***to,
1052         int     *nto,
1053         Group   ***togroups,
1054         int     *ngroups,
1055         Error   **err,
1056         int     *nerr,
1057         int     type
1058 )
1059 {
1060         int             i;
1061         int             resolved = 0;
1062
1063         for ( i = 0; urllist[i]; i++ ) {
1064
1065                 if ( !strncasecmp( urllist[i], "mail:", 5 ) ) {
1066                         char    *vals[2];
1067
1068                         vals[0] = urllist[i] + 5;
1069                         vals[1] = NULL;
1070                         add_to( to, nto, vals );
1071                         resolved = 1;
1072
1073                 } else if ( ldap_is_ldap_url( urllist[i] ) ) {
1074
1075                         resolved = search_ldap_url( urllist[i], NULL,
1076                                                     address, 0, multi_entry,
1077                                                     to, nto, togroups, ngroups,
1078                                                     err, nerr, type );
1079                 } else {
1080                         /* Produce some sensible error here */
1081                         resolved = 0;
1082                 }
1083         }
1084         return( resolved );
1085 }
1086
1087 /*
1088  * We should probably take MX records into account to cover all bases,
1089  * but really, routing belongs in the MTA.
1090  */
1091 static int
1092 is_my_domain(
1093         char * domain
1094 )
1095 {
1096         char **d;
1097
1098         if ( d == NULL )
1099                 return 0;
1100         for ( d = mydomains; *d; d++ ) {
1101                 if ( !strcasecmp(*d,domain) ) {
1102                         return 1;
1103                 }
1104         }
1105         return 0;
1106 }
1107
1108 /*
1109  * The entry engine processes an entry.  Normally, each entry will resolve
1110  * to one or more values that will be added to the 'to' argument.  This
1111  * argument needs not be the global 'to' list, it may be the g_to field
1112  * in a group.  Groups have no special treatment, unless they require
1113  * a special sender.
1114  */
1115
1116 static int
1117 entry_engine(
1118         LDAPMessage *e,
1119         char    *dn,
1120         char    *address,
1121         char    ***to,
1122         int     *nto,
1123         Group   ***togroups,
1124         int     *ngroups,
1125         Error   **err,
1126         int     *nerr,
1127         int     type
1128 )
1129 {
1130         char    **vals;
1131         int     i;
1132         int     resolved = 0;
1133         char    ***current_to = to;
1134         int     *current_nto = nto;
1135         Group   *current_group = NULL;
1136         char    buf[1024];
1137         char    *localpart, *domainpart;
1138         Subst   substs[2];
1139         int     cur_priority = 0;
1140         char    *route_to_host = NULL;
1141         char    *route_to_address = NULL;
1142         int     needs_mta_routing = 0;
1143         char    **own_addresses = NULL;
1144         int     own_addresses_total = 0;
1145         char    *nvals[2];
1146
1147         for ( i=0; attr_semantics[i] != NULL; i++ ) {
1148                 AttrSemantics   *as = attr_semantics[i];
1149                 int             nent;
1150                 int             j;
1151
1152                 if ( as->as_priority < cur_priority ) {
1153                         /*
1154                          * We already got higher priority information,
1155                          * so no further work to do, ignore the rest.
1156                          */
1157                         break;
1158                 }
1159                 vals = ldap_get_values( ld, e, as->as_name );
1160                 if ( !vals || vals[0] == NULL ) {
1161                         continue;
1162                 }
1163                 nent = count_values( vals );
1164                 if ( nent > 1 && !as->as_m_valued ) {
1165                         add_error( err, nerr, E_AMBIGUOUS, address, e );
1166                         return( 0 );
1167                 }
1168                 switch ( as->as_kind ) {
1169                 case AS_KIND_RECIPIENT:
1170                         cur_priority = as->as_priority;
1171                         if ( ! ( type & ( USER | GROUP_MEMBERS ) ) )
1172                                 break;
1173                         switch ( as->as_syntax ) {
1174                         case AS_SYNTAX_RFC822:
1175                                 add_to( current_to, current_nto, vals );
1176                                 resolved = 1;
1177                                 break;
1178                         case AS_SYNTAX_RFC822_EXT:
1179                                 add_to( current_to, current_nto, vals );
1180                                 resolved = 1;
1181                                 break;
1182                         case AS_SYNTAX_NATIVE_MB:
1183                                 /* We used to concatenate mailHost if set here */
1184                                 /*
1185                                  * We used to send a copy to the vacation host
1186                                  * if onVacation to uid@vacationhost
1187                                  */
1188                                 add_to( current_to, current_nto, vals );
1189                                 resolved = 1;
1190                                 break;
1191
1192                         case AS_SYNTAX_DN:
1193                                 if ( dn_search( vals, address,
1194                                                 current_to, current_nto,
1195                                                 togroups, ngroups,
1196                                                 err, nerr ) ) {
1197                                         resolved = 1;
1198                                 }
1199                                 break;
1200
1201                         case AS_SYNTAX_URL:
1202                                 if ( url_list_search( vals, address,
1203                                                  as->as_m_entries,
1204                                                  current_to, current_nto,
1205                                                  togroups, ngroups,
1206                                                  err, nerr, type ) ) {
1207                                         resolved = 1;
1208                                 }
1209                                 break;
1210
1211                         case AS_SYNTAX_BOOL_FILTER:
1212                                 if ( strcasecmp( vals[0], "true" ) ) {
1213                                         break;
1214                                 }
1215                                 substs[0].sub_char = 'D';
1216                                 substs[0].sub_value = dn;
1217                                 substs[1].sub_char = '\0';
1218                                 substs[1].sub_value = NULL;
1219                                 if ( url_list_search( vals, address,
1220                                                  as->as_m_entries,
1221                                                  current_to, current_nto,
1222                                                  togroups, ngroups,
1223                                                  err, nerr, type ) ) {
1224                                         resolved = 1;
1225                                 }
1226                                 break;
1227
1228                         default:
1229                                 syslog( LOG_ALERT,
1230                                         "Invalid syntax %d for kind %d",
1231                                         as->as_syntax, as->as_kind );
1232                                 break;
1233                         }
1234                         break;
1235
1236                 case AS_KIND_ERRORS:
1237                         cur_priority = as->as_priority;
1238                         /* This is a group with special processing */
1239                         if ( type & GROUP_ERRORS ) {
1240                                 switch (as->as_kind) {
1241                                 case AS_SYNTAX_RFC822:
1242                                         add_to( current_to, current_nto, vals );
1243                                         resolved = 1;
1244                                         break;
1245                                 case AS_SYNTAX_URL:
1246                                 default:
1247                                         syslog( LOG_ALERT,
1248                                                 "Invalid syntax %d for kind %d",
1249                                                 as->as_syntax, as->as_kind );
1250                                 }
1251                         } else {
1252                                 current_group = new_group( dn, togroups,
1253                                                            ngroups );
1254                                 current_to = &current_group->g_members;
1255                                 current_nto = &current_group->g_nmembers;
1256                                 split_address( address,
1257                                                &localpart, &domainpart );
1258                                 if ( domainpart ) {
1259                                         sprintf( buf, "%s-%s@%s",
1260                                                  localpart, ERRORS,
1261                                                  domainpart );
1262                                         free( localpart );
1263                                         free( domainpart );
1264                                 } else {
1265                                         sprintf( buf, "%s-%s@%s",
1266                                                  localpart, ERRORS,
1267                                                  host );
1268                                         free( localpart );
1269                                 }
1270                                 current_group->g_errorsto = strdup( buf );
1271                         }
1272                         break;
1273
1274                 case AS_KIND_REQUEST:
1275                         cur_priority = as->as_priority;
1276                         /* This is a group with special processing */
1277                         if ( type & GROUP_REQUEST ) {
1278                                 add_to( current_to, current_nto, vals );
1279                                 resolved = 1;
1280                         }
1281                         break;
1282
1283                 case AS_KIND_OWNER:
1284                         cur_priority = as->as_priority;
1285                         /* This is a group with special processing */
1286                         if ( type & GROUP_REQUEST ) {
1287                                 add_to( current_to, current_nto, vals );
1288                                 resolved = 1;
1289                         }
1290                         break;
1291
1292                 case AS_KIND_ROUTE_TO_HOST:
1293                         if ( !is_my_domain( vals[0] ) ) {
1294                                 cur_priority = as->as_priority;
1295                                 if ( as->as_syntax == AS_SYNTAX_PRESENT ) {
1296                                         needs_mta_routing = 1;
1297                                 } else {
1298                                         route_to_host = strdup( vals[0] );
1299                                 }
1300                         }
1301                         break;
1302
1303                 case AS_KIND_ROUTE_TO_ADDR:
1304                         for ( j=0; j<own_addresses_total; j++ ) {
1305                                 if ( strcasecmp( vals[0], own_addresses[j] ) ) {
1306                                         cur_priority = as->as_priority;
1307                                         if ( as->as_syntax == AS_SYNTAX_PRESENT ) {
1308                                                 needs_mta_routing = 1;
1309                                         } else {
1310                                                 route_to_address = strdup( vals[0] );
1311                                         }
1312                                 }
1313                                 break;
1314                         }
1315
1316                 case AS_KIND_OWN_ADDR:
1317                         add_to( &own_addresses, &own_addresses_total, vals );
1318                         cur_priority = as->as_priority;
1319                         break;
1320
1321                 default:
1322                         syslog( LOG_ALERT,
1323                                 "Invalid kind %d", as->as_kind );
1324                         /* Error, TBC */
1325                 }
1326                 ldap_value_free( vals );
1327         }
1328         /*
1329          * Now check if we are dealing with mail routing.  We support
1330          * two modes.
1331          *
1332          * The first mode and by far the most robust method is doing
1333          * routing at the MTA.  In this case, we just checked if the
1334          * routing attributes were present and did not seem like
1335          * pointing to ourselves.  The only thing we have to do here
1336          * is adding to the recipient list any of the RFC822 addresses
1337          * of this entry.  That means we needed to retrieve them from
1338          * the entry itself because we might have arrived here through
1339          * some directory search.  The address received as argument is
1340          * not the address of the entry we are processing, but rather
1341          * the RFC822 address we are expanding now.  Unfortunately,
1342          * this requires an MTA that understands LDAP routing.
1343          * Sendmail 8.10.0 does, if compiled properly.
1344          *
1345          * The second method, that is most emphatically not recommended
1346          * is routing in mail500.  This is going to require using the
1347          * percent hack.  Moreover, this may occasionally loop.
1348          */
1349         if ( needs_mta_routing ) {
1350                 if ( !own_addresses ) {
1351                         add_error( err, nerr, E_NOOWNADDRESS, address, e );
1352                         return( 0 );
1353                 }
1354                 nvals[0] = own_addresses[0];    /* Anyone will do */
1355                 nvals[1] = NULL;
1356                 add_to( current_to, current_nto, nvals );
1357                 resolved = 1;
1358         } else if ( route_to_host ) {
1359                 char *p;
1360                 if ( !route_to_address ) {
1361                         if ( !own_addresses ) {
1362                                 add_error( err, nerr, E_NOOWNADDRESS, address, e );
1363                                 return( 0 );
1364                         }
1365                         route_to_address = strdup( own_addresses[0] );
1366                 }
1367                 /* This makes use of the percent hack, but there's no choice */
1368                 p = strchr( route_to_address, '@' );
1369                 if ( p ) {
1370                         *p = '%';
1371                 }
1372                 sprintf( buf, "%s@%s", route_to_address, route_to_host );
1373                 nvals[0] = buf;
1374                 nvals[1] = NULL;
1375                 add_to( current_to, current_nto, nvals );
1376                 resolved = 1;
1377                 free( route_to_host );
1378                 free( route_to_address );
1379         } else if ( route_to_address ) {
1380                 nvals[0] = route_to_address;
1381                 nvals[1] = NULL;
1382                 add_to( current_to, current_nto, nvals );
1383                 resolved = 1;
1384                 free( route_to_address );
1385         }
1386         if ( own_addresses ) {
1387                 ldap_value_free( own_addresses );
1388         }
1389                   
1390         return( resolved );
1391 }
1392
1393 static int
1394 search_bases(
1395         char    *filter,
1396         Subst   *substs,
1397         char    *name,
1398         char    ***to,
1399         int     *nto,
1400         Group   ***togroups,
1401         int     *ngroups,
1402         Error   **err,
1403         int     *nerr,
1404         int     type
1405 )
1406 {
1407         int             b, resolved = 0;
1408
1409         for ( b = 0; base[b] != NULL; b++ ) {
1410
1411                 if ( ! (base[b]->b_search & type) ) {
1412                         continue;
1413                 }
1414
1415                 resolved = search_ldap_url( base[b]->b_url, substs, name,
1416                                             base[b]->b_rdnpref,
1417                                             base[b]->b_m_entries,
1418                                             to, nto, togroups, ngroups,
1419                                             err, nerr, type );
1420                 if ( resolved )
1421                         break;
1422         }
1423         return( resolved );
1424 }
1425
1426 static void
1427 do_address(
1428         char    *name,
1429         char    ***to,
1430         int     *nto,
1431         Group   ***togroups,
1432         int     *ngroups,
1433         Error   **err,
1434         int     *nerr,
1435         int     type
1436 )
1437 {
1438         struct timeval  timeout;
1439         char            *localpart, *domainpart;
1440         int             resolved;
1441         Subst   substs[5];
1442
1443         /*
1444          * Look up the name in X.500, add the appropriate addresses found
1445          * to the to list, or to the err list in case of error.  Groups are
1446          * handled by the do_group routine, individuals are handled here.
1447          * When looking up name, we follow the bases hierarchy, looking
1448          * in base[0] first, then base[1], etc.  For each base, there is
1449          * a set of search filters to try, in order.  If something goes
1450          * wrong here trying to contact X.500, we exit with EX_TEMPFAIL.
1451          * If the b_rdnpref flag is set, then we give preference to entries
1452          * that matched name because it's their rdn, otherwise not.
1453          */
1454
1455         split_address( name, &localpart, &domainpart );
1456         timeout.tv_sec = MAIL500_TIMEOUT;
1457         timeout.tv_usec = 0;
1458         substs[0].sub_char = 'm';
1459         substs[0].sub_value = name;
1460         substs[1].sub_char = 'h';
1461         substs[1].sub_value = host;
1462         substs[2].sub_char = 'l';
1463         substs[2].sub_value = localpart;
1464         substs[3].sub_char = 'd';
1465         substs[3].sub_value = domainpart;
1466         substs[4].sub_char = '\0';
1467         substs[4].sub_value = NULL;
1468
1469         resolved = search_bases( NULL, substs, name,
1470                                  to, nto, togroups, ngroups,
1471                                  err, nerr, type );
1472
1473         if ( !resolved ) {
1474                 /* not resolved - bounce with user unknown */
1475                 if ( type == USER ) {
1476                         add_error( err, nerr, E_USERUNKNOWN, name, NULL );
1477                 } else {
1478                         add_error( err, nerr, E_GROUPUNKNOWN, name, NULL );
1479                 }
1480         }
1481 }
1482
1483 static void
1484 send_message( char **to )
1485 {
1486         int     pid;
1487 #ifndef HAVE_WAITPID
1488         WAITSTATUSTYPE  status;
1489 #endif
1490
1491         if ( debug ) {
1492                 char    buf[1024];
1493                 int     i;
1494
1495                 strcpy( buf, to[0] );
1496                 for ( i = 1; to[i] != NULL; i++ ) {
1497                         strcat( buf, " " );
1498                         strcat( buf, to[i] );
1499                 }
1500
1501                 syslog( LOG_ALERT, "send_message execing sendmail: (%s)", buf );
1502         }
1503
1504         /* parent */
1505         if ( (pid = fork()) != 0 ) {
1506 #ifdef HAVE_WAITPID
1507                 waitpid( pid, (int *) NULL, 0 );
1508 #else
1509                 wait4( pid, &status, WAIT_FLAGS, 0 );
1510 #endif
1511         /* child */
1512         } else {
1513                 /* to includes sendmailargs */
1514                 execv( MAIL500_SENDMAIL, to );
1515
1516                 syslog( LOG_ALERT, "execv failed" );
1517
1518                 exit( EX_TEMPFAIL );
1519         }
1520 }
1521
1522 static void
1523 send_group( Group **group, int ngroup )
1524 {
1525         int     i, pid;
1526         char    **argv;
1527         int     argc;
1528         char    *iargv[7];
1529 #ifndef HAVE_WAITPID
1530         WAITSTATUSTYPE  status;
1531 #endif
1532
1533         for ( i = 0; i < ngroup; i++ ) {
1534                 (void) rewind( stdin );
1535
1536                 iargv[0] = MAIL500_SENDMAIL;
1537                 iargv[1] = "-f";
1538                 iargv[2] = group[i]->g_errorsto;
1539                 iargv[3] = "-oMrX.500";
1540                 iargv[4] = "-odi";
1541                 iargv[5] = "-oi";
1542                 iargv[6] = NULL;
1543
1544                 argv = NULL;
1545                 argc = 0;
1546                 add_to( &argv, &argc, iargv );
1547                 add_to( &argv, &argc, group[i]->g_members );
1548
1549                 if ( debug ) {
1550                         char    buf[1024];
1551                         int     i;
1552
1553                         strcpy( buf, argv[0] );
1554                         for ( i = 1; i < argc; i++ ) {
1555                                 strcat( buf, " " );
1556                                 strcat( buf, argv[i] );
1557                         }
1558
1559                         syslog( LOG_ALERT, "execing sendmail: (%s)", buf );
1560                 }
1561
1562                 /* parent */
1563                 if ( (pid = fork()) != 0 ) {
1564 #ifdef HAVE_WAITPID
1565                         waitpid( pid, (int *) NULL, 0 );
1566 #else
1567                         wait4( pid, &status, WAIT_FLAGS, 0 );
1568 #endif
1569                 /* child */
1570                 } else {
1571                         execv( MAIL500_SENDMAIL, argv );
1572
1573                         syslog( LOG_ALERT, "execv failed" );
1574
1575                         exit( EX_TEMPFAIL );
1576                 }
1577         }
1578 }
1579
1580 static void
1581 send_errors( Error *err, int nerr )
1582 {
1583         int     pid, i, namelen;
1584         FILE    *fp;
1585         int     fd[2];
1586         char    *argv[8];
1587         char    buf[1024];
1588 #ifndef HAVE_WAITPID
1589         WAITSTATUSTYPE  status;
1590 #endif
1591
1592         if ( strcmp( MAIL500_BOUNCEFROM, mailfrom ) == 0 ) {
1593             mailfrom = errorsfrom;
1594         }
1595
1596         argv[0] = MAIL500_SENDMAIL;
1597         argv[1] = "-oMrX.500";
1598         argv[2] = "-odi";
1599         argv[3] = "-oi";
1600         argv[4] = "-f";
1601         argv[5] = MAIL500_BOUNCEFROM;
1602         argv[6] = mailfrom;
1603         argv[7] = NULL;
1604
1605         if ( debug ) {
1606                 int     i;
1607
1608                 strcpy( buf, argv[0] );
1609                 for ( i = 1; argv[i] != NULL; i++ ) {
1610                         strcat( buf, " " );
1611                         strcat( buf, argv[i] );
1612                 }
1613
1614                 syslog( LOG_ALERT, "execing sendmail: (%s)", buf );
1615         }
1616
1617         if ( pipe( fd ) == -1 ) {
1618                 syslog( LOG_ALERT, "cannot create pipe" );
1619                 exit( EX_TEMPFAIL );
1620         }
1621
1622         if ( (pid = fork()) != 0 ) {
1623                 if ( (fp = fdopen( fd[1], "w" )) == NULL ) {
1624                         syslog( LOG_ALERT, "cannot fdopen pipe" );
1625                         exit( EX_TEMPFAIL );
1626                 }
1627
1628                 fprintf( fp, "To: %s\n", mailfrom );
1629                 fprintf( fp, "From: %s\n", errorsfrom );
1630                 fprintf( fp, "Subject: undeliverable mail\n" );
1631                 fprintf( fp, "\n" );
1632                 fprintf( fp, "The following errors occurred when trying to deliver the attached mail:\n" );
1633                 for ( i = 0; i < nerr; i++ ) {
1634                         namelen = strlen( err[i].e_addr );
1635                         fprintf( fp, "\n" );
1636
1637                         switch ( err[i].e_code ) {
1638                         case E_USERUNKNOWN:
1639                                 fprintf( fp, "%s: User unknown\n", err[i].e_addr );
1640                                 break;
1641
1642                         case E_GROUPUNKNOWN:
1643                                 fprintf( fp, "%s: Group unknown\n", err[i].e_addr );
1644                                 break;
1645
1646                         case E_BADMEMBER:
1647                                 fprintf( fp, "%s: Group member does not exist\n",
1648                                     err[i].e_addr );
1649                                 fprintf( fp, "This could be because the distinguished name of the person has changed\n" );
1650                                 fprintf( fp, "If this is the case, the problem can be solved by removing and\n" );
1651                                 fprintf( fp, "then re-adding the person to the group.\n" );
1652                                 break;
1653
1654                         case E_NOREQUEST:
1655                                 fprintf( fp, "%s: Group exists but has no request address\n",
1656                                     err[i].e_addr );
1657                                 break;
1658
1659                         case E_NOERRORS:
1660                                 fprintf( fp, "%s: Group exists but has no errors-to address\n",
1661                                     err[i].e_addr );
1662                                 break;
1663
1664                         case E_NOOWNER:
1665                                 fprintf( fp, "%s: Group exists but has no owner\n",
1666                                     err[i].e_addr );
1667                                 break;
1668
1669                         case E_AMBIGUOUS:
1670                                 do_ambiguous( fp, &err[i], namelen );
1671                                 break;
1672
1673                         case E_NOEMAIL:
1674                                 do_noemail( fp, &err[i], namelen );
1675                                 break;
1676
1677                         case E_MEMBERNOEMAIL:
1678                                 fprintf( fp, "%s: Group member exists but does not have an email address\n",
1679                                     err[i].e_addr );
1680                                 break;
1681
1682                         case E_JOINMEMBERNOEMAIL:
1683                                 fprintf( fp, "%s: User has joined group but does not have an email address\n",
1684                                     err[i].e_addr );
1685                                 break;
1686
1687                         case E_LOOP:
1688                                 fprintf( fp, "%s: User has created a mail loop by adding address %s to their X.500 entry\n",
1689                                     err[i].e_addr, err[i].e_loop );
1690                                 break;
1691
1692                         case E_NOMEMBERS:
1693                                 fprintf( fp, "%s: Group has no members\n",
1694                                     err[i].e_addr );
1695                                 break;
1696
1697                         case E_NOOWNADDRESS:
1698                                 fprintf( fp, "%s: Not enough information to perform required routing\n",
1699                                     err[i].e_addr );
1700                                 break;
1701
1702                         default:
1703                                 syslog( LOG_ALERT, "unknown error %d", err[i].e_code );
1704                                 unbind_and_exit( EX_TEMPFAIL );
1705                                 break;
1706                         }
1707                 }
1708
1709                 fprintf( fp, "\n------- The original message sent:\n\n" );
1710
1711                 while ( fgets( buf, sizeof(buf), stdin ) != NULL ) {
1712                         fputs( buf, fp );
1713                 }
1714                 fclose( fp );
1715
1716 #ifdef HAVE_WAITPID
1717                 waitpid( pid, (int *) NULL, 0 );
1718 #else
1719                 wait4( pid, &status, WAIT_FLAGS, 0 );
1720 #endif
1721         } else {
1722                 dup2( fd[0], 0 );
1723
1724                 execv( MAIL500_SENDMAIL, argv );
1725
1726                 syslog( LOG_ALERT, "execv failed" );
1727
1728                 exit( EX_TEMPFAIL );
1729         }
1730 }
1731
1732 static void
1733 do_noemail( FILE *fp, Error *err, int namelen )
1734 {
1735         int             i, last;
1736         char            *dn, *rdn;
1737         char            **ufn, **vals;
1738
1739         fprintf(fp, "%s: User has no email address registered.\n",
1740             err->e_addr );
1741         fprintf( fp, "%*s  Name, title, postal address and phone for '%s':\n\n",
1742             namelen, " ", err->e_addr );
1743
1744         /* name */
1745         dn = ldap_get_dn( ld, err->e_msg );
1746         ufn = ldap_explode_dn( dn, 1 );
1747         rdn = strdup( ufn[0] );
1748         if ( strcasecmp( rdn, err->e_addr ) == 0 ) {
1749                 if ( (vals = ldap_get_values( ld, err->e_msg, "cn" ))
1750                     != NULL ) {
1751                         for ( i = 0; vals[i]; i++ ) {
1752                                 last = strlen( vals[i] ) - 1;
1753                                 if ( isdigit((unsigned char) vals[i][last]) ) {
1754                                         rdn = strdup( vals[i] );
1755                                         break;
1756                                 }
1757                         }
1758
1759                         ldap_value_free( vals );
1760                 }
1761         }
1762         fprintf( fp, "%*s  %s\n", namelen, " ", rdn );
1763         free( dn );
1764         free( rdn );
1765         ldap_value_free( ufn );
1766
1767         /* titles or descriptions */
1768         if ( (vals = ldap_get_values( ld, err->e_msg, "title" )) == NULL &&
1769             (vals = ldap_get_values( ld, err->e_msg, "description" ))
1770             == NULL ) {
1771                 fprintf( fp, "%*s  No title or description registered\n",
1772                     namelen, " " );
1773         } else {
1774                 for ( i = 0; vals[i] != NULL; i++ ) {
1775                         fprintf( fp, "%*s  %s\n", namelen, " ", vals[i] );
1776                 }
1777
1778                 ldap_value_free( vals );
1779         }
1780
1781         /* postal address */
1782         if ( (vals = ldap_get_values( ld, err->e_msg, "postalAddress" ))
1783             == NULL ) {
1784                 fprintf( fp, "%*s  No postal address registered\n", namelen,
1785                     " " );
1786         } else {
1787                 fprintf( fp, "%*s  ", namelen, " " );
1788                 for ( i = 0; vals[0][i] != '\0'; i++ ) {
1789                         if ( vals[0][i] == '$' ) {
1790                                 fprintf( fp, "\n%*s  ", namelen, " " );
1791                                 while ( isspace((unsigned char) vals[0][i+1]) )
1792                                         i++;
1793                         } else {
1794                                 fprintf( fp, "%c", vals[0][i] );
1795                         }
1796                 }
1797                 fprintf( fp, "\n" );
1798
1799                 ldap_value_free( vals );
1800         }
1801
1802         /* telephone number */
1803         if ( (vals = ldap_get_values( ld, err->e_msg, "telephoneNumber" ))
1804             == NULL ) {
1805                 fprintf( fp, "%*s  No phone number registered\n", namelen,
1806                     " " );
1807         } else {
1808                 for ( i = 0; vals[i] != NULL; i++ ) {
1809                         fprintf( fp, "%*s  %s\n", namelen, " ", vals[i] );
1810                 }
1811
1812                 ldap_value_free( vals );
1813         }
1814 }
1815
1816 /* ARGSUSED */
1817 static void
1818 do_ambiguous( FILE *fp, Error *err, int namelen )
1819 {
1820         int             i, last;
1821         char            *dn, *rdn;
1822         char            **ufn, **vals;
1823         LDAPMessage     *e;
1824
1825         i = ldap_result2error( ld, err->e_msg, 0 );
1826
1827         fprintf( fp, "%s: Ambiguous user.  %s%d matches found:\n\n",
1828             err->e_addr, i == LDAP_SIZELIMIT_EXCEEDED ? "First " : "",
1829             ldap_count_entries( ld, err->e_msg ) );
1830
1831         for ( e = ldap_first_entry( ld, err->e_msg ); e != NULL;
1832             e = ldap_next_entry( ld, e ) ) {
1833                 dn = ldap_get_dn( ld, e );
1834                 ufn = ldap_explode_dn( dn, 1 );
1835                 rdn = strdup( ufn[0] );
1836                 if ( strcasecmp( rdn, err->e_addr ) == 0 ) {
1837                         if ( (vals = ldap_get_values( ld, e, "cn" )) != NULL ) {
1838                                 for ( i = 0; vals[i]; i++ ) {
1839                                         last = strlen( vals[i] ) - 1;
1840                                         if (isdigit((unsigned char) vals[i][last])) {
1841                                                 rdn = strdup( vals[i] );
1842                                                 break;
1843                                         }
1844                                 }
1845
1846                                 ldap_value_free( vals );
1847                         }
1848                 }
1849
1850                 /* 
1851                 if ( isgroup( e ) ) {
1852                         vals = ldap_get_values( ld, e, "description" );
1853                 } else {
1854                         vals = ldap_get_values( ld, e, "title" );
1855                 }
1856                 */
1857                 vals = ldap_get_values( ld, e, "description" );
1858
1859                 fprintf( fp, "    %-20s %s\n", rdn, vals ? vals[0] : "" );
1860                 for ( i = 1; vals && vals[i] != NULL; i++ ) {
1861                         fprintf( fp, "                         %s\n", vals[i] );
1862                 }
1863
1864                 free( dn );
1865                 free( rdn );
1866                 ldap_value_free( ufn );
1867                 if ( vals != NULL )
1868                         ldap_value_free( vals );
1869         }
1870 }
1871
1872 static int
1873 count_values( char **list )
1874 {
1875         int     i;
1876
1877         for ( i = 0; list && list[i] != NULL; i++ )
1878                 ;       /* NULL */
1879
1880         return( i );
1881 }
1882
1883 static void
1884 add_to( char ***list, int *nlist, char **new )
1885 {
1886         int     i, nnew, oldnlist;
1887
1888         nnew = count_values( new );
1889
1890         oldnlist = *nlist;
1891         if ( *list == NULL || *nlist == 0 ) {
1892                 *list = (char **) malloc( (nnew + 1) * sizeof(char *) );
1893                 *nlist = nnew;
1894         } else {
1895                 *list = (char **) realloc( *list, *nlist * sizeof(char *) +
1896                     nnew * sizeof(char *) + sizeof(char *) );
1897                 *nlist += nnew;
1898         }
1899
1900         for ( i = 0; i < nnew; i++ )
1901                 (*list)[i + oldnlist] = strdup( new[i] );
1902         (*list)[*nlist] = NULL;
1903 }
1904
1905 static void
1906 add_single_to( char ***list, char *new )
1907 {
1908         int     nlist;
1909
1910         if ( *list == NULL ) {
1911                 nlist = 0;
1912                 *list = (char **) malloc( 2 * sizeof(char *) );
1913         } else {
1914                 nlist = count_values( *list );
1915                 *list = (char **) realloc( *list,
1916                                            ( nlist + 2 ) * sizeof(char *) );
1917         }
1918
1919         (*list)[nlist] = strdup( new );
1920         (*list)[nlist+1] = NULL;
1921 }
1922
1923 static int
1924 isgroup( LDAPMessage *e )
1925 {
1926         int     i, j;
1927         char    **oclist;
1928
1929         if ( !groupclasses ) {
1930                 return( 0 );
1931         }
1932
1933         oclist = ldap_get_values( ld, e, "objectClass" );
1934
1935         for ( i = 0; oclist[i] != NULL; i++ ) {
1936                 for ( j = 0; groupclasses[j] != NULL; j++ ) {
1937                         if ( strcasecmp( oclist[i], groupclasses[j] ) == 0 ) {
1938                                 ldap_value_free( oclist );
1939                                 return( 1 );
1940                         }
1941                 }
1942         }
1943         ldap_value_free( oclist );
1944
1945         return( 0 );
1946 }
1947
1948 static void
1949 add_error( Error **err, int *nerr, int code, char *addr, LDAPMessage *msg )
1950 {
1951         if ( *nerr == 0 ) {
1952                 *err = (Error *) malloc( sizeof(Error) );
1953         } else {
1954                 *err = (Error *) realloc( *err, (*nerr + 1) * sizeof(Error) );
1955         }
1956
1957         (*err)[*nerr].e_code = code;
1958         (*err)[*nerr].e_addr = strdup( addr );
1959         (*err)[*nerr].e_msg = msg;
1960         (*nerr)++;
1961 }
1962
1963 static void
1964 unbind_and_exit( int rc )
1965 {
1966         int     i;
1967
1968         if ( (i = ldap_unbind( ld )) != LDAP_SUCCESS )
1969                 syslog( LOG_ALERT, "ldap_unbind failed %d\n", i );
1970
1971         exit( rc );
1972 }