/*
 * Copyright (c) 2000 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * Portions Copyright (c) 2000 Apple Computer, Inc.  All Rights
 * Reserved.  This file contains Original Code and/or Modifications of
 * Original Code as defined in and that are subject to the Apple Public
 * Source License Version 1.1 (the "License").  You may not use this file
 * except in compliance with the License.  Please obtain a copy of the
 * License at http://www.apple.com/publicsource and read it before using
 * this file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON- INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
/*
 * Copyright (C) 1990 by NeXT, Inc. All rights reserved.
 *
 * Modified July 2000 by Luke Howard <lukeh@darwin.apple.com>
 * to support constructing a fake X.500 DIT from a NetInfo
 * hierarchy. 
 *
 * Modified August 2000 to remove code common to network.c.
 */

#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/param.h>
#include <stdio.h>
#include <string.h>
#include <netinfo/ni.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/ioctl.h>

#include <NetInfo/network.h>
#include <NetInfo/nilib2.h>
#include <NetInfo/dsutil.h>
#include <NetInfo/dsx500.h>
#include <NetInfo/dsx500dit.h>
#include <NetInfo/dsengine.h>

#define ni_name_match_seg(a, b, len) (strncmp(a, b, len) == 0 && (a)[len] == 0)

static const char NAME_NAME[] = "name";
static const char NAME_MASTER[] = "master";
static const char NAME_MACHINES[] = "machines";
static const char NAME_IP_ADDRESS[] = "ip_address";
static const char NAME_SERVES[] = "serves";
static const char NAME_SUFFIX[] = "suffix";
static const char NAME_OU[] = "ou=";
static const char NAME_UNKNOWN[] = "ou=###UNKNOWN###";
static const char NAME_DOTDOT[] = "..";
static const char NAME_DOT[] = ".";

static ni_name
escape_domain(ni_name name)
{
	int extra;
	char *p;
	char *s;
	ni_name newname;

	extra = sizeof(NAME_OU) - 1;
	for (p = name; *p; p++)
	{
		if ((*p == '/') || (*p == '\\')) extra++;
	}
	
	newname = malloc(strlen(name) + extra + 1);
	strcpy(newname, NAME_OU);
	s = newname + sizeof(NAME_OU) - 1;
	for (p = name; *p; p++)
	{
		if ((*p == '/') || (*p == '\\')) *s++ = '\\';
		*s++ = *p;
	}

	*s = 0;
	return newname;
}

static char *
finddomain(void *ni, struct in_addr addr, ni_name tag)
{
	ni_id nid;
	ni_idlist idl;
	ni_namelist nl_serves, nl_suffix, *pnl_suffix;
	ni_index i;
	ni_name slash;
	ni_status status;
	char *domain;

	status = ni_root(ni, &nid);
	if (status != NI_OK) return NULL;

	status = ni_lookup(ni, &nid, NAME_NAME, NAME_MACHINES, &idl);
	if (status != NI_OK) return NULL;

	nid.nii_object = idl.niil_val[0];
	ni_idlist_free(&idl);

	status = ni_lookup(ni, &nid, NAME_IP_ADDRESS, inet_ntoa(addr), &idl);
	if (status != NI_OK) return NULL;

	nid.nii_object = idl.niil_val[0];
	ni_idlist_free(&idl);

	status = ni_lookupprop(ni, &nid, NAME_SERVES, &nl_serves);
	if (status != NI_OK) return NULL;

	status = ni_lookupprop(ni, &nid, NAME_SUFFIX, &nl_suffix);
	if (status == NI_OK) pnl_suffix = &nl_suffix;
	else pnl_suffix = NULL;

	for (i = 0; i < nl_serves.ninl_len; i++)
	{
		slash = rindex(nl_serves.ninl_val[i], '/');
		if (slash == NULL) continue;

		if (ni_name_match(slash + 1, tag))
		{
			/* If we've got a value in "suffix" at this index, use instead */
			if (pnl_suffix != NULL &&
				pnl_suffix->ninl_len > i
				&& pnl_suffix->ninl_val[i][0] != '\0')
			{
				domain = copyString(pnl_suffix->ninl_val[i]);
			}
			else
			{
				*slash = '\0';
				domain = escape_domain(nl_serves.ninl_val[i]);
			}
			ni_namelist_free(&nl_serves);
			if (pnl_suffix) ni_namelist_free(pnl_suffix);
			return domain;
		}
	}

	ni_namelist_free(&nl_serves);
	if (pnl_suffix) ni_namelist_free(pnl_suffix);

	return NULL;
}

