RE: libnetng : name resolution

"Kirby Kuehl" <[email protected]> Sat, 14 Aug 2004 14:01:48 -0500
Newsgroups gmane.comp.security.libnet
Message-ID <[email protected]>
 
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

WIN32 code ported without too much trouble. (See changes in attached)
NOTE: I have used the getopt.c and getopt.h included with the current
win32 libnet code.

- - Kirby

C:\cvs\gai\Debug>gai -i 10.25.80.155
ascii -> addr: name=10.25.80.155 addr=10 25 80 4294967195


C:\cvs\gai\Debug>gai -i 10.25.80.155 -n
ascii -> addr: name=10.25.80.155 addr=10 25 80 4294967195


C:\cvs\gai\Debug>gai -i www.google.com
ascii -> addr: name=www.google.com addr=66 102 7 4294967187

C:\cvs\gai\Debug>gai -i www.google.com -c
ascii -> addr: name=www.google.com addr=66 102 7 4294967187  (FQDN
ok)


C:\cvs\gai\Debug>gai -i www.google.com -n
libnet_resolve_ascii2addr(): unable to resolve (err=11001: No such
host is known
. ) 

- -----Original Message-----
From: Frédéric Raynal [mailto:[email protected]] 
Sent: Saturday, August 14, 2004 12:47 PM
To: [email protected]
Cc: mike schiffman; Kirby Kuehl
Subject: libnetng : name resolution


Hi,

Attached is a file containing the new API for name resolution. As we
talked earlier with Mike nd Kirby, it is now based on getaddrinfo()
and getnameinfo() because they are supposed to be protocol
independant. So, the functions provided here work with both IPv4 and
IPv6 ... in theory ;-)


Moreover, I have included the structure of addresses defined by Dug
Song in libdnet (once again, thanks to you for that nice libray) That
way, compatibility between the libraries will help in futur
developments.

Important
=========

Performing some tests on Linux (2.6) and Mac OS X (10.3), I noticed
different behavior ... which is quite annoying. I cant say right now
if it comes from a stupid bug I made, or if is a "normal bug".

Hence, I _really_ need you to make tests on your system



                              Linux   Mac OS X
                               2.6      10.3
./gai -i 1.2.3.4                ok       ok
./gai -i 1.2.3.4 -n             ok       ok
./gai -i 1.2.3.4 -c           ok 0:1   ok 0:1
./gai -i 217.12.3.11  -c      ok 0:1   ok 0:1

./gai -i google.fr -n          [1]     ok 1:0
./gai -i google.fr            ok 1:0   ok 1:0
./gai -i google.fr -c         ok 1:X   ok 1:0
./gai -i yahoo.fr -c         [2] 1:1  [3] 1:0

./gai -4 1.2.3.4              ok 0:1    [4]
./gai -4 1.2.3.4  -c         [6] 0:1    [4]
./gai -4 1.2.3.4  -n            ok      [4]


[1] unable to resolve (err=-2: Name or service not known) [2] ascii
- -> addr: name=yahoo.fr addr=217 12 3 11  (FQDN ok) [3] ascii -> addr:
name=yahoo.fr addr=217 12 3 11 [4] libnet_resolve_addr2ascii():
getnameinfo() failed (Unknown error: 0)

X:Y means  X DNS request of type A, and Y of type PTR. When nothing
is speicied, it means there is no DNS request.

On Mac OS X, take care there is a DNS cache so there can be no DNS
request if the request is already cached.

Questions
=========

- - these functions need to be tested everywhere they can (Solaris,
Win,
  and so on).

- - why getnameinfo() fails on my iBook ?

- - can someone test that with IPv6

- - these functions are probably not available on all systems. OpenSSH
  made their own portable one (in openbsd/fake-rfc2553.c)
  Is there a volunteer to do that ? I promise to offer him a beer.



Thanks for your help

	Fred

PS: I'll release an alpha version (ie not all builders and features
from 1.1 API will be available) probably tomorow or monday.

