示例#1
0
static int ForceTermination(UFILE *uclient, PService psvc, PAction ac, pvar_entry_t args, int nargs)
{
    uint success = TRUE; /* assume no error will occur */
    PWANIPConnectionData pdata = (PWANIPConnectionData) psvc->opaque;
    //PWANDevicePrivateData pdevdata = (PWANDevicePrivateData) psvc->device->parent->opaque;
    char wanproto[100];

    /* Our ConnectionType is always IP_Routed, so I don't need to check that here. */
    if (pdata->connection_status == IP_DISCONNECTED) {
	soap_error(uclient, SOAP_CONNECTIONALREADYTERMNATED);
	success = FALSE;
    } else if (pdata->connection_status != IP_CONNECTED 
	       && pdata->connection_status != IP_CONNECTING) {
	soap_error(uclient, SOAP_DISCONNECTINPROGRESS);
	success = FALSE;
    } else { 
	igd_pri_wan_var(wanproto, sizeof(wanproto), "proto");
	/* Save the wan_proto into NVRAM to restore when the igd is enabled */
	nvram_set("upnp_wan_proto", nvram_safe_get(wanproto));
	nvram_set(wanproto, "disabled");
	req_nvram_commit(1);
	igd_restart(3);
    }
    
    return success;
}
示例#2
0
文件: ipt.c 项目: BackupGGCode/wl500g
int DeletePortMapping( UFILE *uclient, PService psvc, PAction ac, pvar_entry_t args, int nargs)
/*  {"NewRemoteHost", VAR_RemoteHost, VAR_IN},		*/
/*  {"NewExternalPort", VAR_ExternalPort, VAR_IN},	*/
/*  {"NewProtocol", VAR_PortMappingProtocol, VAR_IN},	*/
{
    int i, parse_status, status = 0;
    netconf_nat_t e;
    mapping_t mapping;
    
    // bypass port mapping when NAT is disabled
    if (nvram_invmatch("wan_nat_x", "1")) return 0;

    parse_status = (int) parse_dnat(&e, 
				    ac->params[2].value,	/* NewProtocol */
				    ac->params[0].value,	/* NewRemoteHost */
				    ac->params[1].value,	NULL, /* NewExternalPort */
				    NULL,		/* NewInternalClient */
				    NULL, NULL 		/* NewInternalPort */ );
    if (!parse_status) {
	status = SOAP_INVALID_ARGS;
    } else {
	status = SOAP_NOSUCHENTRYINARRAY;
	for (i = 0; mapmgr_get_port_map(i, &mapping); i++) {
	    if (SameMatchInfo(&e, (netconf_nat_t*)&mapping)) {
		mapmgr_delete_port_map(i);
		status = 0; /* SUCCESS! */
	    }
	}
    }	

    if (status) 
	soap_error( uclient, status );

    return (status == 0);
}
示例#3
0
文件: soap.c 项目: Haxe/gtk-gnutella
/**
 * Cancel a SOAP RPC.
 */