static char *
dsx500dit_domainof(void *ni, void *parent)
{
	struct sockaddr_in addr;
	ni_name tag;
	ni_name dom;
	ni_status status;
	interface_list_t *ilist;
	int i;

	status = ni_addrtag(ni, &addr, &tag);
	if (status != NI_OK) return ni_name_dup(NAME_UNKNOWN);
	
	dom = finddomain(parent, addr.sin_addr, tag);
	if (dom != NULL)
	{
		ni_name_free(&tag);
		return dom;
	}

	if (sys_is_my_address(&(addr.sin_addr)))
	{
		/* Try all my non-loopback interfaces */
		ilist = sys_interfaces();
		if (ilist == NULL) return ni_name_dup(NAME_UNKNOWN);

		for (i = 0; i < ilist->count; i++)
		{
			if (ilist->interface[i].addr.s_addr == htonl(INADDR_LOOPBACK)) continue;
	
			addr.sin_addr.s_addr = ilist->interface[i].addr.s_addr;
			dom = finddomain(parent, addr.sin_addr, tag);
			if (dom != NULL)
			{
				ni_name_free(&tag);
				return dom;
			}
		}
	}

	dom = malloc(strlen(tag) + 256);
	sprintf(dom, "%s%s@%s", NAME_OU, tag, inet_ntoa(addr.sin_addr));
	ni_name_free(&tag);
	return dom;
}

static char *
mastersuffix(void *ni)
{
	ni_id nid;
	ni_idlist idl;
	ni_namelist nl, nl_suffix;
	ni_index i;
	ni_name slash, machine = NULL, tag = NULL;
	ni_status status;
	char *domain;

	status = ni_root(ni, &nid);
	if (status != NI_OK) return NULL;

	/* read master property */
	status = ni_lookupprop(ni, &nid, NAME_MASTER, &nl);
	if (status != NI_OK || nl.ninl_len == 0) return NULL;

	/* get machine and tag */
	slash = rindex(nl.ninl_val[0], '/');
	if (slash == NULL) return NULL;

	*slash = 0;
	machine = ni_name_dup(nl.ninl_val[0]);
	tag = ni_name_dup(slash + 1);
	ni_namelist_free(&nl);

	status = ni_lookup(ni, &nid, NAME_NAME, NAME_MACHINES, &idl);
	if (status != NI_OK) 
	{
		ni_name_free(&machine);
		ni_name_free(&tag);
		return NULL;
	}

	nid.nii_object = idl.niil_val[0];
	ni_idlist_free(&idl);

	status = ni_lookup(ni, &nid, NAME_NAME, machine, &idl);
	if (status != NI_OK) 
	{
		ni_name_free(&machine);
		ni_name_free(&tag);
		return NULL;
	}

	nid.nii_object = idl.niil_val[0];
	ni_idlist_free(&idl);

	status = ni_lookupprop(ni, &nid, NAME_SERVES, &nl);
	if (status != NI_OK) 
	{
		ni_name_free(&machine);
		ni_name_free(&tag);
		return NULL;
	}

	status = ni_lookupprop(ni, &nid, NAME_SUFFIX, &nl_suffix);
	if (status != NI_OK)
	{
		ni_namelist_free(&nl);
		ni_name_free(&machine);
		ni_name_free(&tag);
		return NULL;
	}

	domain = NULL;

	for (i = 0; i < nl.ninl_len; i++)
	{
		slash = rindex(nl.ninl_val[i], '/');
		if (slash == NULL) continue;

		if (ni_name_match(slash + 1, tag))
		{
			if (nl_suffix.ninl_len > i && nl_suffix.ninl_val[i][0] != '\0')
			{
				/* leaks ??? */
				domain = copyString(nl_suffix.ninl_val[i]);
			}
			/* no use reading the serves value because it will be "." */
			break;
		}
	}

	ni_namelist_free(&nl);
	ni_namelist_free(&nl_suffix);

	ni_name_free(&machine);
	ni_name_free(&tag);

	return domain;
}

