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