/*
 * Copyright (c) 1999 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident   "@(#)SLPUtils.c	1.1	99/04/02 SMI"

/*
 * Public utilities and convenience calls (from the API spec):
 *	SLPFindScopes (queries for all known scopes)
 *	SLPEscape / Unescape
 *	SLPFree
 *	SLPSet/GetProperty
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <netdb.h>
#include <unistd.h>
#include <libintl.h>
#include <slp-internal.h>

static struct scopes_tree {
	void *scopes;
	int len;
};

typedef SLPBoolean SLPScopeCallback(SLPHandle, const char *, SLPError, void *);

static SLPSrvURLCallback collate_scopes;
static void collect_scopes(void *, VISIT, int, void *);
static SLPBoolean unpackSAAdvert_scope(slp_handle_impl_t *, char *,
					SLPScopeCallback, void *,
					void **, int *);
static SLPError SAAdvert_for_scopes(SLPHandle, void **);
static SLPError slp_unescape(const char *, char **, SLPBoolean, const char);

/*
 * Finds scopes according the the user administrative model.
 */
SLPError SLPFindScopes(SLPHandle hSLP, char **ppcScopes) {
	SLPError err;
	char *reply, *unesc_reply;
	void *stree = NULL;
	void *collator = NULL;

	if (!hSLP || !ppcScopes) {
		return (SLP_PARAMETER_BAD);
	}

	/* first try administratively configured scopes */
	if ((err = slp_administrative_scopes(ppcScopes, SLP_FALSE))
	    != SLP_OK) {
		return (err);
	}

	if (*ppcScopes) {
	    /* got scopes */
	    return (SLP_OK);
	}

	/* DAs from active and passive discovery */
	if ((err = slp_find_das("", &reply)) != SLP_OK &&
	    err != SLP_NETWORK_ERROR)
		return (err);

	/* Unpack the reply */
	if (reply) {
		int numResults = 0;	/* placeholder; not actually used */

		/* tag call as internal */
		((slp_handle_impl_t *)hSLP)->internal_call = SLP_TRUE;

		(void) slp_unpackSrvReply(
			hSLP, reply, collate_scopes,
			&stree, &collator, &numResults);
		/* invoke last call */
		(void) slp_unpackSrvReply(
			hSLP, NULL, collate_scopes,
			&stree, &collator, &numResults);
		free(reply);

		/* revert internal call tag */
		((slp_handle_impl_t *)hSLP)->internal_call = SLP_FALSE;
	}

	/* Finally, if no results yet, try SA discovery */
	if (!stree) {
	    (void) SAAdvert_for_scopes(hSLP, &stree);
	}

	if (!stree) {
		/* found none, so just return "default" */
		if (!(*ppcScopes = strdup("default"))) {
			slp_err(LOG_CRIT, 0, "SLPFindScopes", "out of memory");
			return (SLP_MEMORY_ALLOC_FAILED);
		}
		return (SLP_OK);
	}

	/* we now have a btree, each leaf of which is a unique scope */
	slp_twalk(stree, collect_scopes, 0, (void *) ppcScopes);

	/* unescape scopes list */
	if ((err = slp_unescape(*ppcScopes, &unesc_reply, SLP_FALSE, '%'))
	    == SLP_OK) {
		free(*ppcScopes);
		*ppcScopes = unesc_reply;
	} else {
		free(unesc_reply);
	}

	return (err);
}

/*
 * Finds scopes according to the adminstrative scoping model. A
 * comma-seperated list of scopes is returned in *ppcScopes; the
 * caller must free *ppcScopes.
 * If the return_default parameter is true, and no scopes are found,
 * *ppcScopes will be set to 'default', otherwise, *ppcScopes will
 * be NULL. This helps simplify internal memory management.
 */
SLPError slp_administrative_scopes(char **ppcScopes,
					SLPBoolean return_default) {
	const char *useScopes;

	*ppcScopes = NULL;

	/* @@@ first try DHCP */
	/* next try the useScopes property */
	useScopes = SLPGetProperty(SLP_CONFIG_USESCOPES);

	if (useScopes && *useScopes) {
		if (!(*ppcScopes = strdup(useScopes))) {
			slp_err(LOG_CRIT, 0, "SLPFindScopes", "out of memory");
			return (SLP_MEMORY_ALLOC_FAILED);
		}
		return (SLP_OK);
	}

	/* found none, so just return "default" */
	if (return_default && !(*ppcScopes = strdup("default"))) {
		slp_err(LOG_CRIT, 0, "SLPFindScopes", "out of memory");
		return (SLP_MEMORY_ALLOC_FAILED);
	}
	return (SLP_OK);
}