static ni_status
dsx500dit_pwdomain(void *ni, ni_name *buf)
{
	void *nip;
	ni_status status;
	char *dom, *parent;

	/* Open parent domain */
	nip = ni_new(ni, "..");
	if (nip == NULL)
	{
		/* We're at the root; read suffix from master's host */
		*buf = mastersuffix(ni);
		if (*buf == NULL) *buf = copyString("");
		return NI_OK;
	}

	/* Get parent's name */
	status = dsx500dit_pwdomain(nip, &parent);
	if (status != NI_OK)
	{
		ni_free(nip);
		return status;
	}

	/* Get my name relative to my parent */
	dom = dsx500dit_domainof(ni, nip);

	if (parent[0] == '\0')
	{
		*buf = dom;
	}
	else
	{
		/* Prepend my relative name to my parent's name */
		*buf = dsx500_make_dn(parent, dom);
		ni_name_free(&dom);
	}

	ni_name_free(&parent);
	ni_free(nip);

	return NI_OK;
}

static dsdata *
servesdotdot(void *ni, ni_entry entry)
{
	ni_name name, sep;
	ni_namelist nl;
	ni_index i;
	ni_id id;
	char *ref;
	dsdata *ret;

	if (entry.names == NULL) return NULL;

	id.nii_object = entry.id;
	for (i = 0; i < entry.names->ninl_len; i++)
	{
		name = entry.names->ninl_val[i];
		sep = index(name, '/');
		if (sep == NULL) continue;

		if (!ni_name_match_seg(NAME_DOTDOT, name, sep - name)) continue;

		NI_INIT(&nl);
		if (ni_lookupprop(ni, &id, NAME_NAME, &nl) != NI_OK) continue;
		if (nl.ninl_len == 0)
		{
			ni_namelist_free(&nl);
			continue;
		}

		ref = (char *)malloc(sizeof("ldap:///") + strlen(nl.ninl_val[0]));
		sprintf(ref, "ldap://%s/", nl.ninl_val[0]);
		ni_namelist_free(&nl);

		ret = cstring_to_dsdata(ref);
		free(ref);
		return ret;
	}

	return NULL;
}

static ni_status
dsx500dit_getparents(void *ni, char *parent_suffix, dsattribute **refs)
{
	ni_id nid;
	ni_idlist idl;
	ni_entrylist entries;
	ni_index i;
	ni_status status;

	*refs = NULL;

	if (parent_suffix == NULL)
	{
		/* We are root. */
		return NI_OK;
	}

	status = ni_root(ni, &nid);
	if (status != NI_OK) return status;

	status = ni_lookup(ni, &nid, NAME_NAME, NAME_MACHINES, &idl);
	if (status != NI_OK) return status;

	/* based on ni_prot_proc.c:hardwired() */
	nid.nii_object = idl.niil_val[0];
	ni_idlist_free(&idl);

	status = ni_list(ni, &nid, NAME_SERVES, &entries);
	if (status != NI_OK) return status;

	for (i = 0; i < entries.niel_len; i++)
	{
		dsdata *ref;

		ref = servesdotdot(ni, entries.niel_val[i]);
		if (ref != NULL)
		{
			if (*refs == NULL)
			{
				dsdata *key = cstring_to_dsdata(parent_suffix);

				*refs = dsattribute_new(key);
				dsdata_release(key);
			}

			dsattribute_append(*refs, ref);
			dsdata_release(ref);
		}
	}

	ni_entrylist_free(&entries);

	return NI_OK;
}

