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