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