static ni_status
serveschild(void *nip, char *base, ni_entry entry, dsattribute ***refs, u_int32_t *count)
{
	ni_name name, sep;
	ni_index i;
	ni_id id;
	ni_status status;
	ni_namelist nl_suffix, nl_name, *pnl_suffix;

	*count = 0;
	*refs = NULL;

	if (entry.names == NULL) return NI_FAILED;

	id.nii_object = entry.id;

	NI_INIT(&nl_name);
	status = ni_lookupprop(nip, &id, NAME_NAME, &nl_name);
	if (status != NI_OK) return status;

	if (nl_name.ninl_len == 0)
	{
		ni_namelist_free(&nl_name);
		return NI_NONAME;
	}

	NI_INIT(&nl_suffix);
	status = ni_lookupprop(nip, &id, NAME_SUFFIX, &nl_suffix);
	if (status == NI_OK) pnl_suffix = &nl_suffix;
	else pnl_suffix = NULL;

	for (i = 0; i < entry.names->ninl_len; i++)
	{
		char *url, *domain, *dn;
		dsdata *_url, *_key;

		name = entry.names->ninl_val[i];
		sep = index(name, '/');
		if (sep == NULL) continue;

		/* skip parent domains */
		if (ni_name_match_seg(NAME_DOTDOT, name, sep - name)) continue;
		/* skip local domain */
		if (ni_name_match_seg(NAME_DOT, name, sep - name)) continue;

		if (pnl_suffix != NULL && pnl_suffix->ninl_len > i && pnl_suffix->ninl_val[i][0] != '\0')
		{
			domain = copyString(pnl_suffix->ninl_val[i]);
		}
		else
		{
			*sep = '\0';
			domain = escape_domain(entry.names->ninl_val[i]);
		}

		dn = dsx500_make_dn(base, domain);
		_key = cstring_to_dsdata(dn);
		free(domain);
		free(dn);

		url = (char *)malloc(sizeof("ldap:///") + strlen(nl_name.ninl_val[0]));
		sprintf(url, "ldap://%s/", nl_name.ninl_val[0]);
		_url = cstring_to_dsdata(url);
		free(url);

		if (*count == 0)
			*refs = (dsattribute **)malloc(sizeof(dsattribute *));
		else
			*refs = (dsattribute **)realloc(*refs, (*count + 1) * sizeof(dsattribute *));

		(*refs)[*count] = dsattribute_new(_key);
		dsattribute_append((*refs)[*count], _url);
		dsdata_release(_key);
		dsdata_release(_url);

		(*count)++;
	}


	ni_namelist_free(&nl_name);
	if (pnl_suffix) ni_namelist_free(pnl_suffix);

	return NI_OK;
}

static ni_status
dsx500dit_getchildren(void *ni, char *suffix, dsattribute ***refs, u_int32_t *count)
{
	ni_id nid;
	ni_idlist idl;
	ni_entrylist entries;
	ni_index i;
	ni_status status;
	dsrecord *r;

	*refs = NULL;
	*count = 0;

	status = ni_root(ni, &nid);
	if (status != NI_OK) return status;

	status = ni_lookup(ni, &nid, NAME_NAME, NAME_MACHINES, &idl);
	if (status != NI_OK) return status;

	/* based on ni_prot_proc.c:hardwired() */
	nid.nii_object = idl.niil_val[0];
	ni_idlist_free(&idl);
	status = ni_list(ni, &nid, NAME_SERVES, &entries);
	if (status != NI_OK) return status;

	r = dsrecord_new();
	if (r == NULL) return NI_FAILED;

	for (i = 0; i < entries.niel_len; i++)
	{
		dsattribute **_refs;
		u_int32_t _count, j;

		/* return a set of { namingcontext, referral } attributes */
		if (serveschild(ni, suffix, entries.niel_val[i], &_refs, &_count) == NI_OK)
		{
			for (j = 0; j < _count; j++)
			{
				dsrecord_merge_attribute(r, _refs[j], SELECT_ATTRIBUTE);
				dsattribute_release(_refs[j]);
			}
			free(_refs);
		}
	}

	ni_entrylist_free(&entries);

	/* ownership switcheroo */

	*refs = r->attribute;
	r->attribute = NULL;

	*count = r->count;
	r->count = 0;

	dsrecord_release(r);

	return *count ? NI_OK : NI_FAILED;
}

