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