<commit_before>/*
 * Copyright (c) 2004 Regents of The University of Michigan.
 * All Rights Reserved.  See COPYRIGHT.
 */

/*****     simta_ldap.c     *****/

#include "config.h"

#ifdef TLS
#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#endif /* TLS */

#include <snet.h>

#include <sys/types.h>
#include <sys/wait.h>
#include <sys/param.h>

#include <sys/time.h>		/* struct timeval */
#include <stdio.h>
#include <syslog.h>
#include <ctype.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <ldap.h>
#include <unistd.h>
#include <errno.h>
#include <netdb.h>

#include "denser.h"
#include "ll.h"
#include "queue.h"
#include "envelope.h"
#include "simta.h"
#include "expand.h"
#include "argcargv.h"
#include "simta_ldap.h"
#include "dn.h"
#include "oklist.h"

#define	SIMTA_LDAP_CONF		"./simta_ldap.conf"

#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif
#define MAXRETRIES		5

#define MAXAMBIGUOUS		10
#define LDAP_TIMEOUT_VAL	180

/* MAXADDRESSLENGTH -- maximum length email address we're gonna process */
#define MAXADDRESSLENGTH	1024
/* ERRORFMTBUFLEN -- Error buffer length process max buffer size. */
#define ERRORFMTBUFLEN		2048

/* 
e* LDAP attribute names
** noattrs -- is a attribute list when no attributes are needed.
** allattrs -- is a attribute list when all attributes are wanted.  It
**             It is also the default attribute list if no attribute
**             config file directive found.
*/
static char	*noattrs[] =  {LDAP_NO_ATTRS, NULL};
static char	*allattrs[] = {"*", NULL};

static char     **attrs     = NULL;

/*
** ldap_search_list -- Contains a parsed uri from the config file.
*/
struct ldap_search_list {
    LDAPURLDesc			*lds_plud;	/* url parsed description */
    int				lds_rdn_pref;	/* TRUE / FALSE */
    int				lds_search_type;/* one of USER, GROUP, ALL */
    char			*lds_string;	/* uri string */
    struct ldap_search_list     *lds_next;	/* next uri */
};

struct ldap_search_list		*ldap_searches = NULL;
struct list			*ldap_people = NULL;
struct list			*ldap_groups = NULL;
LDAP				*ld = NULL;

static char			*ldap_host;
static int			ldap_port;
static time_t			ldap_timeout = LDAP_TIMEOUT_VAL;
static int			starttls;
static char			*binddn;
static char			*bindpw;

static int			ldapdebug;

static char			*vacationhost;
static char			*vacationattr;
static char			*mailfwdattr;

static int			ndomaincomponent = 2;
/*
** simta_ldap_message_stdout -- Dumps an entry to stdout
*/
    static int
simta_ldap_message_stdout( LDAPMessage *m )
{
    LDAPMessage		*entry;
    LDAPMessage		*message;
    char		*dn;
    char		*attribute;
    BerElement		*ber;
    char		**values;
    int			idx;

    if (( entry = ldap_first_entry( ld, m )) == NULL ) {
	ldap_perror( ld, "ldap_first_entry" );
	return( -1 );
    }

    if (( message = ldap_first_message( ld, m )) == NULL ) {
	ldap_perror( ld, "ldap_first_message" );
	return( -1 );
    }

    if (( dn = ldap_get_dn( ld, message )) == NULL ) {
	ldap_perror( ld, "ldap_get_dn" );
	return( -1 );
    }

    printf( "dn: %s\n", dn );
    ldap_memfree( dn );

    for ( attribute = ldap_first_attribute( ld, message, &ber );
          attribute != NULL;
          attribute = ldap_next_attribute( ld, message, ber )   ) {
    
	printf( "%s:\n", attribute );

	if (( values = ldap_get_values( ld, entry, attribute )) == NULL ) {
	    ldap_perror( ld, "ldap_get_values" );
	    return( -1 );
	}

	for ( idx = 0; values[ idx ] != NULL; idx++ ) {
	    printf( "	%s\n", values[ idx ] );
	}

	ldap_value_free( values );
    }

    ber_free( ber, 0 );

    return( 0 );
}

    static void 