dsx500dit *dsx500dit_retain(dsx500dit *i)
{
	if (i == NULL) return i;
	i->retain++;
	return i;
}

void dsx500dit_release(dsx500dit *info)
{
	u_int32_t i;

	if (info == NULL) return;

	info->retain--;
	if (info->retain > 0) return;

	dsdata_release(info->local_suffix);

	if (info->parent_referrals != NULL)
	{
		dsattribute_release(info->parent_referrals);
	}

	if (info->child_referrals != NULL)
	{
		for (i = 0; i < info->child_count; i++)
		{
			dsattribute_release(info->child_referrals[i]);
		}
		free(info->child_referrals);
	}

	free(info);
}

dsx500dit *dsx500dit_new(dsengine *s)
{
	void *ni, *nip;
	int freeit = 0, status;
	char *local_suffix, *parent_suffix;
	dsx500dit *info;

	if (s == NULL) return NULL;

	info = (dsx500dit *)malloc(sizeof(dsx500dit));
	if (info == NULL) return NULL;

	info->local_suffix = NULL;
	info->parent_referrals = NULL;
	info->child_count = 0;
	info->child_referrals = NULL;
	info->retain = 1;

	if (s->store->flags & DSSTORE_FLAGS_REMOTE_NETINFO)
	{
		ni = s->store->index[0];
	}
	else
	{
		dsdata *k, *v;
		dsattribute *a;
		dsrecord *r;
		char *tag, *p;
		struct sockaddr_in server;

		status = dsengine_fetch(s, 0, &r);
		if (status != DSStatusOK)
		{
			dsx500dit_release(info);
			return NULL;
		}

		k = cstring_to_dsdata((char *)NAME_MASTER);
		a = dsrecord_attribute(r, k, SELECT_ATTRIBUTE);
		dsdata_release(k);
		v = dsattribute_value(a, 0);
		dsattribute_release(a);
		p = dsdata_to_cstring(v);

		ni = NULL;

		if (p)
		{
			if (ni_parse_server_tag(p, &server, &tag) == NI_PARSE_OK)
			{
				ni = ni_connect(&server, tag);
			}
		}
		
		dsdata_release(v);
		dsrecord_release(r);

		/* Couldn't parse master attribute. */
		if (ni == NULL)
		{
			info->local_suffix = cstring_to_dsdata("");
			return info;
		}

		freeit = 1;
	}

	/* Open parent domain */
	nip = ni_new(ni, "..");
	if (nip == NULL)
	{
		/* We are root. */
		local_suffix = mastersuffix(ni);
		if (local_suffix == NULL) local_suffix = copyString("");
		parent_suffix = NULL;
	}
	else
	{
		char *dom;

		/* Get parent's name */
		if (dsx500dit_pwdomain(nip, &parent_suffix) != NI_OK)
		{
			dsx500dit_release(info);
			return NULL;
		}

		dom = dsx500dit_domainof(ni, nip);
		if (parent_suffix[0] == '\0')
		{
			local_suffix = dom;
		}
		else
		{
			local_suffix = dsx500_make_dn(parent_suffix, dom);
			ni_name_free(&dom);
		}

		ni_free(nip);
	}

	info->local_suffix = cstring_to_dsdata(local_suffix);

	(void) dsx500dit_getparents(ni, parent_suffix, &info->parent_referrals);
	(void) dsx500dit_getchildren(ni, local_suffix, &info->child_referrals, &info->child_count);

	ni_name_free(&local_suffix);
	ni_name_free(&parent_suffix);

	if (freeit) 
	{
		ni_free(ni);
	}

	return info;
}
