/*	$NetBSD: iscsid_targets.c,v 1.7 2022/05/24 20:50:17 andvar Exp $	*/

/*-
 * Copyright (c) 2005,2006,2011 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Wasabi Systems, Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "iscsid_globals.h"

#include <ctype.h>

/* counter for portal and target ID */
static uint32_t portarget_id = 0;

/* counter for send_targets ID */
static uint32_t send_target_id = 0;


/*
 * create_portal:
 *    Create a portal entry and link it into the appropriate lists.
 *    May also create the associated portal group entry if it does not exist.
 *    Will return the existing entry if the address matches a defined portal.
 *
 *    Parameter:
 *       target   the pointer to the target
 *       addr     the portal address (includes tag)
 *		 dtype    portal discovery type
 *		 did	  discovery ID
 *
 *    Returns:    pointer to created portal
 */

static portal_t *
create_portal(target_t *target, iscsi_portal_address_t *addr,
			  iscsi_portal_types_t dtype, uint32_t did)
{
	portal_group_t *curr;
	portal_t *portal;
	u_short tag = addr->group_tag;

	DEB(9, ("Create Portal addr %s port %d group %d",
			addr->address, addr->port, addr->group_tag));

	if ((portal = find_portal_by_addr(target, addr)) != NULL)
		return portal;

	portal = calloc(1, sizeof(*portal));
	if (!portal) {
		DEBOUT(("Out of memory in create_portal!"));
		return NULL;
	}
	portal->addr = *addr;
	portal->target = target;
	portal->portaltype = dtype;
	portal->discoveryid = did;
	if (!portal->addr.port) {
		portal->addr.port = ISCSI_DEFAULT_PORT;
	}
	for (portarget_id++; !portarget_id ||
	     find_portal_id(portarget_id) != NULL ||
	     find_target_id(TARGET_LIST, portarget_id) != NULL;) {
		portarget_id++;
	}
	portal->entry.sid.id = portarget_id;

	TAILQ_FOREACH(curr, &target->group_list, groups)
		if (curr->tag == tag)
			break;

	if (!curr) {
		curr = calloc(1, sizeof(*curr));
		if (!curr) {
			free(portal);
			DEBOUT(("Out of memory in create_portal!"));
			return NULL;
		}
		curr->tag = tag;
		TAILQ_INIT(&curr->portals);
		TAILQ_INSERT_TAIL(&target->group_list, curr, groups);
		target->num_groups++;
	}

	portal->group = curr;

	TAILQ_INSERT_TAIL(&curr->portals, portal, group_list);
	curr->num_portals++;
	target->num_portals++;

	TAILQ_INSERT_TAIL(&list[PORTAL_LIST].list, &portal->entry, link);
	list[PORTAL_LIST].num_entries++;

	DEB(9, ("create_portal returns %p", portal));
	return portal;
}


/*
 * delete_portal:
 *    Delete a portal entry after unlinking it from its lists.
 *    May also delete the associated portal group entry if the group is empty.
 *
 *    Parameter:
 *       portal   		the pointer to the portal
 *		 delete_empty   delete empty target if true
 */

void
delete_portal(portal_t * portal, boolean_t delete_empty)
{
	portal_group_t *curr = portal->group;
	target_t *target = portal->target;

	TAILQ_REMOVE(&curr->portals, portal, group_list);
	TAILQ_REMOVE(&list[PORTAL_LIST].list, &portal->entry, link);
	curr->num_portals--;
	target->num_portals--;
	list[PORTAL_LIST].num_entries--;

	if (!curr->num_portals) {
		TAILQ_REMOVE(&target->group_list, curr, groups);
		free(curr);
		target->num_groups--;
	}
	free(portal);

	/* Optionally delete target if no portals left */
	if (delete_empty && !target->num_portals) {
		TAILQ_REMOVE(&list[TARGET_LIST].list, &target->entry, link);
		list[TARGET_LIST].num_entries--;
		free(target);
	}
}


/*
 * create_target:
 *    Create a target structure and initialize it.
 *
 *    Parameter:
 *          name        The target name
 *
 *    Returns:    Pointer to target structure, NULL if allocation failed.
 */

