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