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