-----BEGIN PGP SIGNATURE-----
Version: PGP 8.1

iQA/AwUBQR5hm/JlWu/m+KZxEQLbAACeMeJ7J5p9OncPTdZrKyetKN4H+scAoK77
oLM3Z4RV6a11+HTh9FEWdHXz
=jA+q
-----END PGP SIGNATURE-----
gai.c (text/plain, 15.2 KB)
/*
 *  $$
 *
 *  libnet
 *  gai.c - various name resolution type routines
 *
 *  Copyright (c) 1998 - 2004 Mike D. Schiffman <[email protected]>
 *  Ported to the NG API by Fred Raynal <[email protected]>
 *  All rights reserved.
 *
 * 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 AUTHOR 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 AUTHOR 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.
 *
 */


/* 
 *
 * USER KORNER
 * ===========
 *
 * Examples
 *
 * ascii2addr()
 *
 *  + "IP" -> addr 
 *
 *   - flags = LIBNET_ASCII2ADDR [ | LIBNET_NUMERICHOST]
 *
 *     "./gai -i 1.2.3.4" and  " ./gai -i 1.2.3.4 -n" are equivalent
 *     as no name resolution is made.
 * 
 *   - flags = LIBNET_ASCII2ADDR | LIBNET_CANONNAME
 *
 *    It gives the same result, but a DNS (type PTR) request is made
 *
 *     ./gai -i 1.2.3.4  -c
 *     ascii -> addr: name=1.2.3.4 addr=1 2 3 4
 *     ./gai -i 217.12.3.11  -c
 *     ascii -> addr: name=217.12.3.11 addr=217 12 3 11  (FQDN ok)
 *
 *     In both cases, you get the IP address in its numerical, but in
 *     the second case, you also know the address resolves.
 *
 *     Thus, when using LIBNET_CANONNAME, dont forget to check the
 *     return value of the resolve function you call.
 * 
 * + hotname -> addr
 *
 *   - flags = LIBNET_ASCII2ADDR | LIBNET_NUMERICHOST
 * 
 *     ./gai -i google.fr -n
 *     libnet_resolve_ascii2addr(): unable to resolve 
 *     (err=-2: Name or service not known)
 *
 *     This leads to an error because the expected input should have
 *     been an Ip address because of the -n (LIBNET_NUMERICHOST)
 *     makes. And a hostname was provided
 *
 *  - flags = LIBNET_ASCII2ADDR 
 *
 *    ./gai -i google.fr 
 *    A dns query (type A) is sent to the dns to get the requested IP addr.
 *
 *  - flags = LIBNET_ASCII2ADDR | LIBNET_CANONNAME
 *
 *    Several DNS queries are made. The first one is the same as above
 *    (type A) to get the expected IP address. As a name can have
 *    several IPs, they are all checked with a type PTR DNS query.
 *
 *    ./gai -i google.fr -c
 *    ascii -> addr: name=google.fr addr=216 239 59 104
 *    ./gai -i yahoo.fr -c
 *    ascii -> addr: name=yahoo.fr addr=217 12 3 11  (FQDN ok)
 *
 *    We retrieve here the previous results: PTR requests failed for
 *    google, not for yahoo.
 * 
 *
 *  addr2ascii()
 *
 *   - flags =  LIBNET_ADDR2ASCII
 *
 *     A DNS query is made (PTR type) to retrieve the FQDN
 *     corresponding to the address. If no name comes back, the IP
 *     address is converted to a string.
 *
 *    ./gai -4 1.2.3.4
 *    addr -> ascii: name=1.2.3.4 addr=1 2 3 4
 *
 *   - flags =  LIBNET_ADDR2ASCII | LIBNET_CANONNAME
 *
 *     A DNS query is made (PTR type) to retrieve the FQDN
 *     corresponding to the address. If no names come back, a failure
 *     is returned.
 *
 *     ./gai -4 216.239.32.10 -c
 *     addr -> ascii: name=ns1.google.com addr=216 239 32 10
 *     ./gai -4 1.2.3.4  -c
 *     libnet_resolve_addr2ascii(): getnameinfo() failed (Success)
 *
 *     Note: yes, it fails ... even if errno is set to success !!!
 *
 *   - flags =  LIBNET_ADDR2ASCII | LIBNET_NUMERICHOST
 *
 *     No DNS request is made, and the address is converted to its
 *     string.
 *
 *     ./gai -4 1.2.3.4  -n
 *     addr -> ascii: name=1.2.3.4 addr=1 2 3 4
 *
 *
 *
 * DEVEL KORNER
 * ============
 *
 * Name resolution fonction, protocol independant
 * Depending on what you want, parameters will have different roles.
 * 
 * 1. Converting from name to address : LIBNET_ASCII2ADDR
 *
 * Ex #1.1 : convert a hostname to an IPv4 address
 *
 *      libnet_addr_t addr;
 *      addr.type = LIBNET_ADDR_TYPE_IP;
 *      libnet_resolve("google.com", &addr, 
 *                     LIBNET_ASCII2ADDR, errbuf);
 *
 * Ex #1.2 : convert a hostname to an IPv6 address
 *
 *      libnet_addr_t addr;
 *      addr.type = LIBNET_ADDR_TYPE_IP6;
 *      libnet_resolve("google.com", &addr, 
 *                     LIBNET_ASCII2ADDR, errbuf);
 *
 * Ex #1.3 : convert an IP as string to an address 
 *   The flag LIBNET_NUMERICHOST avoids the call to a DNS, and makes a
 *   direct conversion.
 *
 *      libnet_addr_t addr;
 *      addr.type = LIBNET_ADDR_TYPE_IP6;
 *      libnet_resolve("::1", &addr, LIBNET_NUMERICHOST, errbuf);
 *      addr.type = LIBNET_ADDR_TYPE_IP;
 *      libnet_resolve("127.0.0.1", &addr, 
 *                     LIBNET_ASCII2ADDR | LIBNET_NUMERICHOST, errbuf);
 *
 *
 * 2. Converting from an address to a canonical name
 *
 * Ex #2.1 : convert an address to its string
 *
 *       char str[LIBNET_BUFSIZE];
 *       libnet_addr_t addr;
 *       addr_pack(&addr, 
 *                 LIBNET_ADDR_TYPE_IP, 
 *                 LIBNET_IP_ADDR_BITS,
 *		   0x01020304,
 *                 IP_ADDR_LEN);
 *       libnet_resolve(str, &addr, 
 *                      LIBNET_ADDR2ASCII | LIBNET_NUMERICHOST, errbuf);
 *
 * Ex #2.2 : convert an address to its canonical name
 *
 *       char str[LIBNET_BUFSIZE];
 *       libnet_addr_t addr;
 *       addr_pack(&addr, 
 *                 LIBNET_ADDR_TYPE_IP, 
 *                 LIBNET_IP_ADDR_BITS,
 *		   0x01020304,
 *                 IP_ADDR_LEN);
 *       libnet_resolve(str, &addr, 
 *                      LIBNET_ADDR2ASCII | LIBNET_CANONNAME , errbuf);
 *
 * As soon as LIBNET_CANONNAME is given, a request is made to your DNS
 * server. Specifying both LIBNET_CANONNAME and LIBNET_NUMERICHOST is
 * of course possible. That will lead to send a request.
 */


