/*
 * Copyright (c) 2004, 2005 Sendmail, Inc. and its suppliers.
 *	All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: sockmapimpl.c,v 1.14 2006/07/16 02:07:40 ca Exp $")

#include "sm/assert.h"
#include "sm/error.h"
#include "sm/memops.h"
#include "sm/heap.h"
#include "sm/string.h"
#include "sockmap.h"

/* --------- socket map basic implementation ------ */

/*
**  SOCKMAP_FREE -- free socket map context
**
**	Parameters:
**		db -- pointer to socket map
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sockmap_free(sm_sockmap_P db)
{
	if (db != NULL)
		sm_free_size(db, sizeof(*db));
	return SM_SUCCESS;
}

/*
**  SOCKMAP_NEW -- allocate socket map context
**
**	Parameters:
**		pdb -- pointer to pointer to socket map
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sockmap_new(sm_sockmap_P *pdb)
{
	sm_sockmap_P db;

	SM_REQUIRE(pdb != NULL);
	*pdb = NULL;
	db = sm_zalloc(sizeof(*db));
	if (db == NULL)
		return sm_error_perm(SM_EM_MAP, ENOMEM);

	/* default for socket map timeout */
	db->sockmap_tmout = 5;
	*pdb = db;
	return SM_SUCCESS;
}

/*
**  SOCKMAP_DESTROY -- destroy socket map
**
**	Parameters:
**		pdb -- pointer to pointer to socket map
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sockmap_destroy(sm_sockmap_P *pdb)
{
	sm_ret_T ret;

	SM_REQUIRE(pdb != NULL);
	ret = SM_SUCCESS;
	if (*pdb != NULL)
	{
		if ((*pdb)->sockmap_fp != NULL)
		{
			ret = sm_io_close((*pdb)->sockmap_fp, SM_IO_CF_NONE);
			(*pdb)->sockmap_fp = NULL;
		}
		sockmap_free(*pdb);
		*pdb = NULL;
	}
	return ret;
}

/*
**  SOCKMAP_CLOSE -- close socket map
**
**	Parameters:
**		db -- pointer to socket map
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sockmap_close(sm_sockmap_P db)
{
	sm_ret_T ret;

	ret = SM_SUCCESS;
	if (db != NULL && db->sockmap_fp != NULL)
	{
		ret = sm_io_close(db->sockmap_fp, SM_IO_CF_NONE);
		db->sockmap_fp = NULL;
	}
	return ret;
}

/*
**  SOCKMAP_OPEN -- open socket map
**
**	Parameters:
**		pdb -- pointer to pointer to socket map
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sockmap_open(sm_sockmap_P db)
{
	sm_ret_T ret;
	int fd;

	SM_REQUIRE(db != NULL);
	db->sockmap_fp = NULL;
	if (db->sockmap_path != NULL)
	{
		ret = unix_client_connect(db->sockmap_path, &fd);
	}
	else
	{
		ret = net_client_connectipv4(db->sockmap_ipv4,
				db->sockmap_port, &fd);
	}
	if (sm_is_err(ret))
		goto fail;
	ret = sm_io_open(SmStStdiofd, (void *) &fd, SM_IO_RDWR,
			&db->sockmap_fp, SM_IO_WHAT_END);
	if (sm_is_err(ret))
		goto fail;
	ret = sm_io_setinfo(db->sockmap_fp, SM_IO_DOUBLE, NULL);
	if (sm_is_err(ret))
		goto fail;
	ret = sm_io_setinfo(db->sockmap_fp, SM_IO_WHAT_TIMEOUT,
			(void *)&db->sockmap_tmout);
	if (sm_is_err(ret))
		goto fail;
	return ret;

  fail:
	/* cleanup? */
	sockmap_close(db);
	return ret;
}

/*
**  SOCKMAP_LOOKUP -- lookup a key in SOCKMAP, return data if found
**
**	Parameters:
**		map -- map context
**		key -- key
**		data -- data (output)
**
**	Returns:
**		usual sm_error code
*/