static target_t *
create_target(uint8_t * name)
{
	target_t *target;

	DEB(9, ("Create Target %s", name));

	if ((target = calloc(1, sizeof(*target))) == NULL) {
		DEBOUT(("Out of memory in create_target!"));
		return NULL;
	}

	TAILQ_INIT(&target->group_list);

	for (portarget_id++;
	     !portarget_id ||
	     find_portal_id(portarget_id) != NULL ||
	     find_target_id(TARGET_LIST, portarget_id) != NULL;
	     portarget_id++) {
	}

	target->entry.sid.id = portarget_id;
	strlcpy((char *)target->TargetName, (char *)name, sizeof(target->TargetName));

	return target;
}


/*
 * delete_target:
 *    Delete a target entry after unlinking it from its lists.
 *    Also deletes all portals associated with the target.
 *
 *    Parameter:
 *       target   the pointer to the target
 */

static void
delete_target(target_t * target)
{
	portal_group_t *cgroup;
	portal_t *curr = NULL;

	/* First delete all portals in all portal groups. */
	/* (this will also delete the groups) */
	while (target->num_groups) {
		cgroup = TAILQ_FIRST(&target->group_list);
		while (cgroup && cgroup->num_portals) {
			curr = TAILQ_FIRST(&cgroup->portals);
			if (curr)
				delete_portal(curr, FALSE);
		}
	}

	/*Now delete the target itself */
	TAILQ_REMOVE(&list[TARGET_LIST].list, &target->entry, link);
	list[TARGET_LIST].num_entries--;
	free(target);
}


/*
 * create_send_target:
 *    Create a send_target structure and initialize it.
 *
 *    Parameter:
 *          name        The target name
 *          addr        The portal address
 *
 *    Returns:    Pointer to structure, NULL if allocation failed.
 */

static target_t *
create_send_target(uint8_t * name, iscsi_portal_address_t * addr)
{
	send_target_t *target;

	DEB(9, ("Create Send Target %s", name));

	if ((target = calloc(1, sizeof(*target))) == NULL)
		return NULL;

	for (send_target_id++;
		 !send_target_id
		 || find_target_id(SEND_TARGETS_LIST, send_target_id) != NULL;)
		send_target_id++;

	target->entry.sid.id = send_target_id;
	strlcpy((char *)target->TargetName, (char *)name, sizeof(target->TargetName));
	target->addr = *addr;
	target->num_groups = 1;
	target->num_portals = 1;

	return (target_t *)(void *)target;
}

/*
 * delete_send_target:
 *    Delete a send_target entry after unlinking it from its lists.
 *
 *    Parameter:
 *       send_target   the pointer to the send_target
 */

static void
delete_send_target(send_target_t * send_target)
{
	generic_entry_t *curr;
	uint32_t id;

	id = send_target->entry.sid.id;

	TAILQ_FOREACH(curr, &list[PORTAL_LIST].list, link) {
		portal_t *p = (void *)curr;
		if (p->portaltype == PORTAL_TYPE_SENDTARGET &&
			p->discoveryid == id)
			p->discoveryid = 0; /* mark deleted */
	}

	TAILQ_REMOVE(&list[SEND_TARGETS_LIST].list, &send_target->entry, link);
	list[SEND_TARGETS_LIST].num_entries--;
	free(send_target);
}



/*
 * add_target:
 *    Handle ADD_TARGET request: Create a target or send_target and its
 *    associated portals.
 *    This routine allows the same target to be defined more than once,
 *    adding any missing data (for example additional portals).
 *
 *    Parameter:
 *          par         The request parameters.
 *          prsp        Pointer to address of response buffer.
 *          prsp_temp   Will be set to TRUE if buffer was allocated, FALSE
 *                      for static buffer.
 */