#include <stdio.h>
#include <stdlib.h>

#include <sys/types.h>
#ifndef _WIN32
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <getopt.h>
#else
#include <winsock2.h>
#include <ws2tcpip.h>
#include "getopt.h"
typedef __int8 u_int8_t;
typedef __int16 u_int16_t;
typedef __int32 u_int32_t;
typedef __int64 u_int64_t;
#define snprintf _snprintf
#define __func__ __FUNCTION__
#pragma comment (lib, "Ws2_32.lib")
#endif

#include <assert.h>
#include <errno.h>


#define LIBNET_ASCII2ADDR   0x01
#define LIBNET_ADDR2ASCII   0x02
#define LIBNET_NS_MASK      0x03

#define LIBNET_NUMERICHOST  0x04
#define LIBNET_CANONNAME    0x08


#define LIBNET_BUFSIZE          0x100
#define LIBNET_ERRBUF_SIZE      LIBNET_BUFSIZE

typedef u_int32_t ipv4_addr_t;
typedef u_int64_t ipv6_addr_t;

/* ethernet addresses are 6 octets long */
#define LIBNET_ETH_ADDR_BITS      48
#ifndef ETHER_ADDR_LEN
#define ETHER_ADDR_LEN           0x6
#endif

/* FDDI addresses are 6 octets long */
#define LIBNET_FDDI_ADDR_BITS     48
#ifndef FDDI_ADDR_LEN
#define FDDI_ADDR_LEN            0x6
#endif