void
soap_rpc_cancel(soap_rpc_t *sr)
{
	soap_rpc_check(sr);

	soap_error(sr, SOAP_E_CANCELLED);
}
示例#4
0
static int RequestConnection(UFILE *uclient, PService psvc, PAction ac, pvar_entry_t args, int nargs)
{
    uint success = TRUE; /* assume no error will occur */
    PWANIPConnectionData pdata = psvc->opaque;
    char wanproto[100], *str;

    if (pdata->connection_status == IP_UNCONFIGURED) {
	soap_error(uclient, SOAP_CONNECTIONNOTCONFIGURED);
	success = FALSE;
    } else if (pdata->connection_status == IP_DISCONNECTED) {
	igd_pri_wan_var(wanproto, sizeof(wanproto), "proto");
	/* Save the wan_proto into NVRAM to restore when the igd is enabled */
	if ((str = nvram_get("upnp_wan_proto")) == NULL)
	    nvram_set(wanproto, "dhcp");	/* pick dhcp as defualt */
	else
	    nvram_set(wanproto, str);
	req_nvram_commit(1);
	igd_restart(3);
	pdata->connection_status = IP_CONNECTING;
	pdata->connection_timeout = CONNECTION_TIMEOUT;
	mark_changed(psvc, VAR_ConnectionStatus);
    }
    mark_changed(psvc, VAR_ConnectionStatus);

    return success;
}
示例#5
0
文件: ipt.c 项目: BackupGGCode/wl500g
int GetSpecificPortMappingEntry(UFILE *uclient, PService psvc, PAction ac, pvar_entry_t args, int nargs)
/* {"NewRemoteHost", VAR_RemoteHost, VAR_IN},				*/
/* {"NewExternalPort", VAR_ExternalPort, VAR_IN},			*/
/* {"NewProtocol", VAR_PortMappingProtocol, VAR_IN},			*/
/* {"NewInternalPort", VAR_InternalPort, VAR_OUT},			*/
/* {"NewInternalClient", VAR_InternalClient, VAR_OUT},			*/
/* {"NewEnabled", VAR_PortMappingEnabled, VAR_OUT},			*/
/* {"NewPortMappingDescription", VAR_PortMappingDescription, VAR_OUT},	*/
/* {"NewLeaseDuration", VAR_PortMappingLeaseDuration, VAR_OUT},		*/
{
    int i, parse_status, status;
    netconf_nat_t e;
    mapping_t mapping;
    const unsigned char *bytep;
    static char InternalClient[16];
    static char InternalPort[7];
    static char Enabled[2];
    static char Description[60];

    parse_status = (int) parse_dnat(&e, 
			       ac->params[2].value,	/* NewProtocol */
			       ac->params[0].value,	/* NewRemoteHost */
			       ac->params[1].value, NULL,	/* NewExternalPort */
			       NULL,			/* NewInternalClient */
			       NULL, NULL		/* NewInternalPort */ );

    if (!parse_status) {
	status = SOAP_INVALID_ARGS;
    } else {
	status = SOAP_NOSUCHENTRYINARRAY;
	for (i = 0; mapmgr_get_port_map(i, &mapping); i++) {
	    if (SameMatchInfo(&e, (netconf_nat_t*)&mapping)) {
		InternalClient[0] = '\0';
		if (mapping.ipaddr.s_addr) {
		    bytep = (const unsigned char *) &(mapping.ipaddr);
		    snprintf(InternalClient, sizeof(InternalClient), 
			     "%d.%d.%d.%d", bytep[0], bytep[1], bytep[2], bytep[3]);
		}

		snprintf(InternalPort, sizeof(InternalPort), "%d", ntohs(mapping.ports[0]));
		snprintf(Enabled, sizeof(Enabled), "%d", ((mapping.match.flags & NETCONF_DISABLED) ? 0 : 1));
		strncpy(Description, mapping.desc, sizeof(Description));

		ac->params[3].value = InternalPort;
		ac->params[4].value = InternalClient;
		ac->params[5].value = Enabled;
		ac->params[6].value = Description;
		ac->params[7].value = "0";
		status = 0; /* SUCCESS! */
		break;
	    }
	}
    }

    if (status) 
	soap_error( uclient, status );

    return (status == 0);
}
示例#6
0
文件: ipt.c 项目: BackupGGCode/wl500g
int GetGenericPortMappingEntry(UFILE *uclient, PService psvc, PAction ac, pvar_entry_t args, int nargs)
/* {"NewPortMappingIndex", VAR_PortMappingNumberOfEntries, VAR_IN}, 	*/
/* {"NewRemoteHost", VAR_RemoteHost, VAR_OUT}, 				*/
/* {"NewExternalPort", VAR_ExternalPort, VAR_OUT}, 			*/
/* {"NewProtocol", VAR_PortMappingProtocol, VAR_OUT}, 			*/
/* {"NewInternalPort", VAR_InternalPort, VAR_OUT}, 			*/
/* {"NewInternalClient", VAR_InternalClient, VAR_OUT}, 			*/
/* {"NewEnabled", VAR_PortMappingEnabled, VAR_OUT}, 			*/
/* {"NewPortMappingDescription", VAR_PortMappingDescription, VAR_OUT},	*/
/* {"NewLeaseDuration", VAR_PortMappingLeaseDuration, VAR_OUT}, 	*/
{
    int success = TRUE;
    char *PortMappingIndex = ac->params[0].value;
    mapping_t mapping;
    u_int32 i;
    const unsigned char *bytep;
    static char RemoteHost[16];
    static char ExternalPort[7];
    static char InternalClient[16];
    static char InternalPort[7];
    static char Enabled[2];
    static char Description[60];

    i = atoi(PortMappingIndex);
    if (mapmgr_get_port_map(i, &mapping)) {
	RemoteHost[0] = '\0';
	if (mapping.match.src.ipaddr.s_addr) {
	    bytep = (const unsigned char *) &(mapping.match.src.ipaddr);
	    snprintf(RemoteHost, sizeof(RemoteHost), 
		     "%d.%d.%d.%d", bytep[0], bytep[1], bytep[2], bytep[3]);
	}

	InternalClient[0] = '\0';
	if (mapping.ipaddr.s_addr) {
	    bytep = (const unsigned char *) &(mapping.ipaddr);
	    snprintf(InternalClient, sizeof(InternalClient), 
		     "%d.%d.%d.%d", bytep[0], bytep[1], bytep[2], bytep[3]);
	}

	snprintf(ExternalPort, sizeof(ExternalPort), "%d", ntohs(mapping.match.dst.ports[0]));
	snprintf(InternalPort, sizeof(InternalPort), "%d", ntohs(mapping.ports[0]));
	snprintf(Enabled, sizeof(Enabled), "%d", ((mapping.match.flags & NETCONF_DISABLED) ? 0 : 1));
	snprintf(Description, sizeof(Description), "%s", mapping.desc);

	ac->params[1].value = RemoteHost;
	ac->params[2].value = ExternalPort;
	ac->params[3].value = (mapping.match.ipproto == IPPROTO_TCP ? "TCP" :  "UDP" );
	ac->params[4].value = InternalPort;
	ac->params[5].value = InternalClient;
	ac->params[6].value = Enabled;
	ac->params[7].value = Description;
	ac->params[8].value = "0";
    } else {
	soap_error( uclient, SOAP_SPECIFIEDARRAYINDEXINVALID );
	success = FALSE;
    }

    return success;
}
示例#7
0
文件: soap.c 项目: Haxe/gtk-gnutella
/**
 * Delayed RPC start.
 */