void
add_target(iscsid_add_target_req_t *par, iscsid_response_t **prsp,
			int *prsp_temp)
{
	iscsid_add_target_rsp_t *res;
	iscsid_response_t *rsp = *prsp;
	target_t *target, *tn;
	portal_t *portal;
	int i, num;

	DEB(9, ("Add Target, name %s, num_portals %d",
			par->TargetName, par->num_portals));

	if (par->list_kind == SEND_TARGETS_LIST && par->num_portals != 1) {
		rsp->status = ISCSID_STATUS_PARAMETER_INVALID;
		return;
	}
	/* check to see if the target already exists */
	if ((par->TargetName[0] &&
	     (target = find_TargetName(par->list_kind, par->TargetName)) != NULL) ||
	    (par->list_kind == SEND_TARGETS_LIST &&
	     (target = (target_t *)(void *)
			find_send_target_by_addr(&par->portal[0])) != NULL)) {
		num = target->num_portals;

		/* symbolic name? */
		if (par->sym_name[0]) {
			/* already named? rename if OK */
			tn = find_target_symname(par->list_kind, par->sym_name);
			if (tn && tn != target) {
				rsp->status = ISCSID_STATUS_DUPLICATE_NAME;
				return;
			}
			strlcpy((char *)target->entry.sid.name, (char *)par->sym_name, sizeof(target->entry.sid.name));
		}
	} else {
		if (par->sym_name[0] &&
			(find_target_symname(par->list_kind, par->sym_name) ||
			 find_portal_name(par->sym_name))) {
			rsp->status = ISCSID_STATUS_DUPLICATE_NAME;
			return;
		}

		if (par->list_kind == SEND_TARGETS_LIST)
			target = create_send_target(par->TargetName, &par->portal[0]);
		else
			target = create_target(par->TargetName);

		if (target == NULL) {
			rsp->status = ISCSID_STATUS_NO_RESOURCES;
			return;
		}
		num = 0;
		strlcpy((char *)target->entry.sid.name, (char *)par->sym_name,
			sizeof(target->entry.sid.name));
	}

	rsp = make_rsp(sizeof(*res) + (par->num_portals * sizeof(uint32_t)),
			prsp, prsp_temp);
	if (rsp == NULL)
		return;

	res = (iscsid_add_target_rsp_t *)(void *)rsp->parameter;
	res->target_id = target->entry.sid.id;

	/* link into target list */
	if (!num) {
		TAILQ_INSERT_TAIL(&list[par->list_kind].list, &target->entry,
			link);
		list[par->list_kind].num_entries++;
	}

	/*
	   Add the given portals. Note that create_portal also checks for
	   duplicate entries, and returns the pointer to the existing entry
	   if the request is a duplicate.
	 */

	if (par->list_kind == SEND_TARGETS_LIST) {
		res->portal_id[0] = target->entry.sid.id;
		res->num_portals = 1;
	} else {
		for (i = 0; i < (int)par->num_portals; i++) {
			portal = create_portal(target, &par->portal[i],
					PORTAL_TYPE_STATIC,
					target->entry.sid.id);
			if (portal == NULL) {
				rsp->status = ISCSID_STATUS_NO_RESOURCES;
				break;
			}
			res->portal_id[i] = portal->entry.sid.id;
		}
		res->num_portals = i;
	}

	DEB(9, ("AddTarget returns"));
}


/*
 * add_discovered_target:
 *    Check whether the given target and portal already exist.
 *    If not, add them.
 *
 *    Parameter:
 *          TargetName
 *          portal
 *          dtype = type of portal added: PORTAL_TYPE_SENDTARGET or
 *					PORTAL_TYPE_ISNS
 *          did = ID of SendTargets or iSNS for which portal was discovered
 *
 *    Returns: Pointer to created target, NULL on error (out of memory)
 *    Always sets portaltype to dtype even if portal already exists
 *      (used for refreshing to mark portals that we find)
 */

target_t *
add_discovered_target(uint8_t * TargetName, iscsi_portal_address_t * addr,
				iscsi_portal_types_t dtype, uint32_t did)
{
	target_t *target;
	portal_t *portal;

	DEB(9, ("Add Discovered Target, name %s, addr %s",
			TargetName, addr->address));

	if ((target = find_TargetName(TARGET_LIST, TargetName)) == NULL) {
		if ((target = create_target(TargetName)) == NULL) {
			return NULL;
		}
		portal = create_portal(target, addr, dtype, did);
		if (portal == NULL) {
			free(target);
			return NULL;
		}
		TAILQ_INSERT_TAIL(&list[TARGET_LIST].list, &target->entry, link);
		list[TARGET_LIST].num_entries++;
	} else if ((portal = create_portal(target, addr, dtype, did)) == NULL) {
		return NULL;
	}
	portal->portaltype = dtype;

	return target;
}


/*
 * set_target_options:
 *    Handle SET_TARGET_OPTIONS request: Copy the given options into the
 *    target structure.
 *
 *    Parameter:
 *          par         The request parameters.
 *
 *    Returns:     status
 */