/*
 * This function operates on the same btree as the collate_scopes().
 * The difference is that this one is called for each
 * SAAdvert recieved.
 */
/* ARGSUSED */
static SLPBoolean saadvert_callback(SLPHandle hp, char *scopes,
					SLPError err, void **stree) {
	char *s, *tstate;

	if (err != SLP_OK) {
		return (SLP_TRUE);
	}

	for (
		s = strtok_r((char *)scopes, ",", &tstate);
		s;
		s = strtok_r(NULL, ",", &tstate)) {

		char *ascope, **srch;

		if (!(ascope = strdup(s))) {	/* no memory! */
			slp_err(LOG_CRIT, 0, "collate_scopes",
				"out of memory");
			return (SLP_TRUE);
		}

		srch = slp_tsearch(
			(void *) ascope, stree,
			(int (*)(const void *, const void *)) slp_strcasecmp);
		if (*srch != ascope)
			/* scope is already in there, so just free ascope */
			free(ascope);
	}

	return (SLP_TRUE);
}

/*
 * Generates an SAAdvert solicitation, and returns any scopes found
 * from all recieved SAAdverts in stree. stree must be a btree
 * structure.
 */
static SLPError SAAdvert_for_scopes(SLPHandle hSLP, void **stree) {
	SLPError err;
	SLPBoolean sync_state;
	slp_handle_impl_t *hp = (slp_handle_impl_t *)hSLP;
	char *predicate;
	const char *type_hint;

	/* get type hint, if set */
	if ((type_hint = SLPGetProperty(SLP_CONFIG_TYPEHINT)) != NULL &&
		*type_hint != 0) {

		size_t hintlen = strlen(type_hint);
		size_t predlen = strlen("(service-type=)");

		/* check bounds */
		if (hintlen > (SLP_MAX_STRINGLEN - predlen)) {
			return (SLP_PARAMETER_BAD);
		}
		if (!(predicate = malloc(hintlen + predlen + 1))) {
			slp_err(LOG_CRIT, 0, "SAAdvert_for_scopes",
				"out of memory");
			return (SLP_MEMORY_ALLOC_FAILED);
		}
		(void) strcpy(predicate, "(service-type=");
		(void) strcat(predicate, type_hint);
		(void) strcat(predicate, ")");
	} else {
		predicate = "";
		type_hint = NULL;
	}

	/* No callback for SLPFindScopes, so force synchronous mode only */
	sync_state = hp->async;
	hp->async = SLP_FALSE;

	if ((err = slp_start_call(hp)) != SLP_OK)
		return (err);

	err = slp_packSrvRqst("service:service-agent", predicate, hp);

	if (err == SLP_OK)
		err = slp_ua_common(hSLP, "",
				    (SLPGenericAppCB *)saadvert_callback,
				    stree,
				    (SLPMsgReplyCB *)unpackSAAdvert_scope);

	if (type_hint) {
		free(predicate);
	}

	if (err != SLP_OK)
		slp_end_call(hp);

	/* restore sync state */
	hp->async = sync_state;

	return (err);
}

/*
 * Unpack an SAAdvert and pass each set of scopes into cb.
 */
/* ARGSUSED */
static SLPBoolean unpackSAAdvert_scope(slp_handle_impl_t *hSLP, char *reply,
					SLPScopeCallback cb, void *cookie,
					void **collator, int *numResults) {
	char *surl, *scopes, *attrs;
	SLPBoolean cont;

	if (!reply) {
		cb(hSLP, NULL, SLP_LAST_CALL, cookie);
		return (SLP_FALSE);
	}

	/* tag call as internal; gets all scopes, regardless of maxResults */
	hSLP->internal_call = SLP_TRUE;

	if (slp_unpackSAAdvert(reply, &surl, &scopes, &attrs) != SLP_OK) {
		return (SLP_TRUE);
	}

	cont = cb(hSLP, scopes, SLP_OK, cookie);

	/* revert internal_call tag */
	hSLP->internal_call = SLP_FALSE;

	free(surl);
	free(scopes);
	free(attrs);

	return (cont);
}

/*
 * Creates a service request for finding DAs or SAs (based on 'filter'),
 * and sends it to slpd, returning the reply in 'reply'.
 */