static void
soap_rpc_launch(cqueue_t *unused_cq, gpointer obj)
{
	soap_rpc_t *sr = obj;
	http_post_data_t post;

	(void) unused_cq;
	soap_rpc_check(sr);

	sr->delay_ev = NULL;

	if (GNET_PROPERTY(soap_debug) > 4) {
		g_debug("SOAP \"%s\" at \"%s\": launching (%s)",
			sr->action, sr->url, sr->retry ? "retry" : "initial");
	}

	sr->reply_len = 0;		/* In case we retry, clear out older data */

	/*
	 * Launch the asynchronous POST request.
	 */

	post.content_type = SOAP_CONTENT_TYPE;
	post.data = pmsg_start(sr->mb);
	post.datalen = pmsg_size(sr->mb);
	post.data_free = NULL;
	post.data_free_arg = NULL;

	sr->ha = http_async_post(sr->url, &post, soap_header_ind,
				soap_data_ind, soap_error_ind);

	/*
	 * If we cannot create the HTTP request, it can be the URL is wrong,
	 * or no connection can be established to the host.  Hence it's a
	 * contacting error, not an I/O error at this stage.
	 */

	if (sr->ha == NULL) {
		if (GNET_PROPERTY(soap_debug)) {
			g_warning("SOAP cannot contact \"%s\": %s",
				sr->url, http_async_strerror(http_async_errno));
		}
		soap_error(sr, SOAP_E_CONTACT);
		return;
	}

	/*
	 * Customize the HTTP layer.
	 */

	http_async_set_opaque(sr->ha, sr, NULL);
	http_async_set_op_post_request(sr->ha, soap_build_request);
	http_async_set_op_headsent(sr->ha, soap_sent_head);
	http_async_set_op_datasent(sr->ha, soap_sent_data);
	http_async_set_op_gotreply(sr->ha, soap_got_reply);
	http_async_option_ctl(sr->ha, HTTP_O_READ_REPLY, HTTP_CTL_ADD);
}
示例#8
0
文件: soap.c 项目: Haxe/gtk-gnutella
/**
 * HTTP async callback, invoked on errors.
 */