uint32_t
set_target_options(iscsid_get_set_target_options_t * par)
{
	target_t *target;

	if ((target = find_target(par->list_kind, &par->target_id)) == NULL)
		return ISCSID_STATUS_INVALID_TARGET_ID;

	target->options = *par;

	return ISCSID_STATUS_SUCCESS;
}


/*
 * set_target_auth:
 *    Handle SET_TARGET_AUTHENTICATION request: Copy the given options into the
 *    target structure.
 *
 *    Parameter:
 *          par         The request parameters.
 *
 *    Returns:     status
 */

uint32_t
set_target_auth(iscsid_set_target_authentication_req_t * par)
{
	target_t *target;

	if ((target = find_target(par->list_kind, &par->target_id)) == NULL) {
		return ISCSID_STATUS_INVALID_TARGET_ID;
	}
	target->auth = *par;

	return ISCSID_STATUS_SUCCESS;
}


/*
 * get_target_info:
 *    Handle GET_TARGET_INFO request: Return information about the given
 *    target and its portals. If a portal ID is given, returns only the
 *    target info and the ID of this portal.
 *
 *    Parameter:
 *          par         The request parameters.
 *          prsp        Pointer to address of response buffer.
 *          prsp_temp   Will be set to TRUE if buffer was allocated, FALSE
 *                      for static buffer.
 */

void
get_target_info(iscsid_list_id_t *par, iscsid_response_t **prsp, int *prsp_temp)
{
	iscsid_get_target_rsp_t *res;
	iscsid_response_t *rsp = *prsp;
	uint32_t *idp;
	target_t *target;
	portal_group_t *cgroup;
	portal_t *curr = NULL;
	int num = 1;

	DEB(10, ("get_target_info, id %d", par->id.id));

	if ((target = find_target(par->list_kind, &par->id)) == NULL) {
		if (par->list_kind == SEND_TARGETS_LIST ||
		    (curr = find_portal(&par->id)) == NULL) {
			rsp->status = ISCSID_STATUS_INVALID_TARGET_ID;
			return;
		}
		target = curr->target;
	} else if (par->list_kind != SEND_TARGETS_LIST) {
		num = target->num_portals;
	}
	rsp = make_rsp(sizeof(*res) + (num - 1) * sizeof(uint32_t),
			prsp, prsp_temp);
	if (rsp == NULL)
		return;

	res = (iscsid_get_target_rsp_t *)(void *)rsp->parameter;
	res->target_id = target->entry.sid;
	strlcpy((char *)res->TargetName, (char *)target->TargetName,
			sizeof(res->TargetName));
	strlcpy((char *)res->TargetAlias, (char *)target->TargetAlias,
			sizeof(res->TargetAlias));

	res->num_portals = num;
	idp = res->portal;

	if (curr) {
		*idp = curr->entry.sid.id;
	} else if (par->list_kind != SEND_TARGETS_LIST) {
		TAILQ_FOREACH(cgroup, &target->group_list, groups)
			TAILQ_FOREACH(curr, &cgroup->portals, group_list)
			* idp++ = curr->entry.sid.id;
	} else
		*idp = target->entry.sid.id;
}


/*
 * add_portal:
 *    Handle ADD_PORTAL request: Add a portal to an existing target.
 *
 *    Parameter:
 *          par         The request parameters.
 *          prsp        Pointer to address of response buffer.
 *          prsp_temp   Will be set to TRUE if buffer was allocated, FALSE
 *                      for static buffer.
 */