/* IPv6 addresses are 16 octets long */
#define LIBNET_IP6_ADDR_BITS     128
#ifndef IP6_ADDR_LEN
#define IP6_ADDR_LEN            0x10
#endif

/* IPv6 addresses are 16 octets long */
#define LIBNET_IP_ADDR_BITS       32
#ifndef IP_ADDR_LEN
#define IP_ADDR_LEN             0x04
#endif

   

/* 
 * Doug Song's definition of an address: very practical !!!
 * Thanks to him for libdnet and many other cool stuff
 *
 * [Adding a new kind of address]
 * Simply add it in the union below with the proper size, and put 
 * the shortcut as a define as it is done right after the union.
 */
struct libnet_addr
{
    u_int16_t		addr_type;      /* type of address */

#define LIBNET_ADDR_TYPE_NONE		0	/* No address set */
#define	LIBNET_ADDR_TYPE_ETH		1	/* Ethernet */
#define	LIBNET_ADDR_TYPE_IP		2	/* Internet Protocol v4 */
#define	LIBNET_ADDR_TYPE_IP6		3	/* Internet Protocol v6 */
#define	LIBNET_ADDR_TYPE_FDDI		4	/* FDDI */

    u_int16_t		addr_bits;      /* number of significant bits */

    union 
    {
	/* 
	 * Put here any other type of address you might want handled.
	 * Anyway, arrays at the end of the union should provide generic 
	 * name, but it is much more conveniant to have name with meanings.
	 */
	u_int8_t	__eth[ETHER_ADDR_LEN];
	u_int8_t	__fddi[FDDI_ADDR_LEN];

	ipv4_addr_t	__ipv4;
	ipv6_addr_t	__ipv6;
	
	u_int8_t       	__data8[16];
	u_int16_t	__data16[8];
	u_int32_t	__data32[4];
    } __addr_u;

#define addr_eth		__addr_u.__eth
#define addr_fddi		__addr_u.__fddi
#define addr_ip  		__addr_u.__ipv4
#define addr_ip6		__addr_u.__ipv6
#define addr_data8		__addr_u.__data8
#define addr_data16		__addr_u.__data16
#define addr_data32		__addr_u.__data32

    /* may be useful ...
      LIST_ENTRY(libnet_addr) next;    	
    */
};

typedef struct libnet_addr  libnet_addr_t;

#define addr_pack(addr, type, bits, data, len) do {	\
	(addr)->addr_type = type;			\
	(addr)->addr_bits = bits;			\
	memcpy((addr)->addr_data8, (char *)data, len);	\
} while (0)





/**
 * return : -1 on failure, 0 on success (1 if FQDN is retrievable)
 */

