alias_smedia.c revision 145921
133965Sjdp/*
277298Sobrien * alias_smedia.c
333965Sjdp *
433965Sjdp * Copyright (c) 2000 Whistle Communications, Inc.
533965Sjdp * All rights reserved.
633965Sjdp *
733965Sjdp * Subject to the following obligations and disclaimer of warranty, use and
833965Sjdp * redistribution of this software, in source or object code forms, with or
933965Sjdp * without modifications are expressly permitted by Whistle Communications;
1033965Sjdp * provided, however, that:
1133965Sjdp * 1. Any and all reproductions of the source or object code must include the
1233965Sjdp *    copyright notice above and the following disclaimer of warranties; and
1333965Sjdp * 2. No rights are granted, in any manner or form, to use Whistle
1433965Sjdp *    Communications, Inc. trademarks, including the mark "WHISTLE
1533965Sjdp *    COMMUNICATIONS" on advertising, endorsements, or otherwise except as
1633965Sjdp *    such appears in the above copyright notice or in the software.
1733965Sjdp *
1833965Sjdp * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
1933965Sjdp * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
2077298Sobrien * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
2133965Sjdp * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
2277298Sobrien * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
2333965Sjdp * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
2433965Sjdp * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
2533965Sjdp * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
2633965Sjdp * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
2733965Sjdp * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
2833965Sjdp * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
2933965Sjdp * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
3033965Sjdp * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
3133965Sjdp * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3233965Sjdp * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
3333965Sjdp * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
3433965Sjdp * OF SUCH DAMAGE.
3533965Sjdp *
3633965Sjdp * Copyright (c) 2000  Junichi SATOH <junichi@astec.co.jp>
3733965Sjdp *                                   <junichi@junichi.org>
3833965Sjdp * All rights reserved.
3933965Sjdp *
4033965Sjdp * Redistribution and use in source and binary forms, with or without
4133965Sjdp * modification, are permitted provided that the following conditions
4233965Sjdp * are met:
4377298Sobrien * 1. Redistributions of source code must retain the above copyright
4460484Sobrien *    notice, this list of conditions and the following disclaimer.
4560484Sobrien * 2. Redistributions in binary form must reproduce the above copyright
4660484Sobrien *    notice, this list of conditions and the following disclaimer in the
4760484Sobrien *    documentation and/or other materials provided with the distribution.
4833965Sjdp *
4933965Sjdp * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
5033965Sjdp * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
5133965Sjdp * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
5233965Sjdp * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
5333965Sjdp * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
5433965Sjdp * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
5533965Sjdp * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
5633965Sjdp * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
5733965Sjdp * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
5833965Sjdp * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
5933965Sjdp * SUCH DAMAGE.
6033965Sjdp *
6133965Sjdp * Authors: Erik Salander <erik@whistle.com>
6233965Sjdp *          Junichi SATOH <junichi@astec.co.jp>
6333965Sjdp *                        <junichi@junichi.org>
6477298Sobrien */
6577298Sobrien
6633965Sjdp#include <sys/cdefs.h>
6777298Sobrien__FBSDID("$FreeBSD: head/sys/netinet/libalias/alias_smedia.c 145921 2005-05-05 19:27:32Z glebius $");
6877298Sobrien
6933965Sjdp/*
7038889Sjdp   Alias_smedia.c is meant to contain the aliasing code for streaming media
7138889Sjdp   protocols.  It performs special processing for RSTP sessions under TCP.
7277298Sobrien   Specifically, when a SETUP request is sent by a client, or a 200 reply
7338889Sjdp   is sent by a server, it is intercepted and modified.  The address is
7433965Sjdp   changed to the gateway machine and an aliasing port is used.
7533965Sjdp
7633965Sjdp   More specifically, the "client_port" configuration parameter is
7777298Sobrien   parsed for SETUP requests.  The "server_port" configuration parameter is
7877298Sobrien   parsed for 200 replies eminating from a server.  This is intended to handle
7933965Sjdp   the unicast case.
8033965Sjdp
8133965Sjdp   RTSP also allows a redirection of a stream to another client by using the
8233965Sjdp   "destination" configuration parameter.  The destination config parm would
8333965Sjdp   indicate a different IP address.  This function is NOT supported by the
8438889Sjdp   RTSP translation code below.
8538889Sjdp
8638889Sjdp   The RTSP multicast functions without any address translation intervention.
8733965Sjdp
8833965Sjdp   For this routine to work, the SETUP/200 must fit entirely
8933965Sjdp   into a single TCP packet.  This is typically the case, but exceptions
9033965Sjdp   can easily be envisioned under the actual specifications.
9133965Sjdp
9233965Sjdp   Probably the most troubling aspect of the approach taken here is
9333965Sjdp   that the new SETUP/200 will typically be a different length, and
9433965Sjdp   this causes a certain amount of bookkeeping to keep track of the
9577298Sobrien   changes of sequence and acknowledgment numbers, since the client
9633965Sjdp   machine is totally unaware of the modification to the TCP stream.
9733965Sjdp
9833965Sjdp   Initial version:  May, 2000 (eds)
9933965Sjdp*/
10033965Sjdp
10133965Sjdp#ifdef _KERNEL
10233965Sjdp#include <sys/param.h>
10377298Sobrien#include <sys/libkern.h>
10433965Sjdp#else
10577298Sobrien#include <sys/types.h>
10633965Sjdp#include <stdio.h>
10733965Sjdp#include <string.h>
10833965Sjdp#endif
10933965Sjdp
11033965Sjdp#include <netinet/in_systm.h>
11133965Sjdp#include <netinet/in.h>
11233965Sjdp#include <netinet/ip.h>
11333965Sjdp#include <netinet/tcp.h>
11433965Sjdp#include <netinet/udp.h>
11533965Sjdp
11633965Sjdp#ifdef _KERNEL
11760484Sobrien#include <netinet/libalias/alias_local.h>
11877298Sobrien#include <netinet/libalias/alias.h>
11933965Sjdp#else
12033965Sjdp#include "alias_local.h"
12133965Sjdp#endif
12233965Sjdp
12333965Sjdp#define RTSP_CONTROL_PORT_NUMBER_1 554
12433965Sjdp#define RTSP_CONTROL_PORT_NUMBER_2 7070
12533965Sjdp#define RTSP_PORT_GROUP            2
12633965Sjdp
12733965Sjdp#define ISDIGIT(a) (((a) >= '0') && ((a) <= '9'))
12833965Sjdp
12933965Sjdpstatic int
13033965Sjdpsearch_string(char *data, int dlen, const char *search_str)
13133965Sjdp{
13233965Sjdp	int i, j, k;
13333965Sjdp	int search_str_len;
13433965Sjdp
13533965Sjdp	search_str_len = strlen(search_str);
13633965Sjdp	for (i = 0; i < dlen - search_str_len; i++) {
13733965Sjdp		for (j = i, k = 0; j < dlen - search_str_len; j++, k++) {
13833965Sjdp			if (data[j] != search_str[k] &&
13933965Sjdp			    data[j] != search_str[k] - ('a' - 'A')) {
14033965Sjdp				break;
14133965Sjdp			}
14233965Sjdp			if (k == search_str_len - 1) {
14333965Sjdp				return (j + 1);
14477298Sobrien			}
14533965Sjdp		}
14633965Sjdp	}
14760484Sobrien	return (-1);
14833965Sjdp}
14933965Sjdp
15033965Sjdpstatic int
15133965Sjdpalias_rtsp_out(struct libalias *la, struct ip *pip,
15233965Sjdp    struct alias_link *lnk,
15333965Sjdp    char *data,
15433965Sjdp    const char *port_str)
15533965Sjdp{
15633965Sjdp	int hlen, tlen, dlen;
15733965Sjdp	struct tcphdr *tc;
15833965Sjdp	int i, j, pos, state, port_dlen, new_dlen, delta;
15933965Sjdp	u_short p[2], new_len;
16033965Sjdp	u_short sport, eport, base_port;
16133965Sjdp	u_short salias = 0, ealias = 0, base_alias = 0;
16260484Sobrien	const char *transport_str = "transport:";
16333965Sjdp	char newdata[2048], *port_data, *port_newdata, stemp[80];
16433965Sjdp	int links_created = 0, pkt_updated = 0;
16533965Sjdp	struct alias_link *rtsp_lnk = NULL;
16633965Sjdp	struct in_addr null_addr;
16733965Sjdp
16833965Sjdp	/* Calculate data length of TCP packet */
16933965Sjdp	tc = (struct tcphdr *)ip_next(pip);
17033965Sjdp	hlen = (pip->ip_hl + tc->th_off) << 2;
17133965Sjdp	tlen = ntohs(pip->ip_len);
17233965Sjdp	dlen = tlen - hlen;
17333965Sjdp
17433965Sjdp	/* Find keyword, "Transport: " */
17533965Sjdp	pos = search_string(data, dlen, transport_str);
17633965Sjdp	if (pos < 0) {
17733965Sjdp		return (-1);
17833965Sjdp	}
17933965Sjdp	port_data = data + pos;
18033965Sjdp	port_dlen = dlen - pos;
18133965Sjdp
18233965Sjdp	memcpy(newdata, data, pos);
18333965Sjdp	port_newdata = newdata + pos;
18433965Sjdp
18533965Sjdp	while (port_dlen > (int)strlen(port_str)) {
18633965Sjdp		/* Find keyword, appropriate port string */
18733965Sjdp		pos = search_string(port_data, port_dlen, port_str);
18833965Sjdp		if (pos < 0) {
18933965Sjdp			break;
19033965Sjdp		}
19133965Sjdp		memcpy(port_newdata, port_data, pos + 1);
19233965Sjdp		port_newdata += (pos + 1);
19333965Sjdp
19433965Sjdp		p[0] = p[1] = 0;
19533965Sjdp		sport = eport = 0;
19633965Sjdp		state = 0;
19733965Sjdp		for (i = pos; i < port_dlen; i++) {
19833965Sjdp			switch (state) {
19933965Sjdp			case 0:
20060484Sobrien				if (port_data[i] == '=') {
20160484Sobrien					state++;
20260484Sobrien				}
20360484Sobrien				break;
20460484Sobrien			case 1:
20560484Sobrien				if (ISDIGIT(port_data[i])) {
20660484Sobrien					p[0] = p[0] * 10 + port_data[i] - '0';
20760484Sobrien				} else {
20860484Sobrien					if (port_data[i] == ';') {
20960484Sobrien						state = 3;
21060484Sobrien					}
21160484Sobrien					if (port_data[i] == '-') {
21260484Sobrien						state++;
21360484Sobrien					}
21460484Sobrien				}
21560484Sobrien				break;
21660484Sobrien			case 2:
21760484Sobrien				if (ISDIGIT(port_data[i])) {
21860484Sobrien					p[1] = p[1] * 10 + port_data[i] - '0';
21960484Sobrien				} else {
22060484Sobrien					state++;
22160484Sobrien				}
22260484Sobrien				break;
22360484Sobrien			case 3:
22460484Sobrien				base_port = p[0];
22560484Sobrien				sport = htons(p[0]);
22660484Sobrien				eport = htons(p[1]);
22760484Sobrien
22860484Sobrien				if (!links_created) {
22960484Sobrien
23060484Sobrien					links_created = 1;
23160484Sobrien					/*
23260484Sobrien					 * Find an even numbered port
23360484Sobrien					 * number base that satisfies the
23460484Sobrien					 * contiguous number of ports we
23560484Sobrien					 * need
23660484Sobrien					 */
23760484Sobrien					null_addr.s_addr = 0;
23860484Sobrien					if (0 == (salias = FindNewPortGroup(la, null_addr,
23960484Sobrien					    FindAliasAddress(la, pip->ip_src),
24060484Sobrien					    sport, 0,
24160484Sobrien					    RTSP_PORT_GROUP,
24260484Sobrien					    IPPROTO_UDP, 1))) {
24360484Sobrien#ifdef DEBUG
24460484Sobrien						fprintf(stderr,
24560484Sobrien						    "PacketAlias/RTSP: Cannot find contiguous RTSP data ports\n");
24660484Sobrien#endif
24760484Sobrien					} else {
24877298Sobrien
24960484Sobrien						base_alias = ntohs(salias);
25060484Sobrien						for (j = 0; j < RTSP_PORT_GROUP; j++) {
25160484Sobrien							/*
25260484Sobrien							 * Establish link
25377298Sobrien							 * to port found in
25460484Sobrien							 * RTSP packet
25560484Sobrien							 */
25660484Sobrien							rtsp_lnk = FindRtspOut(la, GetOriginalAddress(lnk), null_addr,
25760484Sobrien							    htons(base_port + j), htons(base_alias + j),
25860484Sobrien							    IPPROTO_UDP);
25960484Sobrien							if (rtsp_lnk != NULL) {
26060484Sobrien#ifndef NO_FW_PUNCH
26160484Sobrien								/*
26260484Sobrien								 * Punch
26360484Sobrien								 * hole in
26460484Sobrien								 * firewall
26560484Sobrien								 */
26660484Sobrien								PunchFWHole(rtsp_lnk);
26760484Sobrien#endif
26877298Sobrien							} else {
26977298Sobrien#ifdef DEBUG
27060484Sobrien								fprintf(stderr,
27160484Sobrien								    "PacketAlias/RTSP: Cannot allocate RTSP data ports\n");
27260484Sobrien#endif
27360484Sobrien								break;
27460484Sobrien							}
27560484Sobrien						}
27660484Sobrien					}
27760484Sobrien					ealias = htons(base_alias + (RTSP_PORT_GROUP - 1));
27860484Sobrien				}
27960484Sobrien				if (salias && rtsp_lnk) {
28060484Sobrien
28160484Sobrien					pkt_updated = 1;
28260484Sobrien
28360484Sobrien					/* Copy into IP packet */
28460484Sobrien					sprintf(stemp, "%d", ntohs(salias));
28560484Sobrien					memcpy(port_newdata, stemp, strlen(stemp));
28660484Sobrien					port_newdata += strlen(stemp);
28760484Sobrien
28860484Sobrien					if (eport != 0) {
28960484Sobrien						*port_newdata = '-';
29060484Sobrien						port_newdata++;
29160484Sobrien
29260484Sobrien						/* Copy into IP packet */
29360484Sobrien						sprintf(stemp, "%d", ntohs(ealias));
29460484Sobrien						memcpy(port_newdata, stemp, strlen(stemp));
29560484Sobrien						port_newdata += strlen(stemp);
29660484Sobrien					}
29760484Sobrien					*port_newdata = ';';
29860484Sobrien					port_newdata++;
29960484Sobrien				}
30060484Sobrien				state++;
30160484Sobrien				break;
30260484Sobrien			}
30360484Sobrien			if (state > 3) {
30460484Sobrien				break;
30560484Sobrien			}
30660484Sobrien		}
30760484Sobrien		port_data += i;
30860484Sobrien		port_dlen -= i;
30960484Sobrien	}
31060484Sobrien
31160484Sobrien	if (!pkt_updated)
31260484Sobrien		return (-1);
31360484Sobrien
31460484Sobrien	memcpy(port_newdata, port_data, port_dlen);
31560484Sobrien	port_newdata += port_dlen;
31660484Sobrien	*port_newdata = '\0';
31760484Sobrien
31860484Sobrien	/* Create new packet */
31960484Sobrien	new_dlen = port_newdata - newdata;
32060484Sobrien	memcpy(data, newdata, new_dlen);
32160484Sobrien
32260484Sobrien	SetAckModified(lnk);
32360484Sobrien	delta = GetDeltaSeqOut(pip, lnk);
32460484Sobrien	AddSeq(pip, lnk, delta + new_dlen - dlen);
32560484Sobrien
32660484Sobrien	new_len = htons(hlen + new_dlen);
32760484Sobrien	DifferentialChecksum(&pip->ip_sum,
32860484Sobrien	    &new_len,
32960484Sobrien	    &pip->ip_len,
33077298Sobrien	    1);
33177298Sobrien	pip->ip_len = new_len;
33277298Sobrien
33377298Sobrien	tc->th_sum = 0;
33477298Sobrien	tc->th_sum = TcpChecksum(pip);
33533965Sjdp
33677298Sobrien	return (0);
33777298Sobrien}
33877298Sobrien
33933965Sjdp/* Support the protocol used by early versions of RealPlayer */
34033965Sjdp
34133965Sjdpstatic int
34233965Sjdpalias_pna_out(struct libalias *la, struct ip *pip,
34333965Sjdp    struct alias_link *lnk,
34433965Sjdp    char *data,
34533965Sjdp    int dlen)
34633965Sjdp{
34733965Sjdp	struct alias_link *pna_links;
34833965Sjdp	u_short msg_id, msg_len;
34933965Sjdp	char *work;
35033965Sjdp	u_short alias_port, port;
35133965Sjdp	struct tcphdr *tc;
35233965Sjdp
35333965Sjdp	work = data;
35477298Sobrien	work += 5;
35577298Sobrien	while (work + 4 < data + dlen) {
35633965Sjdp		memcpy(&msg_id, work, 2);
35777298Sobrien		work += 2;
35877298Sobrien		memcpy(&msg_len, work, 2);
35933965Sjdp		work += 2;
36077298Sobrien		if (ntohs(msg_id) == 0) {
36133965Sjdp			/* end of options */
36277298Sobrien			return (0);
36377298Sobrien		}
36477298Sobrien		if ((ntohs(msg_id) == 1) || (ntohs(msg_id) == 7)) {
36533965Sjdp			memcpy(&port, work, 2);
36677298Sobrien			pna_links = FindUdpTcpOut(la, pip->ip_src, GetDestAddress(lnk),
36777298Sobrien			    port, 0, IPPROTO_UDP, 1);
36877298Sobrien			if (pna_links != NULL) {
36977298Sobrien#ifndef NO_FW_PUNCH
37077298Sobrien				/* Punch hole in firewall */
37133965Sjdp				PunchFWHole(pna_links);
37233965Sjdp#endif
37333965Sjdp				tc = (struct tcphdr *)ip_next(pip);
37433965Sjdp				alias_port = GetAliasPort(pna_links);
37533965Sjdp				memcpy(work, &alias_port, 2);
37633965Sjdp
37738889Sjdp				/* Compute TCP checksum for revised packet */
37833965Sjdp				tc->th_sum = 0;
37933965Sjdp				tc->th_sum = TcpChecksum(pip);
38033965Sjdp			}
38133965Sjdp		}
38233965Sjdp		work += ntohs(msg_len);
38333965Sjdp	}
38433965Sjdp
38533965Sjdp	return (0);
38633965Sjdp}
38733965Sjdp
38833965Sjdpvoid
38933965SjdpAliasHandleRtspOut(struct libalias *la, struct ip *pip, struct alias_link *lnk, int maxpacketsize)
39033965Sjdp{
39133965Sjdp	int hlen, tlen, dlen;
39233965Sjdp	struct tcphdr *tc;
39333965Sjdp	char *data;
39433965Sjdp	const char *setup = "SETUP", *pna = "PNA", *str200 = "200";
39533965Sjdp	const char *okstr = "OK", *client_port_str = "client_port";
39633965Sjdp	const char *server_port_str = "server_port";
39733965Sjdp	int i, parseOk;
39833965Sjdp
39933965Sjdp	(void)maxpacketsize;
40038889Sjdp
40138889Sjdp	tc = (struct tcphdr *)ip_next(pip);
40238889Sjdp	hlen = (pip->ip_hl + tc->th_off) << 2;
40338889Sjdp	tlen = ntohs(pip->ip_len);
40438889Sjdp	dlen = tlen - hlen;
40538889Sjdp
40638889Sjdp	data = (char *)pip;
40738889Sjdp	data += hlen;
40838889Sjdp
40938889Sjdp	/* When aliasing a client, check for the SETUP request */
41038889Sjdp	if ((ntohs(tc->th_dport) == RTSP_CONTROL_PORT_NUMBER_1) ||
41138889Sjdp	    (ntohs(tc->th_dport) == RTSP_CONTROL_PORT_NUMBER_2)) {
41238889Sjdp
41338889Sjdp		if (dlen >= (int)strlen(setup)) {
41438889Sjdp			if (memcmp(data, setup, strlen(setup)) == 0) {
41538889Sjdp				alias_rtsp_out(la, pip, lnk, data, client_port_str);
41660484Sobrien				return;
41760484Sobrien			}
41860484Sobrien		}
41960484Sobrien		if (dlen >= (int)strlen(pna)) {
42060484Sobrien			if (memcmp(data, pna, strlen(pna)) == 0) {
42160484Sobrien				alias_pna_out(la, pip, lnk, data, dlen);
42277298Sobrien			}
42377298Sobrien		}
42477298Sobrien	} else {
42560484Sobrien
42677298Sobrien		/*
42777298Sobrien		 * When aliasing a server, check for the 200 reply
42833965Sjdp		 * Accomodate varying number of blanks between 200 & OK
42933965Sjdp		 */
43077298Sobrien
43177298Sobrien		if (dlen >= (int)strlen(str200)) {
43277298Sobrien
43333965Sjdp			for (parseOk = 0, i = 0;
43477298Sobrien			    i <= dlen - (int)strlen(str200);
43577298Sobrien			    i++) {
43677298Sobrien				if (memcmp(&data[i], str200, strlen(str200)) == 0) {
43733965Sjdp					parseOk = 1;
43833965Sjdp					break;
43933965Sjdp				}
44077298Sobrien			}
44177298Sobrien			if (parseOk) {
44233965Sjdp
44333965Sjdp				i += strlen(str200);	/* skip string found */
44433965Sjdp				while (data[i] == ' ')	/* skip blank(s) */
44533965Sjdp					i++;
44633965Sjdp
44733965Sjdp				if ((dlen - i) >= (int)strlen(okstr)) {
44833965Sjdp
44933965Sjdp					if (memcmp(&data[i], okstr, strlen(okstr)) == 0)
45033965Sjdp						alias_rtsp_out(la, pip, lnk, data, server_port_str);
45133965Sjdp
45233965Sjdp				}
45333965Sjdp			}
45433965Sjdp		}
45533965Sjdp	}
45633965Sjdp}
45733965Sjdp