/* $OpenLDAP: pkg/ldap/libraries/libldap/dnssrv.c,v 1.15 2002/01/07 19:18:38 kurt Exp $ */
/*
 * Copyright 1998-2002 The OpenLDAP Foundation, All Rights Reserved.
 * COPYING RESTRICTIONS APPLY, see COPYRIGHT file
 */

/*
 * locate LDAP servers using DNS SRV records.
 * Location code based on MIT Kerberos KDC location code.
 */
#include "portable.h"

#include <stdio.h>

#include <ac/stdlib.h>

#include <ac/param.h>
#include <ac/socket.h>
#include <ac/string.h>
#include <ac/time.h>

#include "ldap-int.h"

#ifdef HAVE_ARPA_NAMESER_H
#include <arpa/nameser.h>
#endif
#ifdef HAVE_RESOLV_H
#include <resolv.h>
#endif

/* Sometimes this is not defined. */
#ifndef T_SRV
#define T_SRV            33
#endif				/* T_SRV */

int ldap_dn2domain(
	LDAP_CONST char *dn_in,
	char **domainp)
{
	int i;
	char *domain = NULL;
	char **dn;

	if( dn_in == NULL || domainp == NULL ) {
		return -1;
	}

	dn = ldap_explode_dn( dn_in, 0 );

	if( dn == NULL ) {
		return -2;
	}

	for( i=0; dn[i] != NULL; i++ ) {
		char ** rdn = ldap_explode_rdn( dn[i], 0 );

		if( rdn == NULL || *rdn == NULL ) {
			LDAP_FREE( rdn );
			LDAP_FREE( domain );
			LDAP_VFREE( dn );
			return -3;
		}


		if( rdn[1] == NULL ) {
			/*
			 * single-valued RDN
			 */
			char *dc;

#define LDAP_DC "dc="
#define LDAP_DCOID "0.9.2342.19200300.100.1.25="

			if( strncasecmp( rdn[0],
				LDAP_DC, sizeof(LDAP_DC)-1 ) == 0 )
			{
				dc = &rdn[0][sizeof(LDAP_DC)-1];

			} else if( strncmp( rdn[0],
				LDAP_DCOID, sizeof(LDAP_DCOID)-1 ) == 0 )
			{
				dc = &rdn[0][sizeof(LDAP_DCOID)-1];

			} else {
				dc = NULL;
			}

			if( dc != NULL ) {
				char *ndomain;

				if( *dc == '\0' ) {
					/* dc value is empty! */
					LDAP_FREE( rdn );
					LDAP_FREE( domain );
					LDAP_VFREE( dn );
					LDAP_VFREE( rdn );
					return -4;
				}

				ndomain = LDAP_REALLOC( domain,
					( domain == NULL ? 0 : strlen(domain) )
					+ strlen(dc) + sizeof(".") );

				if( ndomain == NULL ) {
					LDAP_FREE( rdn );
					LDAP_FREE( domain );
					LDAP_VFREE( dn );
					LDAP_VFREE( rdn );
					return -5;
				}

				if( domain == NULL ) {
					ndomain[0] = '\0';
				} else {
					strcat( ndomain, "." );
				}

				strcat( ndomain, dc );

				domain = ndomain;
				continue;
			}
		}

		/*
		 * multi-valued RDN or fall thru
		 */

		LDAP_VFREE( rdn );
		LDAP_FREE( domain );
		domain = NULL;
	} 

	if( domain != NULL &&  *domain == '\0' ) {
		LDAP_FREE( domain );
		domain = NULL;
	}

	*domainp = domain;
	return 0;
}