simta_ldapdomain (char * buf, char ** domain)
{
    char *pbuf;
    int	 dotcnt = 0;

    pbuf = &buf [ strlen (buf) - 1 ];

    while (pbuf > buf) {
	if (*pbuf == '.') {
	    if (dotcnt == (ndomaincomponent - 1)) {
		pbuf++;
		break;
	    }
	    dotcnt++;
	}
	pbuf--;
    }
    *domain = strdup(pbuf);
    return;
}

    static void 
simta_ldapuser (char * buf, char ** user, char ** domain)
{
    char  *puser;

    *domain = NULL;
    *user = strdup (buf);

    puser = strchr(*user , '@');
    if (puser) {
	*puser = '\0';
	puser++;
	simta_ldapdomain (puser, domain);
    }
    else
	*domain = strdup("");  
    return;
}

/*
** Unbind from the directory.
*/
    static void
simta_ldap_unbind ()
{
    ldap_unbind( ld );
    ld = NULL;
    return;
}

    static int
simta_ldap_init ()
{
    int ldaprc;
    int	maxambiguous = MAXAMBIGUOUS;
    int protocol = LDAP_VERSION3;

    if ( ld == NULL ) {
	if (( ld = ldap_init( ldap_host, ldap_port )) == NULL ) {
	    syslog( LOG_ERR, "ldap_init: %m" );
	    return( LDAP_SYSERROR );
	}
	if ( ldapdebug ) {
	    ldaprc = ber_set_option( NULL, LBER_OPT_DEBUG_LEVEL, &ldapdebug );
	    if( ldaprc != LBER_OPT_SUCCESS ) {
		syslog( LOG_ERR, 
	"simta_ldap_init: Failed setting LBER_OPT_DEBUG_LEVEL=%d\n", ldapdebug);
	    }
	    ldaprc = ldap_set_option( NULL, LDAP_OPT_DEBUG_LEVEL, &ldapdebug );
	    if( ldaprc != LDAP_OPT_SUCCESS ) {
		syslog( LOG_ERR, 
	"simta_ldap_init: Failed setting LDAP_OPT_DEBUG_LEVEL=%d\n", ldapdebug);
	    }
	}
	ldaprc = ldap_set_option( ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
	if (ldaprc != LDAP_OPT_SUCCESS ) {
	    syslog( LOG_ERR, 
	"simta_ldap_init: Failed setting LDAP_OPT_REFERRALS to LDAP_OPT_OFF");
	    return( LDAP_SYSERROR );
	}
	ldaprc = ldap_set_option(ld, LDAP_OPT_SIZELIMIT, (void *)&maxambiguous);
	if (ldaprc != LDAP_OPT_SUCCESS ) {
	    syslog( LOG_ERR, 
		"simta_ldap_init: Failed setting LDAP_OPT_SIZELIMIT = %d\n",
		maxambiguous);
	    return( LDAP_SYSERROR );
	}
	ldaprc = ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &protocol );
	if( ldaprc != LDAP_OPT_SUCCESS ) {
	    syslog( LOG_ERR, 
	"simta_ldap_init: Failed setting LDAP_OPT_PROTOCOL_VERSION = %d\n",
		protocol );
	    return( LDAP_SYSERROR );
	}

	if ( starttls &&
	   ((ldaprc = ldap_start_tls_s( ld, NULL, NULL )) != LDAP_SUCCESS )) {
	    syslog( LOG_ERR, "ldap_start_tls: %s", ldap_err2string(ldaprc));
	    if ( starttls > 1 ) {
		return( LDAP_SYSERROR );
	    }
	}
    }
    if (binddn) {
	if (ldap_bind_s( ld, binddn, bindpw, LDAP_AUTH_SIMPLE) != LDAP_SUCCESS){
	    ldap_perror( ld, "ldap_bind" );
	    return( LDAP_SYSERROR );
	}
    }
    return (0);
}