void
add_portal(iscsid_add_portal_req_t *par, iscsid_response_t **prsp,
			int *prsp_temp)
{
	iscsid_add_portal_rsp_t *res;
	iscsid_response_t *rsp = *prsp;
	target_t *target;
	portal_t *portal;

	DEB(9, ("Add portal: target %d (%s), symname %s, addr %s",
			par->target_id.id, par->target_id.name,
			par->sym_name, par->portal.address));

	if ((target = find_target(TARGET_LIST, &par->target_id)) == NULL) {
		rsp->status = ISCSID_STATUS_INVALID_TARGET_ID;
		return;
	}

	if (par->sym_name[0] &&
		(find_target_symname(TARGET_LIST, par->sym_name) ||
		 find_portal_name(par->sym_name))) {
		rsp->status = ISCSID_STATUS_DUPLICATE_NAME;
		return;
	}

	portal = create_portal(target, &par->portal, PORTAL_TYPE_STATIC,
							target->entry.sid.id);
	if (portal == NULL) {
		rsp->status = ISCSID_STATUS_NO_RESOURCES;
		return;
	}

	if (par->sym_name[0]) {
		strlcpy((char *)portal->entry.sid.name, (char *)par->sym_name,
			sizeof(portal->entry.sid.name));
	}
	portal->options = par->options;

	rsp = make_rsp(sizeof(*res), prsp, prsp_temp);
	if (rsp == NULL)
		return;
#if 0 /*XXX: christos res is uninitialized here?? */
	res->target_id = target->entry.sid;
	res->portal_id = portal->entry.sid;
#endif

	DEB(9, ("AddPortal success (id %d)", portal->entry.sid.id));
}


/*
 * get_portal_info:
 *    Handle GET_PORTAL_INFO request: Return information about the given
 *    portal.
 *
 *    Parameter:
 *          par         The request parameters.
 *          prsp        Pointer to address of response buffer.
 *          prsp_temp   Will be set to TRUE if buffer was allocated, FALSE
 *                      for static buffer.
 */

void
get_portal_info(iscsid_list_id_t *par, iscsid_response_t **prsp, int *prsp_temp)
{
	iscsid_get_portal_rsp_t *res;
	iscsid_response_t *rsp = *prsp;
	portal_t *portal = NULL;
	send_target_t *starg = NULL;
	int err;

	DEB(10, ("get_portal_info, id %d", par->id.id));

	if (par->list_kind == SEND_TARGETS_LIST)
		err = ((starg = (send_target_t *)(void *)find_target(par->list_kind,
							&par->id)) == NULL);
	else
		err = ((portal = find_portal(&par->id)) == NULL);

	if (err) {
		rsp->status = ISCSID_STATUS_INVALID_PORTAL_ID;
		return;
	}

	rsp = make_rsp(sizeof(*res), prsp, prsp_temp);
	if (rsp == NULL)
		return;

	res = (iscsid_get_portal_rsp_t *)(void *)rsp->parameter;
	if (par->list_kind == SEND_TARGETS_LIST) {
		res->target_id = starg->entry.sid;
		res->portal_id = starg->entry.sid;
		res->portal = starg->addr;
	} else {
		res->target_id = portal->target->entry.sid;
		res->portal_id = portal->entry.sid;
		res->portal = portal->addr;
	}
}

/*
 * remove_target:
 *    Handle REMOVE_TARGET request: Removes a target, target portal,
 *		or Send-Targets portal from its respective list.
 *      Removing a target will remove all associated portals.
 *
 *    Parameter:
 *          par         The request parameters = iscsid_list_id_t
 *						containing the target ID.
 *
 *    Returns:     status
 */

uint32_t
remove_target(iscsid_list_id_t * par)
{
	target_t *target = NULL;
	portal_t *portal = NULL;
	send_target_t *starg = NULL;
	int err;

	DEB(9, ("remove_target, id %d", par->id.id));

	if (par->list_kind == SEND_TARGETS_LIST) {
		err = ((starg = (send_target_t *)(void *)find_target(par->list_kind,
							&par->id)) == NULL);
		if (!err) {
			delete_send_target(starg);
		}
	} else if (par->list_kind == PORTAL_LIST) {
		err = ((portal = find_portal(&par->id)) == NULL);
		if (!err) {
			delete_portal(portal, FALSE);
		}
	} else {
		target = find_target(par->list_kind, &par->id);
		err = (target == NULL);
		if (!err) {
			delete_target(target);
		}
	}

	return err ? ISCSID_STATUS_INVALID_PORTAL_ID : ISCSID_STATUS_SUCCESS;
}



/*
 * cl_get_address:
 *    Get an address specification that may include port and group tag.
 *
 *    Parameter:
 *          portal   The portal address
 *          str      The parameter string to scan
 *
 *    Returns:    0 on error, 1 if OK.
 */