int
libnet_resolve_ascii2addr(char *name, size_t size, 
			  libnet_addr_t *addr, int flags, char*errbuf)
{
    struct addrinfo *gai = NULL, hints;
    int gaierr, ret;

    memset(&hints, 0, sizeof(hints));

    if (flags&LIBNET_NUMERICHOST) hints.ai_flags |= AI_NUMERICHOST ;
    if (flags&LIBNET_CANONNAME) hints.ai_flags |= AI_CANONNAME ;
    hints.ai_family = (addr->addr_type == LIBNET_ADDR_TYPE_IP ? AF_INET:AF_INET6);
    
    gaierr = getaddrinfo(name, NULL, &hints, &gai);
    if (gaierr)
    {
	snprintf(errbuf, LIBNET_ERRBUF_SIZE,
		 "%s(): unable to resolve (err=%d: %s)", 
		 __func__, gaierr, gai_strerror(gaierr));
	return -1;
    }

    if (addr->addr_type == LIBNET_ADDR_TYPE_IP)
    {
	memcpy(addr->addr_data8, 
	       (char*) &(((struct sockaddr_in*)gai->ai_addr)->sin_addr), 
	       IP_ADDR_LEN);
	addr->addr_bits = LIBNET_IP_ADDR_BITS;
    }
    else
    {
	memcpy(addr->addr_data8, 
	       (char*) &(((struct sockaddr_in6*)gai->ai_addr)->sin6_addr), 
	       IP6_ADDR_LEN);
	addr->addr_bits = LIBNET_IP6_ADDR_BITS;
    }

    ret = (gai->ai_canonname && strncmp(gai->ai_canonname, name, size) ? 1 : 0);

    freeaddrinfo(gai);

    return ret;
}


int libnet_resolve_addr2ascii(char *name, size_t size, 
			      libnet_addr_t *addr, int flags, char*errbuf)
{
    int opt = 0, socklen, addrlen;
    struct sockaddr *sa;

    if (flags & LIBNET_NUMERICHOST) opt |= NI_NUMERICHOST;
    if (flags & LIBNET_CANONNAME) opt |= NI_NAMEREQD;

    //printf("addr=%x\n", addr->addr_ip);

    if (addr->addr_type == LIBNET_ADDR_TYPE_IP)
    {
	struct sockaddr_in *sin;
	sin = (struct sockaddr_in *)malloc( sizeof(*sin));
	if (!sin)
	{
	    snprintf(errbuf, LIBNET_ERRBUF_SIZE, 
		     "%s(): cant allocate sockaddr_in\n", __func__);
	    return -1;
	}
	socklen = sizeof(*sin);
	memset(sin, 0, socklen);
	sin->sin_family = AF_INET;
	memcpy(&sin->sin_addr, &addr->addr_ip, IP_ADDR_LEN);
	addrlen = IP_ADDR_LEN;
	sa = (struct sockaddr *)sin;
    }
    else
    {
	struct sockaddr_in6 *sin6;
	sin6 = (struct sockaddr_in6 *)malloc( sizeof(*sin6));
	if (!sin6)
	{
	    snprintf(errbuf, LIBNET_ERRBUF_SIZE, 
		     "%s(): cant allocate sockaddr_in6\n", __func__);
	    return -1;
	}
	socklen = sizeof(*sin6);
	memset(sin6, 0, socklen);
	sin6->sin6_family = AF_INET6;
	memcpy(&sin6->sin6_addr, &addr->addr_ip6, IP6_ADDR_LEN);
	addrlen = IP6_ADDR_LEN;
	sa = (struct sockaddr *)sin6;
    }

    memset(name, 0, size);

    if (getnameinfo(sa, socklen, name, size, NULL, 0, opt))
    {
	snprintf(errbuf, LIBNET_ERRBUF_SIZE, 
		 "%s(): getnameinfo() failed (%s)", __func__, strerror(errno));
	free(sa);
	return -1;
    }

    free(sa);
    return 0;
}