/*
** This function looks thru the attribute "attr" values 
** for the first matching value in the "master" list
*/
    static  int
simta_ldap_value( LDAPMessage *e, char *attr, struct list *master )
{
    int			idx;
    char		**values;
    struct list		*l;

    if (( values = ldap_get_values( ld, e, attr )) != NULL ) {

	for ( idx = 0; values[ idx ] != NULL; idx++ ) {
	    for ( l = master ; l != NULL; l = l->l_next ) {
		if ( strcasecmp( values[ idx ], l->l_string ) == 0 ) {
		    ldap_value_free( values );
		    return ( 1 );
		}
	    }
	}
	ldap_value_free( values );
    }
    return( 0 );
}

    /* return a statically allocated string if all goes well, NULL if not.
     *
     *     - Build search string where:
     *         + %s -> username
     *         + %h -> hostname
     */

    static char *
simta_ldap_string( char *filter, char *user, char *domain )
{
    size_t		len;
    static size_t	buf_len = 0;
    static char		*buf = NULL;
    char		*c;
    char		*d;
    char		*insert;
    size_t		place;

    /* make sure buf is big enough search url */
    if (( len = strlen( filter ) + 1 ) > buf_len ) {
	if (( buf = (char*)realloc( buf, len )) == NULL ) {
	    syslog( LOG_ERR, "realloc: %m" );
	    return( NULL );
	}

	buf_len = len;
    }

    d = buf;
    c = filter;

    while ( *c != '\0' ) {
	switch ( *c ) {
	case '%':
	    switch ( * ( c + 1 )) {
		case 's':
		    /* if needed, resize buf to handle upcoming insert */
		    if (( len += strlen( user )) > buf_len ) {
			place = d - buf;
			if (( buf = (char*)realloc( buf, len )) == NULL ) {
			    syslog( LOG_ERR, "realloc: %m" );
			    return( NULL );
			}
			d = buf + place;
			buf_len = len;
		    }

		    /* insert word */
		    for ( insert = user; *insert != '\0'; insert++ ) {
			if (( *insert == '.' ) || ( *insert == '_' )) {
			    *d = ' ';
			} else {
			    *d = *insert;
			}
			d++;
		    }

		    /* advance read cursor */
		    c += 2;
		    break;

		case 'h':
		    /* if needed, resize buf to handle upcoming insert */
		    if (( len += strlen( domain )) > buf_len ) {
			place = d - buf;
			if (( buf = (char*)realloc( buf, len )) == NULL ) {
			    syslog( LOG_ERR, "realloc: %m" );
			    return( NULL );
			}
			d = buf + place;
			buf_len = len;
		    }

		    /* insert word */
		    for ( insert = domain; *insert != '\0'; insert++ ) {
			*d = *insert;
			d++;
		    }

		    /* advance read cursor */
		    c += 2;
		    break;

		case '%':
		    /* %% -> copy single % to data buffer */
		    *d = *c;
		    /* advance cursors */
		    c += 2;
		    d++;
		    break;

		default:
		    c++;
		    break;
	    }
	    break;

	default:
	    /* raw character, copy to data buffer */
	    *d = *c;

	    /* advance cursors */
	    d++;
	    c++;
	    break;
	}
    }

    *d = '\0';

    return( buf );
}


    /* this function should return:
     *     LDAP_SYSERROR if there is an error
     *     LDAP_LOCAL if addr is found in the db
     *     LDAP_NOT_LOCAL if addr is not found in the db
     */

    static int