SLPError slp_find_das(const char *filter, char **reply) {
	SLPError err;
	char *msg, hostname[MAXHOSTNAMELEN];

	/* Try the cache first */
	if (*reply = slp_find_das_cached(filter)) {
		return (SLP_OK);
	}

	/*
	 * create a find scopes message:
	 * this is a SrvRqst for the type directory-agent.sun.
	 */
	/* use the local host's name for the scope */
	(void) gethostname(hostname, MAXHOSTNAMELEN);

	err = slp_packSrvRqst_single(
		SLP_SUN_DA_TYPE, hostname, filter, &msg, "en");

	if (err == SLP_OK) {
		err = slp_send2slpd(msg, reply);
		free(msg);
	}

	/* Add the reply to the cache */
	if (err == SLP_OK) {
		slp_put_das_cached(filter, *reply, slp_get_length(*reply));
	}

	return (err);
}

/*
 * This is called for each URL entry in the DA service reply (sun private).
 * Contained within the cookie is a btree, to which it adds new
 * scopes from the URL entry. The scopes are retrieved from the btree
 * by traversing the tree in SLPFindScopes().
 * SLPHandle h is NULL, so don't touch it!
 */
/*ARGSUSED*/
static SLPBoolean collate_scopes(SLPHandle h, const char *u,
					unsigned short lifetime,
					SLPError errCode, void *cookie) {
	SLPSrvURL *surl;
	char *s, *tstate, *p, *url;
	void **collator = cookie;

	if (errCode != SLP_OK)
		return (SLP_TRUE);

	/* dup url so as not to corrupt da cache */
	if (!(url = strdup(u))) {
		slp_err(LOG_CRIT, 0, "collate_scopes", "out of memory");
		return (SLP_FALSE);
	}

	/* parse url into a SLPSrvURL struct */
	if (SLPParseSrvURL(url, &surl) != SLP_OK)
		return (SLP_TRUE);	/* bad URL; skip it */

	/* collate the scopes using the btree stree->scopes: */
	/* skip the 'scopes=' part */
	if (!(p = strchr(surl->s_pcSrvPart, '='))) {
		free(surl);
		return (SLP_TRUE);	/* bad URL; skip it */
	}
	p++;

	for (
		s = strtok_r(p, ",", &tstate);
		s;
		s = strtok_r(NULL, ",", &tstate)) {

		char *ascope, **srch;

		if (!(ascope = strdup(s))) {	/* no memory! */
			slp_err(LOG_CRIT, 0, "collate_scopes",
				"out of memory");
			free(surl);
			return (SLP_TRUE);
		}

		srch = slp_tsearch(
			(void *) ascope, collator,
			(int (*)(const void *, const void *)) slp_strcasecmp);
		if (*srch != ascope)
			/* scope is already in there, so just free ascope */
			free(ascope);
	}

	free(url);
	free(surl);

	return (SLP_TRUE);
}

/*
 * Each time we visit a node for the last time, copy that scope into
 * the scope collection and free the scope string and the node.
 */
/*ARGSUSED*/
static void collect_scopes(void *node, VISIT order, int level, void *cookie) {
	char **scopes = (char **)cookie;

	if (order == endorder || order == leaf) {
		char *s = *(char **)node;
		slp_add2list(s, scopes, SLP_FALSE);
		free(s);
		free(node);
	}
}

void SLPFree(void *pvMem) {
	if (pvMem)
		free(pvMem);
}

/*
 * Escape / Unescape
 */

#define	isBadTagChar(c)	((c) == '*' || (c) == '_' || \
			(c) == '\n' || (c) == '\t' || (c) == '\r')

#define	isReserved(c)	((c) <= 31 || (c) == '(' || (c) == ')' || \
			(c) == ',' || (c) == '\\' || (c) == '!' || \
			(c) == '<' || (c) == '=' || (c) == '>' || \
			(c) == '~')