static int
cl_get_address(iscsi_portal_address_t * portal, char *str)
{
	char *sp, *sp2;
	int val;

	/* is there a port? don't check inside square brackets (IPv6 addr) */
	for (sp = str + 1, val = 0; *sp && (*sp != ':' || val); sp++) {
		if (*sp == '[')
			val = 1;
		else if (*sp == ']')
			val = 0;
	}

	/* */
	if (*sp) {
		for (sp2 = sp + 1; *sp2 && *sp2 != ':'; sp2++);
		/* if there's a second colon, assume it's an unbracketed IPv6
		 * address */
		if (!*sp2) {
			/* truncate source, that's the address */
			*sp++ = '\0';
			if (sscanf(sp, "%d", &val) != 1)
				return 0;
			if (val < 0 || val > 0xffff)
				return 0;
			portal->port = (uint16_t) val;
		}
		/* is there a group tag? */
		for (; isdigit((unsigned char)*sp); sp++) {
		}
		if (*sp && *sp != ',')
			return 0;
	} else
		for (sp = str + 1; *sp && *sp != ','; sp++);

	if (*sp) {
		if (sscanf(sp + 1, "%d", &val) != 1)
			return 0;
		if (val < 0 || val > 0xffff)
			return 0;
		portal->group_tag = (uint16_t) val;
		/* truncate source, that's the address */
		*sp = '\0';
	}
	/* only check length, don't verify correct format (too many
	 * possibilities) */
	if (strlen(str) >= sizeof(portal->address))
		return 0;

	strlcpy((char *)portal->address, str, sizeof(portal->address));

	return 1;
}

/*
 * refresh_send_target:
 *    Handle REFRESH_TARGETS request for a Send Target
 *
 *    Parameter:
 *          id    The send target ID.
 *
 *    Returns:     status
 */


static uint32_t
refresh_send_target(uint32_t id)
{
	uint8_t *response_buffer = NULL;
	uint32_t response_size;
	uint32_t ret;
	uint8_t *TargetName;
	iscsi_portal_address_t addr;
	uint8_t *tp, *sp;
	generic_entry_t *curr;
	generic_entry_t *next;
	send_target_t *sendtarg;
	int rc;

	/*
	 * Go through our list of portals and mark each one
	 * belonging to the current sendtargets group to refreshing
	 * This mark is used afterwards to remove any portals that
	 * were not refreshed (because the mark gets reset for portals
	 * that are refreshed).
	 */

	TAILQ_FOREACH(curr, &list[PORTAL_LIST].list, link) {
		portal_t *p = (void *)curr;
		if (p->portaltype == PORTAL_TYPE_SENDTARGET &&
		    p->discoveryid == id) {
			p->portaltype = PORTAL_TYPE_REFRESHING;
		}
	}

	if ((ret = send_targets(id, &response_buffer, &response_size)) == 0) {
		/*
		 * Parse the response target list
		 * The SendTargets response in response_buffer is a list of
		 * target strings. Each target string consists of a TargetName
		 * string, followed by 0 or more TargetAddress strings:
		 *
		 *      TargetName=<target-name>
		 *      TargetAddress=<hostname-or-ip>[:<tcp-port>],
		 *				<portal-group-tag>
		 * The entire list is terminated by a null (after
		 * response_size bytes) (this terminating NULL was placed
		 * there by send_targets routine.)
		 */

		tp = response_buffer;
		while (*tp) {
			if (strncmp((char *)tp, "TargetName=", 11) != 0) {
				DEBOUT(("Response not TargetName <%s>", tp));
				break;
			}
			tp += 11;
			TargetName = tp; /*Point to target name */
			while (*tp++) {
			}
			rc = -1; /* Mark no address found yet */

			/*Now process any "TargetAddress" entries following */
			while (*tp && strncmp((char *)tp, "TargetAddress=", 14) == 0) {
				tp += 14;
				sp = tp; /* save start of address */
				while (*tp++) {
				}
				/*Get the target address */
				rc = cl_get_address(&addr, (char *)sp);
				if (rc) {
					add_discovered_target(TargetName,
						&addr, PORTAL_TYPE_SENDTARGET,
						id);
				} else {
					DEBOUT(("Syntax error in returned target address <%s>", sp));
					break;
				}
			}

			if (rc == -1) {
				/* There are no TargetAddress entries
				 * associated with TargetName. This means the
				 * sendtarget address is used. */
				sendtarg = find_send_target_id(id);
				if (sendtarg != NULL) {
					add_discovered_target(TargetName,
						&sendtarg->addr,
						PORTAL_TYPE_SENDTARGET, id);
				}
			}
		} /* end of while */
	}
	/*
	 * Go through our list of portals and look for ones
	 * that are still marked for refreshing.
	 * These are ones that are no longer there and should be removed.
	 */

	for (curr = TAILQ_FIRST(&list[PORTAL_LIST].list); curr != NULL;
		 curr = next) {
		portal_t *p = (void *)curr;
		next = TAILQ_NEXT(curr, link);
		if (p->portaltype == PORTAL_TYPE_REFRESHING)
			delete_portal(p, TRUE);
	}

	/*
	 * Clean up
	 */

	if (response_buffer != NULL)
		free(response_buffer);

	return ret;
}


