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