SLPError SLPEscape(const char *pcInbuf, char **ppcOutBuf, SLPBoolean isTag) {
	char *buf, *pin, *pout;

	if (!pcInbuf || !ppcOutBuf)
		return (SLP_PARAMETER_BAD);

	if (!(buf = malloc(strlen(pcInbuf) * 3 + 1))) {
		slp_err(LOG_CRIT, 0, "SLPEscape", "out of memory");
		return (SLP_MEMORY_ALLOC_FAILED);
	}
	*ppcOutBuf = buf;

	for (pin = (char *)pcInbuf, pout = buf; *pin; ) {
		int len;

		/* If char is start of multibyte char, just copy it in */
		if ((len = mblen(pin, MB_CUR_MAX)) > 1) {
			int i;
			for (i = 0; i < len && *pin; i++)
				*pout++ = *pin++;
			continue;
		}

		/* check for bad tag */
		if (isTag && isBadTagChar(*pin))
			return (SLP_PARSE_ERROR);

		if (isReserved(*pin)) {
			if (isTag)
				return (SLP_PARSE_ERROR);
			(void) sprintf(pout, "\\%.2x", *pin);
			pout += 3;
			pin++;
		} else {
			*pout++ = *pin++;
		}
	}
	*pout = 0;

	return (SLP_OK);
}

SLPError SLPUnescape(const char *pcInbuf, char **ppcOutBuf, SLPBoolean isTag) {
	if (!pcInbuf || !ppcOutBuf)
		return (SLP_PARAMETER_BAD);

	return (slp_unescape(pcInbuf, ppcOutBuf, isTag, '\\'));
}


/*
 * The actual unescaping routine; allows for different escape chars.
 */
static SLPError slp_unescape(const char *pcInbuf, char **ppcOutBuf,
				SLPBoolean isTag, const char esc_char) {
	char *buf, *pin, *pout, conv[3];

	if (!(buf = malloc(strlen(pcInbuf) * 3 + 1))) {
		slp_err(LOG_CRIT, 0, "SLPEscape", "out of memory");
		return (SLP_MEMORY_ALLOC_FAILED);
	}
	*ppcOutBuf = buf;

	conv[2] = 0;
	for (pin = (char *)pcInbuf, pout = buf; *pin; ) {
		int len;

		/* If char is start of multibyte char, just copy it in */
		if ((len = mblen(pin, MB_CUR_MAX)) > 1) {
			int i;
			for (i = 0; i < len && *pin; i++)
				*pout++ = *pin++;
			continue;
		}

		if (*pin == esc_char) {
			if (!pin[1] || !pin[2])
				return (SLP_PARSE_ERROR);
			pin++;
			conv[0] = *pin++;
			conv[1] = *pin++;
			*pout++ = (char)strtol(conv, NULL, 16);
			if (isTag && isBadTagChar(*pout))
				return (SLP_PARSE_ERROR);
		} else {
			*pout++ = *pin++;
		}
	}
	*pout = 0;

	return (SLP_OK);
}

/*
 * Properties
 *
 * All properties are stored in a global tree (prop_table). This
 * tree is created and accessed by slp_tsearch and slp_tfind.
 */
struct prop_entry {
	const char *key, *val;
};
typedef struct prop_entry slp_prop_entry_t;

/* Global properties table */
static void *slp_props = NULL;
static mutex_t prop_table_lock = DEFAULTMUTEX;

static void setDefaults();

static int compare_props(const void *a, const void *b) {
	return (strcmp(
		((slp_prop_entry_t *)a)->key,
		((slp_prop_entry_t *)b)->key));
}

void SLPSetProperty(const char *pcName, const char *pcValue) {
	slp_prop_entry_t *pe, **pe2;

	if (!slp_props) setDefaults();

	if (!pcName || !pcValue) {
		return;
	}

	if (!(pe = malloc(sizeof (*pe)))) {
		slp_err(LOG_CRIT, 0, "SLPSetProperty", "out of memory");
		return;
	}

	/* place the strings under library ownership */
	if (!(pe->key = strdup(pcName))) {
		free(pe);
		slp_err(LOG_CRIT, 0, "SLPSetProperty", "out of memory");
		return;
	}

	if (!(pe->val = strdup(pcValue))) {
		free((void *) pe->key);
		free(pe);
		slp_err(LOG_CRIT, 0, "SLPSetProperty", "out of memory");
		return;
	}

	/* is pcName already set? */
	(void) mutex_lock(&prop_table_lock);
	pe2 = slp_tsearch((void *) pe, &slp_props, compare_props);
	if (pe != *pe2) {
		/* this prop is already set; overwrite the old value */
		free((void *) (*pe2)->val);
		(*pe2)->val = pe->val;
		free((void *) pe->key);
		free(pe);
	}
	(void) mutex_unlock(&prop_table_lock);
}