simta_local_search (char ** attrs, char * user, char * domain, int *count)
{
    char		*search_string;
    struct ldap_search_list *lds;
    LDAPMessage		*res;
    int			rc;
    struct timeval	timeout;

    *count = 0;
    /* for each base string in ldap_searches:
     *     - Build search string
     *     - query the LDAP db with the search string
     */
    for ( lds = ldap_searches; lds != NULL; lds = lds->lds_next ) {
	search_string = simta_ldap_string( lds->lds_plud->lud_filter, 
			user, domain);
	if ( search_string == NULL ) 
	    return( LDAP_SYSERROR );

	timeout.tv_sec = ldap_timeout;
	timeout.tv_usec = 0;
	res = NULL;
	rc = ldap_search_st( ld, lds->lds_plud->lud_dn, 
			lds->lds_plud->lud_scope, search_string, attrs, 0, 
			&timeout, &res );
	if ( rc != LDAP_SUCCESS && rc != LDAP_SIZELIMIT_EXCEEDED ) {
	    syslog( LOG_ERR, 
	"simta_local_search: ldap_search_st Failed: %s", ldap_err2string(rc ));
		
	    ldap_msgfree( res );

	    simta_ldap_unbind (ld);
	    return( LDAP_SYSERROR );
	}

	*count = ldap_count_entries( ld, res );
	ldap_msgfree( res );
	if (*count)
	    break;
    }
    return (0);
}

/*
** Looks at the incoming email address
** looking for "-errors", "-requests", or "-owners"
**
*/
    static int 
simta_address_type (char * address)
{
    int    addrtype;
    char   *paddr = address;

    
    addrtype = LDS_USER;  /* default */
    if ( (paddr = strrchr ( address, '-')) != NULL )
    {
        paddr++;
        if ((strcasecmp(paddr, ERROR) == 0) ||
            (strcasecmp(paddr, ERRORS) == 0)) {
            addrtype = LDS_GROUP_ERRORS;
            *(--paddr) = '\0';
        } else if ((strcasecmp(paddr, REQUEST) == 0) ||
                   (strcasecmp(paddr, REQUESTS) == 0)) {
            addrtype = LDS_GROUP_REQUEST;
            *(--paddr) = '\0';
        } else if ((strcasecmp(paddr, OWNER) == 0) ||
                   (strcasecmp(paddr, OWNERS) == 0)) {
            addrtype = LDS_GROUP_OWNER;
            *(--paddr) = '\0';
        }
    }

    return (addrtype);
}

    static void
do_ambiguous (struct exp_addr *e_addr, char *addr, LDAPMessage *res)
{

    int		idx;
    char	*dn; 
    char	*rdn;
    char	**ufn;
    char	**vals;
    LDAPMessage	*e;

    if ( bounce_text( e_addr->e_addr_errors, addr, 
				": Ambigous user", NULL ) != 0 ) {
	return;
    }

    for ( e = ldap_first_entry( ld, res ); e != NULL;
	  e = ldap_next_entry( ld, e ) ) {
	dn = ldap_get_dn( ld, e );
	ufn = ldap_explode_dn( dn, 1 );
	rdn = strdup( ufn[0] );
	ldap_value_free( ufn );
	free( dn );

	if ( strcasecmp( rdn, addr ) == 0 ) {
	    if ( (vals = ldap_get_values( ld, e, "cn" )) != NULL ) {
		rdn = strdup( vals[0] );
		ldap_value_free( vals );
	    }
	}
	if (ldap_groups 
	&& ( simta_ldap_value( e, "objectClass", ldap_groups) > 0 )) {
	    vals = ldap_get_values( ld, e, "description" );
	} else {
	    vals = ldap_get_values( ld, e, "title" );
	}
	if (vals && vals[0]) {
	    if (bounce_text( e_addr->e_addr_errors, rdn, "\t", vals[0] ) != 0) {
		return;
	    }

	    for ( idx = 1; vals && vals[idx] != NULL; idx++ ) {
		if (bounce_text( e_addr->e_addr_errors, "\t\t", vals[idx], NULL)
			!= 0) {
		    return;
		}
	    }
	    ldap_value_free( vals );
	}
	free( rdn );
    }
}

    static void