int ldap_domain2dn(
	LDAP_CONST char *domain_in,
	char **dnp)
{
    char *domain, *s, *tok_r, *dn;
    size_t loc;

    if (domain_in == NULL || dnp == NULL) {
	return LDAP_NO_MEMORY;
    }
    domain = LDAP_STRDUP(domain_in);
    if (domain == NULL) {
	return LDAP_NO_MEMORY;
    }
    dn = NULL;
    loc = 0;

    for (s = ldap_pvt_strtok(domain, ".", &tok_r);
	 s != NULL;
	 s = ldap_pvt_strtok(NULL, ".", &tok_r)) {
	size_t len = strlen(s);

	dn = (char *) LDAP_REALLOC(dn, loc + sizeof(",dc=") + len );
	if (dn == NULL) {
	    LDAP_FREE(domain);
	    return LDAP_NO_MEMORY;
	}
	if (loc > 0) {
	    /* not first time. */
	    strcpy(dn + loc, ",");
	    loc++;
	}
	strcpy(dn + loc, "dc=");
	loc += sizeof("dc=")-1;

	strcpy(dn + loc, s);
	loc += len;
    }

    LDAP_FREE(domain);

    *dnp = dn;

    return LDAP_SUCCESS;
}

/*
 * Lookup and return LDAP servers for domain (using the DNS
 * SRV record _ldap._tcp.domain).
 */
int ldap_domain2hostlist(
	LDAP_CONST char *domain,
	char **list )
{
#ifdef HAVE_RES_QUERY
    char *request;
    char *hostlist = NULL;
    int rc, len, cur = 0;
    unsigned char reply[1024];

	if( domain == NULL || *domain == '\0' ) {
		return LDAP_PARAM_ERROR;
	}

	if( list == NULL ) {
		return LDAP_PARAM_ERROR;
	}

    request = LDAP_MALLOC(strlen(domain) + sizeof("_ldap._tcp."));
    if (request == NULL) {
		return LDAP_NO_MEMORY;
    }
    sprintf(request, "_ldap._tcp.%s", domain);

#ifdef LDAP_R_COMPILE
    ldap_pvt_thread_mutex_lock(&ldap_int_resolv_mutex);
#endif

    rc = LDAP_UNAVAILABLE;
    len = res_query(request, C_IN, T_SRV, reply, sizeof(reply));
    if (len >= 0) {
	unsigned char *p;
	char host[1024];
	int status;
	u_short port;
	/* int priority, weight; */

	/* Parse out query */
	p = reply;
	p += sizeof(HEADER);
	status = dn_expand(reply, reply + len, p, host, sizeof(host));
	if (status < 0) {
	    goto out;
	}
	p += status;
	p += 4;

	while (p < reply + len) {
	    int type, class, ttl, size;
	    status = dn_expand(reply, reply + len, p, host, sizeof(host));
	    if (status < 0) {
		goto out;
	    }
	    p += status;
	    type = (p[0] << 8) | p[1];
	    p += 2;
	    class = (p[0] << 8) | p[1];
	    p += 2;
	    ttl = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
	    p += 4;
	    size = (p[0] << 8) | p[1];
	    p += 2;
	    if (type == T_SRV) {
		int buflen;
		status = dn_expand(reply, reply + len, p + 6, host, sizeof(host));
		if (status < 0) {
		    goto out;
		}
		/* ignore priority and weight for now */
		/* priority = (p[0] << 8) | p[1]; */
		/* weight = (p[2] << 8) | p[3]; */
		port = (p[4] << 8) | p[5];

		buflen = strlen(host) + sizeof(":65355");
		hostlist = (char *) LDAP_REALLOC(hostlist, cur + buflen);
		if (hostlist == NULL) {
		    rc = LDAP_NO_MEMORY;
		    goto out;
		}
		if (cur > 0) {
		    /* not first time around */
		    hostlist[cur++] = ' ';
		}
		cur += sprintf(&hostlist[cur], "%s:%hd", host, port);
	    }
	    p += size;
	}
    }
    if (hostlist == NULL) {
	/* No LDAP servers found in DNS. */
	rc = LDAP_UNAVAILABLE;
	goto out;
    }

    rc = LDAP_SUCCESS;
	*list = hostlist;

  out:
#ifdef LDAP_R_COMPILE
    ldap_pvt_thread_mutex_unlock(&ldap_int_resolv_mutex);
#endif

    if (request != NULL) {
	LDAP_FREE(request);
    }
    if (rc != LDAP_SUCCESS && hostlist != NULL) {
	LDAP_FREE(hostlist);
    }
    return rc;
#else
    return LDAP_NOT_SUPPORTED;
#endif /* HAVE_RES_QUERY */
}