const char *SLPGetProperty(const char *pcName) {
	slp_prop_entry_t pe[1], **ans;

	if (!slp_props) setDefaults();

	if (!pcName) {
		return (NULL);
	}

	pe->key = pcName;

	(void) mutex_lock(&prop_table_lock);
	ans = slp_tfind(pe, &slp_props, compare_props);
	(void) mutex_unlock(&prop_table_lock);
	if (ans)
		return ((*ans)->val);
	return (NULL);
}

static void setDefaults() {
	slp_prop_entry_t *pe;
	static mutex_t lock = DEFAULTMUTEX;

	(void) mutex_lock(&lock);
	if (slp_props) {
		(void) mutex_unlock(&lock);
		return;
	}

	pe = malloc(sizeof (*pe));
	pe->key = strdup(SLP_CONFIG_ISBROADCASTONLY);
	pe->val = strdup("false");
	(void) slp_tsearch((void *) pe, &slp_props, compare_props);

	pe = malloc(sizeof (*pe));
	pe->key = strdup(SLP_CONFIG_MULTICASTTTL);
	pe->val = strdup("255");
	(void) slp_tsearch((void *) pe, &slp_props, compare_props);

	pe = malloc(sizeof (*pe));
	pe->key = strdup(SLP_CONFIG_MULTICASTMAXWAIT);
	pe->val = strdup("15000");
	(void) slp_tsearch((void *) pe, &slp_props, compare_props);

	pe = malloc(sizeof (*pe));
	pe->key = strdup(SLP_CONFIG_DATAGRAMTIMEOUTS);
	pe->val = strdup("2000,2000,2000");
	(void) slp_tsearch((void *) pe, &slp_props, compare_props);

	pe = malloc(sizeof (*pe));
	pe->key = strdup(SLP_CONFIG_MULTICASTTIMEOUTS);
	pe->val = strdup("1000,3000,3000,3000,3000");
	(void) slp_tsearch((void *) pe, &slp_props, compare_props);

	pe = malloc(sizeof (*pe));
	pe->key = SLP_CONFIG_MTU; pe->val = "1400";
	(void) slp_tsearch((void *) pe, &slp_props, compare_props);

	pe = malloc(sizeof (*pe));
	pe->key = strdup(SLP_CONFIG_MAXRESULTS);
	pe->val = strdup("-1");
	(void) slp_tsearch((void *) pe, &slp_props, compare_props);

	pe = malloc(sizeof (*pe));
	pe->key = strdup(SLP_CONFIG_SECURITY_ON);
	pe->val = strdup("false");
	(void) slp_tsearch((void *) pe, &slp_props, compare_props);

	pe = malloc(sizeof (*pe));
	pe->key = strdup(SLP_CONFIG_BYPASS_AUTH);
	pe->val = strdup("false");
	(void) slp_tsearch((void *) pe, &slp_props, compare_props);

	slp_readConfig();

	(void) mutex_unlock(&lock);
}

static const char *error_strings[] = {
	"OK",				/* 0 */
	"Language not supported",	/* -1 */
	"Parse error",			/* -2 */
	"Invalid registration",		/* -3 */
	"Scope not supported",		/* -4 */
	"Invalid error number",		/* -5 */
	"Authentication absent",	/* -6 */
	"Authentication failed",	/* -7 */
	"Invalid error number",		/* -8 */
	"Invalid error number",		/* -9 */
	"Invalid error number",		/* -10 */
	"Invalid error number",		/* -11 */
	"Invalid error number",		/* -12 */
	"Invalid update",		/* -13 */
	"Invalid error number",		/* -14 */
	"Invalid error number",		/* -15 */
	"Invalid error number",		/* -16 */
	"Not implemented",		/* -17 */
	"Buffer overflow",		/* -18 */
	"Network timed out",		/* -19 */
	"Network init failed",		/* -20 */
	"Memory alloc failed",		/* -21 */
	"Parameter bad",		/* -22 */
	"Network error",		/* -23 */
	"Internal system error",	/* -24 */
	"Handle in use",		/* -25 */
	"Type error"			/* -26 */
};

#define	SLP_MAX_ERR_CNT	26

const char *slp_strerror(SLPError err) {
	int abserr;
	const char *str;

	if (err == SLP_LAST_CALL) {
		str = "Last call";
	} else if (err == SLP_SECURITY_UNAVAILABLE) {
		str = "Security Implementation Unavailable";
	} else {
		abserr = abs(err);
		if (abserr > SLP_MAX_ERR_CNT) {
			str = "Invalid error number";
		} else {
			str = error_strings[abserr];
		}
	}

	return (dgettext("libslp", str));
}