do_noemail (struct exp_addr *e_addr, char *addr, LDAPMessage *res)
{

    int		idx;
    char	*dn; <commit_msg>print RDN of ambigious results, even if no title/description<commit_after>/*
 * Copyright (c) 2004 Regents of The University of Michigan.
 * All Rights Reserved.  See COPYRIGHT.
 */

/*****     simta_ldap.c     *****/

#include "config.h"

#ifdef TLS
#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#endif /* TLS */

#include <snet.h>

#include <sys/types.h>
#include <sys/wait.h>
#include <sys/param.h>

#include <sys/time.h>		/* struct timeval */
#include <stdio.h>
#include <syslog.h>
#include <ctype.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <ldap.h>
#include <unistd.h>
#include <errno.h>
#include <netdb.h>

#include "denser.h"
#include "ll.h"
#include "queue.h"
#include "envelope.h"
#include "simta.h"
#include "expand.h"
#include "argcargv.h"
#include "simta_ldap.h"
#include "dn.h"
#include "oklist.h"

#define	SIMTA_LDAP_CONF		"./simta_ldap.conf"

#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif
#define MAXRETRIES		5

#define MAXAMBIGUOUS		10
#define LDAP_TIMEOUT_VAL	180

/* MAXADDRESSLENGTH -- maximum length email address we're gonna process */
#define MAXADDRESSLENGTH	1024
/* ERRORFMTBUFLEN -- Error buffer length process max buffer size. */
#define ERRORFMTBUFLEN		2048

/* 
e* LDAP attribute names
** noattrs -- is a attribute list when no attributes are needed.
** allattrs -- is a attribute list when all attributes are wanted.  It
**             It is also the default attribute list if no attribute
**             config file directive found.
*/
static char	*noattrs[] =  {LDAP_NO_ATTRS, NULL};
static char	*allattrs[] = {"*", NULL};

static char     **attrs     = NULL;

/*
** ldap_search_list -- Contains a parsed uri from the config file.
*/
struct ldap_search_list {
    LDAPURLDesc			*lds_plud;	/* url parsed description */
    int				lds_rdn_pref;	/* TRUE / FALSE */
    int				lds_search_type;/* one of USER, GROUP, ALL */
    char			*lds_string;	/* uri string */
    struct ldap_search_list     *lds_next;	/* next uri */
};

struct ldap_search_list		*ldap_searches = NULL;
struct list			*ldap_people = NULL;
struct list			*ldap_groups = NULL;
LDAP				*ld = NULL;

static char			*ldap_host;
static int			ldap_port;
static time_t			ldap_timeout = LDAP_TIMEOUT_VAL;
static int			starttls;
static char			*binddn;
static char			*bindpw;

static int			ldapdebug;

static char			*vacationhost;
static char			*vacationattr;
static char			*mailfwdattr;

static int			ndomaincomponent = 2;
/*
** simta_ldap_message_stdout -- Dumps an entry to stdout
*/
    static int
simta_ldap_message_stdout( LDAPMessage *m )
{
    LDAPMessage		*entry;
    LDAPMessage		*message;
    char		*dn;
    char		*attribute;
    BerElement		*ber;
    char		**values;
    int			idx;

    if (( entry = ldap_first_entry( ld, m )) == NULL ) {
	ldap_perror( ld, "ldap_first_entry" );
	return( -1 );
    }

    if (( message = ldap_first_message( ld, m )) == NULL ) {
	ldap_perror( ld, "ldap_first_message" );
	return( -1 );
    }

    if (( dn = ldap_get_dn( ld, message )) == NULL ) {
	ldap_perror( ld, "ldap_get_dn" );
	return( -1 );
    }

    printf( "dn: %s\n", dn );
    ldap_memfree( dn );

    for ( attribute = ldap_first_attribute( ld, message, &ber );
          attribute != NULL;
          attribute = ldap_next_attribute( ld, message, ber )   ) {
    
	printf( "%s:\n", attribute );

	if (( values = ldap_get_values( ld, entry, attribute )) == NULL ) {
	    ldap_perror( ld, "ldap_get_values" );
	    return( -1 );
	}

	for ( idx = 0; values[ idx ] != NULL; idx++ ) {
	    printf( "	%s\n", values[ idx ] );
	}

	ldap_value_free( values );
    }

    ber_free( ber, 0 );

    return( 0 );
}

    static void 
simta_ldapdomain (char * buf, char ** domain)
{
    char *pbuf;
    int	 dotcnt = 0;

    pbuf = &buf [ strlen (buf) - 1 ];

    while (pbuf > buf) {
	if (*pbuf == '.') {
	    if (dotcnt == (ndomaincomponent - 1)) {
		pbuf++;
		break;
	    }
	    dotcnt++;
	}
	pbuf--;
    }
    *domain = strdup(pbuf);
    return;
}

    static void 
simta_ldapuser (char * buf, char ** user, char ** domain)
{
    char  *puser;

    *domain = NULL;
    *user = strdup (buf);

    puser = strchr(*user , '@');
    if (puser) {
	*puser = '\0';
	puser++;
	simta_ldapdomain (puser, domain);
    }
    else
	*domain = strdup("");  
    return;
}

/*
** Unbind from the directory.
*/
    static void
simta_ldap_unbind ()
{
    ldap_unbind( ld );
    ld = NULL;
    return;
}

    static int
simta_ldap_init ()
{
    int ldaprc;
    int	maxambiguous = MAXAMBIGUOUS;
    int protocol = LDAP_VERSION3;

    if ( ld == NULL ) {
	if (( ld = ldap_init( ldap_host, ldap_port )) == NULL ) {
	    syslog( LOG_ERR, "ldap_init: %m" );
	    return( LDAP_SYSERROR );
	}
	if ( ldapdebug ) {
	    ldaprc = ber_set_option( NULL, LBER_OPT_DEBUG_LEVEL, &ldapdebug );
	    if( ldaprc != LBER_OPT_SUCCESS ) {
		syslog( LOG_ERR, 
	"simta_ldap_init: Failed setting LBER_OPT_DEBUG_LEVEL=%d\n", ldapdebug);
	    }
	    ldaprc = ldap_set_option( NULL, LDAP_OPT_DEBUG_LEVEL, &ldapdebug );
	    if( ldaprc != LDAP_OPT_SUCCESS ) {
		syslog( LOG_ERR, 
	"simta_ldap_init: Failed setting LDAP_OPT_DEBUG_LEVEL=%d\n", ldapdebug);
	    }
	}
	ldaprc = ldap_set_option( ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
	if (ldaprc != LDAP_OPT_SUCCESS ) {
	    syslog( LOG_ERR, 
	"simta_ldap_init: Failed setting LDAP_OPT_REFERRALS to LDAP_OPT_OFF");
	    return( LDAP_SYSERROR );
	}
	ldaprc = ldap_set_option(ld, LDAP_OPT_SIZELIMIT, (void *)&maxambiguous);
	if (ldaprc != LDAP_OPT_SUCCESS ) {
	    syslog( LOG_ERR, 
		"simta_ldap_init: Failed setting LDAP_OPT_SIZELIMIT = %d\n",
		maxambiguous);
	    return( LDAP_SYSERROR );
	}
	ldaprc = ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &protocol );
	if( ldaprc != LDAP_OPT_SUCCESS ) {
	    syslog( LOG_ERR, 
	"simta_ldap_init: Failed setting LDAP_OPT_PROTOCOL_VERSION = %d\n",
		protocol );
	    return( LDAP_SYSERROR );
	}

	if ( starttls &&
	   ((ldaprc = ldap_start_tls_s( ld, NULL, NULL )) != LDAP_SUCCESS )) {
	    syslog( LOG_ERR, "ldap_start_tls: %s", ldap_err2string(ldaprc));
	    if ( starttls > 1 ) {
		return( LDAP_SYSERROR );
	    }
	}
    }
    if (binddn) {
	if (ldap_bind_s( ld, binddn, bindpw, LDAP_AUTH_SIMPLE) != LDAP_SUCCESS){
	    ldap_perror( ld, "ldap_bind" );
	    return( LDAP_SYSERROR );
	}
    }
    return (0);
}

/*
** This function looks thru the attribute "attr" values 
** for the first matching value in the "master" list
*/
    static  int
simta_ldap_value( LDAPMessage *e, char *attr, struct list *master )
{
    int			idx;
    char		**values;
    struct list		*l;

    if (( values = ldap_get_values( ld, e, attr )) != NULL ) {

	for ( idx = 0; values[ idx ] != NULL; idx++ ) {
	    for ( l = master ; l != NULL; l = l->l_next ) {
		if ( strcasecmp( values[ idx ], l->l_string ) == 0 ) {
		    ldap_value_free( values );
		    return ( 1 );
		}
	    }
	}
	ldap_value_free( values );
    }
    return( 0 );
}

    /* return a statically allocated string if all goes well, NULL if not.
     *
     *     - Build search string where:
     *         + %s -> username
     *         + %h -> hostname
     */

    static char *
simta_ldap_string( char *filter, char *user, char *domain )
{
    size_t		len;
    static size_t	buf_len = 0;
    static char		*buf = NULL;
    char		*c;
    char		*d;
    char		*insert;
    size_t		place;

    /* make sure buf is big enough search url */
    if (( len = strlen( filter ) + 1 ) > buf_len ) {
	if (( buf = (char*)realloc( buf, len )) == NULL ) {
	    syslog( LOG_ERR, "realloc: %m" );
	    return( NULL );
	}

	buf_len = len;
    }

    d = buf;
    c = filter;

    while ( *c != '\0' ) {
	switch ( *c ) {
	case '%':
	    switch ( * ( c + 1 )) {
		case 's':
		    /* if needed, resize buf to handle upcoming insert */
		    if (( len += strlen( user )) > buf_len ) {
			place = d - buf;
			if (( buf = (char*)realloc( buf, len )) == NULL ) {
			    syslog( LOG_ERR, "realloc: %m" );
			    return( NULL );
			}
			d = buf + place;
			buf_len = len;
		    }

		    /* insert word */
		    for ( insert = user; *insert != '\0'; insert++ ) {
			if (( *insert == '.' ) || ( *insert == '_' )) {
			    *d = ' ';
			} else {
			    *d = *insert;
			}
			d++;
		    }

		    /* advance read cursor */
		    c += 2;
		    break;

		case 'h':
		    /* if needed, resize buf to handle upcoming insert */
		    if (( len += strlen( domain )) > buf_len ) {
			place = d - buf;
			if (( buf = (char*)realloc( buf, len )) == NULL ) {
			    syslog( LOG_ERR, "realloc: %m" );
			    return( NULL );
			}
			d = buf + place;
			buf_len = len;
		    }

		    /* insert word */
		    for ( insert = domain; *insert != '\0'; insert++ ) {
			*d = *insert;
			d++;
		    }

		    /* advance read cursor */
		    c += 2;
		    break;

		case '%':
		    /* %% -> copy single % to data buffer */
		    *d = *c;
		    /* advance cursors */
		    c += 2;
		    d++;
		    break;

		default:
		    c++;
		    break;
	    }
	    break;

	default:
	    /* raw character, copy to data buffer */
	    *d = *c;

	    /* advance cursors */
	    d++;
	    c++;
	    break;
	}
    }

    *d = '\0';

    return( buf );
}


    /* this function should return:
     *     LDAP_SYSERROR if there is an error
     *     LDAP_LOCAL if addr is found in the db
     *     LDAP_NOT_LOCAL if addr is not found in the db
     */

    static int
simta_local_search (char ** attrs, char * user, char * domain, int *count)
{
    char		*search_string;
    struct ldap_search_list *lds;
    LDAPMessage		*res;
    int			rc;
    struct timeval	timeout;

    *count = 0;
    /* for each base string in ldap_searches:
     *     - Build search string
     *     - query the LDAP db with the search string
     */
    for ( lds = ldap_searches; lds != NULL; lds = lds->lds_next ) {
	search_string = simta_ldap_string( lds->lds_plud->lud_filter, 
			user, domain);
	if ( search_string == NULL ) 
	    return( LDAP_SYSERROR );

	timeout.tv_sec = ldap_timeout;
	timeout.tv_usec = 0;
	res = NULL;
	rc = ldap_search_st( ld, lds->lds_plud->lud_dn, 
			lds->lds_plud->lud_scope, search_string, attrs, 0, 
			&timeout, &res );
	if ( rc != LDAP_SUCCESS && rc != LDAP_SIZELIMIT_EXCEEDED ) {
	    syslog( LOG_ERR, 
	"simta_local_search: ldap_search_st Failed: %s", ldap_err2string(rc ));
		
	    ldap_msgfree( res );

	    simta_ldap_unbind (ld);
	    return( LDAP_SYSERROR );
	}

	*count = ldap_count_entries( ld, res );
	ldap_msgfree( res );
	if (*count)
	    break;
    }
    return (0);
}

/*
** Looks at the incoming email address
** looking for "-errors", "-requests", or "-owners"
**
*/
    static int 
simta_address_type (char * address)
{
    int    addrtype;
    char   *paddr = address;

    
    addrtype = LDS_USER;  /* default */
    if ( (paddr = strrchr ( address, '-')) != NULL )
    {
        paddr++;
        if ((strcasecmp(paddr, ERROR) == 0) ||
            (strcasecmp(paddr, ERRORS) == 0)) {
            addrtype = LDS_GROUP_ERRORS;
            *(--paddr) = '\0';
        } else if ((strcasecmp(paddr, REQUEST) == 0) ||
                   (strcasecmp(paddr, REQUESTS) == 0)) {
            addrtype = LDS_GROUP_REQUEST;
            *(--paddr) = '\0';
        } else if ((strcasecmp(paddr, OWNER) == 0) ||
                   (strcasecmp(paddr, OWNERS) == 0)) {
            addrtype = LDS_GROUP_OWNER;
            *(--paddr) = '\0';
        }
    }

    return (addrtype);
}

    static void
do_ambiguous (struct exp_addr *e_addr, char *addr, LDAPMessage *res)
{

    int		idx;
    char	*dn; 
    char	*rdn;
    char	**ufn;
    char	**vals;
    LDAPMessage	*e;

    if ( bounce_text( e_addr->e_addr_errors, addr, 
				": Ambigous user", NULL ) != 0 ) {
	return;
    }

    for ( e = ldap_first_entry( ld, res ); e != NULL;
	  e = ldap_next_entry( ld, e ) ) {
	dn = ldap_get_dn( ld, e );
	ufn = ldap_explode_dn( dn, 1 );
	rdn = strdup( ufn[0] );
	ldap_value_free( ufn );
	free( dn );

	if ( strcasecmp( rdn, addr ) == 0 ) {
	    if ( (vals = ldap_get_values( ld, e, "cn" )) != NULL ) {
		rdn = strdup( vals[0] );
		ldap_value_free( vals );
	    }
	}
	if (ldap_groups 
	&& ( simta_ldap_value( e, "objectClass", ldap_groups) > 0 )) {
	    vals = ldap_get_values( ld, e, "description" );
	} else {
	    vals = ldap_get_values( ld, e, "title" );
	}
	if (vals && vals[0]) {
	    if (bounce_text( e_addr->e_addr_errors, rdn, "\t", vals[0] ) != 0) {
		return;
	    }

	    for ( idx = 1; vals && vals[idx] != NULL; idx++ ) {
		if (bounce_text( e_addr->e_addr_errors, "\t\t", vals[idx], NULL)
			!= 0) {
		    return;
		}
	    }
	    ldap_value_free( vals );
	} else {
	    if (bounce_text( e_addr->e_addr_errors, rdn, NULL, NULL ) != 0) {
		return;
	}
	free( rdn );
    }
}

    static void
do_noemail (struct exp_addr *e_addr, char *addr, LDAPMessage *res)
{

    int		idx;
    char	*dn; 
{'fraction_non_alphanumeric': 0.14647299637852307, 'fraction_numerical': 0.0059439458353015276, 'mean_word_length': 2.4541128484024473, 'pattern_counts': {'":': 2, '<': 39, '<?xml version=': 0, '>': 82, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '50683', 'n_tokens_mistral': 10505, 'n_tokens_neox': 9727, 'n_words': 2477}