/*
 * cleanup_send_target_orphans:
 *    Delete portals that were discovered through a now deleted send target.
 */


static void
cleanup_orphans(iscsi_portal_types_t type)
{
	generic_entry_t *curr;
	generic_entry_t *next;

	/*
	 * Go through the list of portals and look for ones marked with a zero
	 * discovery ID, those are associated with send targets that no
	 * longer exist.
	 */

	for (curr = TAILQ_FIRST(&list[PORTAL_LIST].list); curr != NULL;
		 curr = next) {
		portal_t *p = (void *)curr;
		next = TAILQ_NEXT(curr, link);
		if (p->portaltype == type && p->discoveryid == 0) {
			delete_portal(p, TRUE);
		}
	}
}


/*
 * refresh_targets:
 *    Handle REFRESH_TARGETS request:
 *    Refreshes the list of targets discovered via SendTargets or iSNS
 *
 *    Parameter:
 *          The request parameter = iscsid_refresh_targets_req_t containing:
 *             iscsid_list_kind_t   kind;       Kind: 
 *                                                  SEND_TARGETS_LIST
 *                                                  ISNS_LIST
 *             uint32_t                num_ids;    # of targets in list 
 *             uint32_t            id [1];     List of targets 
 *
 *    Returns:     status
 */

uint32_t
refresh_targets(iscsid_refresh_req_t * par)
{
	uint32_t t;
	uint32_t rc, retval;
	generic_entry_t *curr;

	retval = ISCSID_STATUS_NO_TARGETS_FOUND;

	switch (par->kind) {
	case TARGET_LIST:
		/*
		 * Refreshing for a specific target makes no sense if it's
		 * static. Maybe implement it for dynamically discovered
		 * targets? But then it's best done through the discovering
		 * instance, or we'll refresh much more than just the given
		 * target. And refreshing the whole list is iffy as well. So
		 * refuse this op on the target list for now.
		 */
		break;

	case SEND_TARGETS_LIST:
		DEB(9, ("Refresh Send Targets List - num_ids = %d",
				par->num_ids));
		if (par->num_ids) {
			/* Target ids are specified */
			for (t = 0; t < par->num_ids; t++) {
				rc = refresh_send_target(par->id[t]);
				if (rc == 0) {
					retval = ISCSID_STATUS_SUCCESS;
				}
			}
		} else {
			cleanup_orphans(PORTAL_TYPE_SENDTARGET);

			/* No target ids specified - refresh all. */
			TAILQ_FOREACH(curr, &list[SEND_TARGETS_LIST].list, link)
				if ((rc = refresh_send_target(curr->sid.id)) == 0)
					retval = ISCSID_STATUS_SUCCESS;
		}
		return retval;

#ifndef ISCSI_MINIMAL
	case ISNS_LIST:
		DEB(9, ("Refresh iSNS List - num_ids = %d", par->num_ids));
		if (par->num_ids) {
			/*Target ids are specified */
			for (t = 0; t < par->num_ids; t++)
				if ((rc = refresh_isns_server(par->id[t])) == 0)
					retval = ISCSI_STATUS_SUCCESS;
		} else {
			cleanup_orphans(PORTAL_TYPE_ISNS);

			/*No target ids specified - refresh all. */
			TAILQ_FOREACH(curr, &list[ISNS_LIST].list, link)
				if ((rc = refresh_isns_server(curr->sid.id)) == 0)
					retval = ISCSI_STATUS_SUCCESS;
		}
		return retval;
#endif

	default:
		break;
	}

	return ISCSID_STATUS_PARAMETER_INVALID;
}