static void
soap_error_ind(http_async_t *ha, http_errtype_t type, void *val)
{
	soap_rpc_t *sr = http_async_get_opaque(ha);
	soap_error_t err = SOAP_E_OK;

	soap_rpc_check(sr);

	if (GNET_PROPERTY(soap_debug)) {
		http_async_log_error_dbg(ha, type, val, "SOAP",
			GNET_PROPERTY(soap_debug) > 1);
	}

	if (HTTP_ASYNC_ERROR == type) {
		switch (GPOINTER_TO_INT(val)) {
		case HTTP_ASYNC_CANCELLED:
			/*
			 * Retry with M-POST if cancelled with sr->retry set to TRUE.
			 */
			if (sr->retry) {
				g_assert(NULL == sr->delay_ev);
				sr->delay_ev = cq_main_insert(1, soap_rpc_launch, sr);

				if (GNET_PROPERTY(soap_debug) > 1) {
					g_message("SOAP \"%s\" at \"%s\": retrying with M-POST",
						sr->action, sr->url);
				}
			}
			break;		/* No callback on explicit user cancel */
		case HTTP_ASYNC_DATA2BIG:
			err = SOAP_E_DATA2BIG;
			break;
		case HTTP_ASYNC_CONN_TIMEOUT:
		case HTTP_ASYNC_TIMEOUT:
			err = SOAP_E_TIMEOUT;
			break;
		default:
			err = SOAP_E_TRANSPORT;
			break;
		}
	} else {
		err = SOAP_E_TRANSPORT;
	}

	if (err != SOAP_E_OK)
		soap_error(sr, err);
}
示例#9
0
文件: ipt.c 项目: BackupGGCode/wl500g
int AddPortMapping( UFILE *uclient, PService psvc, PAction ac, pvar_entry_t args, int nargs)
/* {"NewRemoteHost", VAR_RemoteHost, VAR_IN},				*/
/* {"NewExternalPort", VAR_ExternalPort, VAR_IN},			*/
/* {"NewProtocol", VAR_PortMappingProtocol, VAR_IN},			*/
/* {"NewInternalPort", VAR_InternalPort, VAR_IN},			*/
/* {"NewInternalClient", VAR_InternalClient, VAR_IN},			*/
/* {"NewEnabled", VAR_PortMappingEnabled, VAR_IN},			*/
/* {"NewPortMappingDescription", VAR_PortMappingDescription, VAR_IN},	*/
/* {"NewLeaseDuration", VAR_PortMappingLeaseDuration, VAR_IN},		*/
{
    int i, parse_status, status = 0;
    char *LeaseDuration =  ac->params[7].value;
    char *Description = ac->params[6].value;
    bool bEnabled = (bool) atoi(ac->params[5].value);
    bool found_match;
    netconf_nat_t e;
    mapping_t mapping;

    // bypass port mapping when NAT is disabled
    if (nvram_invmatch("wan_nat_x", "1")) return 0;

    do {
	if (atoi(LeaseDuration) != 0) {
	    status = SOAP_ONLYPERMANENTLEASESSUPPORTED;
	    continue;
	} 

	parse_status = (int) parse_dnat(&e, 
					ac->params[2].value, /* NewProtocol */
					ac->params[0].value, /* NewRemoteHost */
					ac->params[1].value, NULL, /* NewExternalPort */
					ac->params[4].value, /* NewInternalClient */
					ac->params[3].value, NULL /* NewInternalPort */
	);
	if (!parse_status) {
	    status = SOAP_INVALID_ARGS;
	    continue;
	}

	
	found_match = FALSE;
	for (i = 0; mapmgr_get_port_map(i, &mapping); i++) {
	    if (SameMatchInfo(&e, (netconf_nat_t*)&mapping)) {
		found_match = TRUE;
		break;
	    }
	}
	
	if (found_match) {
	    if (!SameInternalClient(&e, &mapping)) {
		// we already have that mapping, but a different target...error. 
		status = SOAP_CONFLICTINMAPPINGENTRY;
		continue;
	    } else {
		mapmgr_delete_port_map(i);
	    }
	} 
	
	/* Enabled/disable this mapping according to the "NewEnabled" parameter. */
	if (bEnabled) {
	    e.match.flags &= ~NETCONF_DISABLED;
	} else {
	    e.match.flags |= NETCONF_DISABLED;
	}
	
	strncpy(e.desc, Description, sizeof(e.desc));
		
	mapmgr_add_port_map(&e);

    } while(0);

    if (status) 
	soap_error( uclient, status );

    return (status == 0);
}
示例#10
0
文件: soap.c 项目: Haxe/gtk-gnutella
/**
 * Process the SOAP reply from the server.
 */
