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