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