static void
soap_process_reply(soap_rpc_t *sr)
{
	const char *buf;
	vxml_parser_t *vp;
	vxml_error_t e;
	xnode_t *root = NULL;
	xnode_t *xn = NULL;
	const char *charset;

	soap_rpc_check(sr);

	if (sr->reply_len != 0 && (GNET_PROPERTY(soap_trace) & SOCK_TRACE_IN)) {
		g_debug("----Got SOAP HTTP reply data from %s:", sr->url);
		if (log_printable(LOG_STDERR)) {
			fwrite(sr->reply_data, sr->reply_len, 1, stderr);
			fputs("----End SOAP HTTP reply\n", stderr);
		}
	}

	if (GNET_PROPERTY(soap_debug) > 2) {
		g_debug("SOAP \"%s\" at \"%s\": processing reply (%lu byte%s) HTTP %d",
			sr->action, sr->url, (unsigned long) sr->reply_len,
			1 == sr->reply_len ? "" : "s", sr->http_code);
	}

	/*
	 * If we got a 2xx reply, we need to parse up to the <Body> element
	 * and then pass up the remaining to the user for parsing specific
	 * elemnts accordingly.
	 *
	 * Other reply codes indicate an error.  On 4xx replies we may not
	 * have any XML to parse.  On 5xx replies, we should usually have
	 * a <Fault> indication under the <Body>.
	 *
	 * The strategy used here is to parse the XML reply into a tree and then
	 * analyse the tree, ignoring the HTTP status code which is redundant.
	 */

	buf = header_get(sr->header, "Content-Type");
	if (NULL == buf)
		goto no_xml;

	/*
	 * MIME type and subtypes are case-insensitive (see RFC 2616, section 3.7).
	 */

	if (
		!http_field_starts_with(buf, SOAP_TEXT_REPLY, FALSE) &&
		!http_field_starts_with(buf, SOAP_APPLICATION_REPLY, FALSE)
	) {
		if (GNET_PROPERTY(soap_debug)) {
			g_debug("SOAP \"%s\" at \"%s\": got unexpected Content-Type: %s",
				sr->action, sr->url, buf);
		}
		goto no_xml;
	}

	/*
	 * Extract charset if given.
	 */

	charset = http_parameter_get(buf, "charset");

	/*
	 * Parse the SOAP envelope.
	 */

	vp = vxml_parser_make(sr->action, VXML_O_STRIP_BLANKS);
	vxml_parser_add_data(vp, sr->reply_data, sr->reply_len);

	if (!vxml_parser_set_charset(vp, charset)) {
		g_warning("SOAP \"%s\" at \"%s\": ignoring unknown charset \"%s\"",
			sr->action, sr->url, charset);
	}

	e = vxml_parse_tree(vp, &root);
	vxml_parser_free(vp);

	if (e != VXML_E_OK) {
		if (GNET_PROPERTY(soap_debug)) {
			g_debug("SOAP \"%s\" at \"%s\": cannot parse XML reply: %s",
				sr->action, sr->url, vxml_strerror(e));
		}
		goto bad_xml;
	}

	g_assert(root != NULL);

	/*
	 * Make sure we got a SOAP reply.
	 */

	if (!xnode_is_element_named(root, SOAP_NAMESPACE, SOAP_X_ENVELOPE))
		goto not_soap;

	/*
	 * Look for the <SOAP:Body> element.
	 */

	for (xn = xnode_first_child(root); TRUE; xn = xnode_next_sibling(xn)) {
		if (NULL == xn || !xnode_within_namespace(xn, SOAP_NAMESPACE))
			goto bad_soap;
		if (0 == strcmp(SOAP_X_BODY, xnode_element_name(xn)))
			break;
	}

	/*
	 * Inspect the first child of the <SOAP:Body> element.
	 *
	 * If it's a <SOAP:Fault>, go process it and return an error.
	 * If it's another SOAP tag, we have an unknown structure.
	 * Otherwise it's the reply, for user code to handle.
	 */

	xn = xnode_first_child(xn);

	if (NULL == xn)
		goto bad_soap;

	if (xnode_is_element_named(xn, SOAP_NAMESPACE, SOAP_X_FAULT)) {
		xnode_detach(xn);
		soap_fault(sr, xn);
	} else if (xnode_within_namespace(xn, SOAP_NAMESPACE)) {
		goto bad_soap;
	} else {
		xnode_detach(xn);
		soap_reply(sr, xn);
	}

	xnode_tree_free(root);
	return;

not_soap:
	if (GNET_PROPERTY(soap_debug)) {
		g_debug("SOAP \"%s\" at \"%s\": unexpected root XML "
			"element <%s:%s>",
			sr->action, sr->url, EMPTY_STRING(xnode_element_ns(root)),
			xnode_element_name(root));
	}
	xnode_tree_free(root);
	/* FALL THROUGH */

no_xml:
	soap_error(sr, SOAP_E_PROTOCOL);
	return;

bad_soap:
	if (GNET_PROPERTY(soap_debug)) {
		g_debug("SOAP \"%s\" at \"%s\": unexpected XML structure",
			sr->action, sr->url);
	}
	if (GNET_PROPERTY(soap_debug) > 1) {
		g_debug("SOAP current node is %s", xnode_to_string(xn));
	}
	if (GNET_PROPERTY(soap_debug) > 2)
		xfmt_tree_dump(root, stderr);

	xnode_tree_free(root);
	/* FALL THROUGH */

bad_xml:
	soap_error(sr, SOAP_E_PROCESSING);
	return;
}
示例#11
0
/*
  Parse up the "Body>".  The element immediately after
  that will be the name of the action to invoke,
  possibly preceded by a namespace.

  The action name may be followed by whitespace and
  other attributes, like namespace, etc., or it may
  immediatly by followed by the closing angle-bracket
  '>'.

  The elements contains within the action name are the
  arguments, also possibly preceded by a namespace
  (although the SOAP spec says they should not be)

  The list of argument will be terminated by a closing
  element that corresponds to the action name, also
  possibly preceded by a namespace.

*/
void soap_action(UFILE *up, PService psvc, char *soapaction, char *body)
{
    char *ac, *u, *actstart, *colon;
    var_entry_t args[10];
    int nargs = 0;
    char *argstart, *argend;
    char *valstart, *valend;
    char *sp, *eb;
    char pattern[80];

    // split out the action name.
    if (soapaction != NULL && (ac = index(soapaction, '#')) != NULL) {
	ac++;

	snprintf(pattern, sizeof(pattern), ":Body>");
	if ((u = strstr(body, pattern)) != NULL) {
	    body = u + 1;
	    if ((u = strstr(body, "<")) != NULL) {
		body = u + 1;
		actstart = body;
		sp = index(body, ' ');
		eb = index(body, '>');
		if (sp && eb) {
		    if (sp < eb) 
			*sp = '\0';
		    body = eb + 1;
		    if (body[-2] != '/') {
			snprintf(pattern, sizeof(pattern), "</%s", actstart);
			do {
			    // parse each argument and put it into a list of <name, value> pointers.
			    if ((argstart = strstr(body, "<")) == NULL) 
				break;

			    // if we are at the end of the argument list, 
			    // break out of the loop.
			    if (strncmp(argstart, pattern, strlen(pattern)) == 0) 
				break;
			    
			    argstart++;  // advance over the '<'

			    // although it is contrary to the spec, 
			    // some argument elements may have a namespace prefix.
			    // in partcular, QueryStateVariable actions from windows XP
			    // appear to do this but actions from Messenger do not.
			    // In any case, be prepared to skip the namespace prefix.
			    sp = index(argstart, ' ');
			    eb = index(argstart, '>');
			    if (sp && eb && sp < eb) 
				*sp = '\0';
			    *eb = '\0';
			    argend = eb;
			    
			    // DO NOT move this above the setting of argend to '/0'!!!!
			    if ((colon = index(argstart, ':')) != NULL) 
				argstart = colon + 1;
			    
			    if (argend[-1] == '/') {
				valstart = valend = argend;
			    } else {
				valstart = argend + 1;
				if ((valend = index(valstart, '<')) == NULL) 
				    break;
				*valend = '\0';
			    }

			    // put this argument into the arg list.
			    args[nargs].name = argstart;
			    args[nargs].value = valstart;
			    nargs++;
			    
			    // point to the beginning of the next argument.
			    body = valend + 1;
			} while (*body);
		    }
		}
	    }
	}

	// invoke that action
	UPNP_ACTION(psvc, ac, args, nargs);

	if ( strcmp(ac, "QueryStateVariable") == 0 ) {
	    QueryStateVariable( up, psvc, ac, args, nargs);
	} else {
	    dispatch( up, psvc, ac, args, nargs );
	}
	if ((psvc->flags & VAR_CHANGED) == VAR_CHANGED) {
	    update_all_subscriptions(psvc);
	}

    } else {
	soap_error( up, SOAP_INVALID_ACTION );
    }
}