int
libnet_resolve(char *name, size_t size, 
	       libnet_addr_t *addr, int flags, char*errbuf)
{
    assert(name);
    assert(addr);

    if ( ! (flags & LIBNET_ASCII2ADDR) && ! (flags & LIBNET_ADDR2ASCII))
    {
	snprintf(errbuf, LIBNET_ERRBUF_SIZE, 
		 "%s(): no mode provided in argument (flags=0x%x)",
		 __func__, flags);
	return -1;
    }

    if (addr->addr_type != LIBNET_ADDR_TYPE_IP && 
	addr->addr_type != LIBNET_ADDR_TYPE_IP6)
    {
	snprintf(errbuf, LIBNET_ERRBUF_SIZE, 
		 "%s(): unable to resolve this type of address (%d)",
		 __func__, addr->addr_type);
	return -1;
    }

    return (flags & LIBNET_ASCII2ADDR ? 
	    libnet_resolve_ascii2addr(name, size, addr, flags, errbuf) :
	    libnet_resolve_addr2ascii(name, size, addr, flags, errbuf) );

}

main(int argc, char **argv)
{
    char c;
    libnet_addr_t addr;
    char name[64];
    int flags = 0, ret, i;
    char errbuf[LIBNET_ERRBUF_SIZE];
#ifdef _WIN32
	WSADATA wsaData;

    if ((WSAStartup(0x0202, &wsaData)) != 0)
    {
        snprintf(errbuf, LIBNET_ERRBUF_SIZE, 
                "%s(): unable to initialize winsock 2\n", __func__);
        fprintf(stderr, "%s\n", errbuf);
		exit(EXIT_FAILURE);
    }
#endif

    memset(name, 0, sizeof(name));
    memset(&addr, 0, sizeof(addr));

    while ((c = getopt(argc, argv, "i:I:4:6:nc")) != EOF)
    {
        switch (c)
        {
	    case 'i': 
		/* input is an ascii string, I want its numerical address */
		strncpy(name, optarg, sizeof(name)-1);
		name[sizeof(name)-1] = 0;
		addr.addr_type = LIBNET_ADDR_TYPE_IP;
		flags |= LIBNET_ASCII2ADDR;
		break;

	    case 'I': 
		/* input is an ascii IPv6 string, I want its numerical address */
		strncpy(name, optarg, sizeof(name)-1);
		name[sizeof(name)-1] = 0;
		addr.addr_type = LIBNET_ADDR_TYPE_IP6;
		flags |= LIBNET_ASCII2ADDR;
		break;


	    case '4':
		/* input in an u_int32_t (IPv4 addr), I want its string */
		i = inet_addr(optarg);
		addr_pack(&addr, 
			  LIBNET_ADDR_TYPE_IP, 
			  LIBNET_IP_ADDR_BITS,
			  &i,
			  IP_ADDR_LEN);
		flags |= LIBNET_ADDR2ASCII;
		break;
		
	    case '6':
		/* input is a 16 bytes string converted to a IPv6 addr */
		memcpy(&addr.addr_data8, optarg, IP6_ADDR_LEN);
		addr.addr_type = LIBNET_ADDR_TYPE_IP6;
		addr.addr_bits = LIBNET_IP6_ADDR_BITS;
		flags |= LIBNET_ADDR2ASCII;
		break;

	    case 'c':
		flags |= LIBNET_CANONNAME;
		break;

	    case 'n':
		flags |= LIBNET_NUMERICHOST;
		break;

	    default:
		fprintf(stderr, "unkonwn option: %c\n", c);
		exit(EXIT_FAILURE);
	}
    }


    ret = libnet_resolve(name, sizeof(name), &addr, flags, errbuf);

    if (ret < 0)
    {
	fprintf(stderr, "%s\n", errbuf);
	exit(EXIT_FAILURE);
    }

    if (flags &  LIBNET_ADDR2ASCII)
    {
	printf("addr -> ascii: name=%s addr=", name);
	for (i=0; i<addr.addr_bits >> 3; i++)
	    printf("%u ", addr.addr_data8[i]);
	if (ret) printf(" (FQDN ok)");
	printf("\n");

    }
    else
    {
	printf("ascii -> addr: name=%s addr=", name);
	for (i=0; i<addr.addr_bits >> 3; i++)
	    printf("%u ", addr.addr_data8[i]);
	if (ret) printf(" (FQDN ok)");
	printf("\n");
    }

    return 0;
}