sm_ret_T
sockmap_lookup(sm_sockmap_P map, sm_str_P key, sm_str_P data)
{
	sm_ret_T ret;
	uint len, replylen;
	int c;
	ssize_t recvlen;
	char *value, *status;
	sm_file_T *fp;
	char statbuf[16];

	SM_REQUIRE(map != NULL);
	SM_REQUIRE(key != NULL);
	SM_REQUIRE(data != NULL);

	fp = map->sockmap_fp;
	if (fp == NULL)
	{
		ret = sockmap_open(map);
		if (sm_is_err(ret))
			return ret;
	}
	len = strlen(map->sockmap_name) + 1 + sm_str_getlen(key);
	SM_ASSERT(len > strlen(map->sockmap_name));
	SM_ASSERT(len > sm_str_getlen(key));
	if ((sm_io_fprintf(fp, "%u:%s %S,", len, map->sockmap_name,
			key) == SM_IO_EOF) ||
	    (sm_io_flush(fp) != 0) || (sm_io_error(fp) != 0))
	{
		ret = sm_error_temp(SM_EM_MAP, errno);
		goto errcl;
	}

	if (sm_io_fscanf(fp, "%9u", &replylen) != 1)
	{
		ret = sm_error_temp(SM_EM_MAP, errno);
		goto errcl;
	}
	if (replylen > SOCKETMAP_MAXL)
	{
		ret = sm_error_temp(SM_EM_MAP, SM_E_PR_ERR);
		goto errcl;
	}
	if (sm_io_getc(fp) != ':')
	{
		ret = sm_error_temp(SM_EM_MAP, SM_E_PR_ERR);
		goto error;
	}

	len = 0;
	sm_memzero(statbuf, sizeof(statbuf));
	if (replylen >= sm_str_getsize(data))
	{
		/* not enough space: read status and discard rest */
		while (replylen-- > 0 && (c = sm_io_getc(fp)) != SM_IO_EOF)
		{
			if (len < sizeof(statbuf) - 1)
				statbuf[len++] = c;
		}
	}
	else
	{
		/* read status first */
		c = '\0';
		while (replylen-- > 0 && (c = sm_io_getc(fp)) != SM_IO_EOF
			&& c != ' ')
		{
			if (len < sizeof(statbuf) - 1)
				statbuf[len++] = c;
		}
		if (replylen > 0 && c == ' ')
		{
			ret = sm_io_read(fp, sm_str_data(data), replylen,
					&recvlen);
			if (sm_is_err(ret))
				goto errcl;
			else if (recvlen < replylen)
			{
				ret = sm_error_temp(SM_EM_MAP, errno);
				goto errcl;
			}
			SM_STR_SETLEN(data, recvlen);
		}
	}
	if (sm_io_getc(fp) != ',')
	{
		ret = sm_error_temp(SM_EM_MAP, SM_E_PR_ERR);
		goto errcl;
	}
	statbuf[sizeof(statbuf) - 1] = '\0';
	status = statbuf;

	value = strchr(status, ' ');
	if (value != NULL)
	{
		*value = '\0';
		value++;
	}
	if (sm_streq(status, "OK"))
	{
		ret = SM_SUCCESS;
	}
	else if (sm_streq(status, "NOTFOUND"))
	{
		return sm_error_perm(SM_EM_MAP, SM_E_NOTFOUND);
	}
	else
	{
		if (sm_streq(status, "TEMP") || sm_streq(status, "TIMEOUT"))
			ret = sm_error_temp(SM_EM_MAP, SM_E_TEMPMAP);
		else if (sm_streq(status, "PERM"))
			ret = sm_error_perm(SM_EM_MAP, SM_E_PERMMAP);
		else
			ret = sm_error_temp(SM_EM_MAP, SM_E_PR_ERR);
	}

	return ret;

  errcl:
	sockmap_close(map);
  error:
	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1