4 * Francois Gouget, 1999, based on the work of
5 * RW Hall, 1999, based on public domain code PING.C by Mike Muus (1983)
6 * and later works (c) 1989 Regents of Univ. of California - see copyright
7 * notice at end of source-code.
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
25 * - Systems like FreeBSD don't seem to support the IP_TTL option and maybe others.
26 * But using IP_HDRINCL and building the IP header by hand might work.
27 * - Not all IP options are supported.
28 * - Are ICMP handles real handles, i.e. inheritable and all? There might be some
29 * more work to do here, including server side stuff with synchronization.
30 * - This API should probably be thread safe. Is it really?
31 * - Using the winsock functions has not been tested.
36 #include <sys/types.h>
37 #ifdef HAVE_SYS_SOCKET_H
38 # include <sys/socket.h>
43 #ifdef HAVE_NETINET_IN_SYSTM_H
44 # include <netinet/in_systm.h>
46 #ifdef HAVE_NETINET_IN_H
47 # include <netinet/in.h>
50 #ifdef HAVE_SYS_TIME_H
51 # include <sys/time.h>
59 #ifdef HAVE_ARPA_INET_H
60 # include <arpa/inet.h>
62 #ifdef HAVE_SYS_POLL_H
63 # include <sys/poll.h>
74 #include "wine/debug.h"
76 /* Set up endianness macros for the ip and ip_icmp BSD headers */
78 #define BIG_ENDIAN 4321
81 #define LITTLE_ENDIAN 1234
84 #ifdef WORDS_BIGENDIAN
85 #define BYTE_ORDER BIG_ENDIAN
87 #define BYTE_ORDER LITTLE_ENDIAN
89 #endif /* BYTE_ORDER */
91 #define u_int16_t WORD
92 #define u_int32_t DWORD
94 /* These are BSD headers. We use these here because they are needed on
95 * libc5 Linux systems. On other platforms they are usually simply more
96 * complete than the native stuff, and cause less portability problems
97 * so we use them anyway.
103 WINE_DEFAULT_DEBUG_CHANNEL(icmp
);
104 WINE_DECLARE_DEBUG_CHANNEL(winediag
);
109 IP_OPTION_INFORMATION default_opts
;
112 #define IP_OPTS_UNKNOWN 0
113 #define IP_OPTS_DEFAULT 1
114 #define IP_OPTS_CUSTOM 2
117 #define MAXICMPLEN 76
119 /* The sequence number is unique process wide, so that all threads
120 * have a distinct sequence number.
122 static LONG icmp_sequence
=0;
124 static int in_cksum(u_short
*addr
, int len
)
137 *(u_char
*)(&answer
) = *(u_char
*)w
;
141 sum
= (sum
>> 16) + (sum
& 0xffff);
147 /* Receive a reply (IPv4); this function uses, takes ownership of and will always free `buffer` */
148 static DWORD
icmp_get_reply(int sid
, unsigned char *buffer
, DWORD send_time
, void *reply_buf
, DWORD reply_size
, DWORD timeout
)
150 int repsize
= MAXIPLEN
+ MAXICMPLEN
+ min(65535, reply_size
);
151 struct icmp
*icmp_header
= (struct icmp
*)buffer
;
152 char *endbuf
= (char*)reply_buf
+ reply_size
;
153 struct ip
*ip_header
= (struct ip
*)buffer
;
154 struct icmp_echo_reply
*ier
= reply_buf
;
155 unsigned short id
, seq
, cksum
;
156 struct sockaddr_in addr
;
157 int ip_header_len
= 0;
163 id
= icmp_header
->icmp_id
;
164 seq
= icmp_header
->icmp_seq
;
165 cksum
= icmp_header
->icmp_cksum
;
168 addrlen
= sizeof(addr
);
170 while (poll(&fdr
,1,timeout
)>0) {
171 recv_time
= GetTickCount();
172 res
=recvfrom(sid
, buffer
, repsize
, 0, (struct sockaddr
*)&addr
, &addrlen
);
173 TRACE("received %d bytes from %s\n",res
, inet_ntoa(addr
.sin_addr
));
174 ier
->Status
=IP_REQ_TIMED_OUT
;
176 /* Check whether we should ignore this packet */
177 if ((ip_header
->ip_p
==IPPROTO_ICMP
) && (res
>=sizeof(struct ip
)+ICMP_MINLEN
)) {
178 ip_header_len
=ip_header
->ip_hl
<< 2;
179 icmp_header
=(struct icmp
*)(((char*)ip_header
)+ip_header_len
);
180 TRACE("received an ICMP packet of type,code=%d,%d\n",icmp_header
->icmp_type
,icmp_header
->icmp_code
);
181 if (icmp_header
->icmp_type
==ICMP_ECHOREPLY
) {
182 if ((icmp_header
->icmp_id
==id
) && (icmp_header
->icmp_seq
==seq
))
184 ier
->Status
=IP_SUCCESS
;
185 SetLastError(NO_ERROR
);
188 switch (icmp_header
->icmp_type
) {
190 switch (icmp_header
->icmp_code
) {
191 case ICMP_UNREACH_HOST
:
192 #ifdef ICMP_UNREACH_HOST_UNKNOWN
193 case ICMP_UNREACH_HOST_UNKNOWN
:
195 #ifdef ICMP_UNREACH_ISOLATED
196 case ICMP_UNREACH_ISOLATED
:
198 #ifdef ICMP_UNREACH_HOST_PROHIB
199 case ICMP_UNREACH_HOST_PROHIB
:
201 #ifdef ICMP_UNREACH_TOSHOST
202 case ICMP_UNREACH_TOSHOST
:
204 ier
->Status
=IP_DEST_HOST_UNREACHABLE
;
206 case ICMP_UNREACH_PORT
:
207 ier
->Status
=IP_DEST_PORT_UNREACHABLE
;
209 case ICMP_UNREACH_PROTOCOL
:
210 ier
->Status
=IP_DEST_PROT_UNREACHABLE
;
212 case ICMP_UNREACH_SRCFAIL
:
213 ier
->Status
=IP_BAD_ROUTE
;
216 ier
->Status
=IP_DEST_NET_UNREACHABLE
;
220 if (icmp_header
->icmp_code
==ICMP_TIMXCEED_REASS
)
221 ier
->Status
=IP_TTL_EXPIRED_REASSEM
;
223 ier
->Status
=IP_TTL_EXPIRED_TRANSIT
;
226 ier
->Status
=IP_PARAM_PROBLEM
;
228 case ICMP_SOURCEQUENCH
:
229 ier
->Status
=IP_SOURCE_QUENCH
;
232 if (ier
->Status
!=IP_REQ_TIMED_OUT
) {
233 struct ip
* rep_ip_header
;
234 struct icmp
* rep_icmp_header
;
235 /* The ICMP header size of all the packets we accept is the same */
236 rep_ip_header
=(struct ip
*)(((char*)icmp_header
)+ICMP_MINLEN
);
237 rep_icmp_header
=(struct icmp
*)(((char*)rep_ip_header
)+(rep_ip_header
->ip_hl
<< 2));
239 /* Make sure that this is really a reply to our packet */
240 if (ip_header_len
+ICMP_MINLEN
+(rep_ip_header
->ip_hl
<< 2)+ICMP_MINLEN
>ip_header
->ip_len
) {
241 ier
->Status
=IP_REQ_TIMED_OUT
;
242 } else if ((rep_icmp_header
->icmp_type
!=ICMP_ECHO
) ||
243 (rep_icmp_header
->icmp_code
!=0) ||
244 (rep_icmp_header
->icmp_id
!=id
) ||
245 /* windows doesn't check this checksum, else tracert */
246 /* behind a Linux 2.2 masquerading firewall would fail*/
247 /* (rep_icmp_header->icmp_cksum!=cksum) || */
248 (rep_icmp_header
->icmp_seq
!=seq
)) {
249 /* This was not a reply to one of our packets after all */
250 TRACE("skipping type,code=%d,%d id,seq=%d,%d cksum=%d\n",
251 rep_icmp_header
->icmp_type
,rep_icmp_header
->icmp_code
,
252 rep_icmp_header
->icmp_id
,rep_icmp_header
->icmp_seq
,
253 rep_icmp_header
->icmp_cksum
);
254 TRACE("expected type,code=8,0 id,seq=%d,%d cksum=%d\n",
257 ier
->Status
=IP_REQ_TIMED_OUT
;
263 if (ier
->Status
==IP_REQ_TIMED_OUT
) {
264 /* This packet was not for us.
265 * Decrease the timeout so that we don't enter an endless loop even
266 * if we get flooded with ICMP packets that are not for us.
268 DWORD t
= (recv_time
- send_time
);
269 if (timeout
> t
) timeout
-= t
;
273 /* Check free space, should be large enough for an ICMP_ECHO_REPLY and remainning icmp data */
274 if (endbuf
-(char *)ier
< sizeof(struct icmp_echo_reply
)+(res
-ip_header_len
-ICMP_MINLEN
)) {
275 res
=ier
-(ICMP_ECHO_REPLY
*)reply_buf
;
276 SetLastError(IP_GENERAL_FAILURE
);
279 /* This is a reply to our packet */
280 memcpy(&ier
->Address
,&ip_header
->ip_src
,sizeof(IPAddr
));
281 /* Status is already set */
282 ier
->RoundTripTime
= recv_time
- send_time
;
283 ier
->DataSize
=res
-ip_header_len
-ICMP_MINLEN
;
285 ier
->Data
=endbuf
-ier
->DataSize
;
286 memcpy(ier
->Data
, ((char *)ip_header
)+ip_header_len
+ICMP_MINLEN
, ier
->DataSize
);
287 ier
->Options
.Ttl
=ip_header
->ip_ttl
;
288 ier
->Options
.Tos
=ip_header
->ip_tos
;
289 ier
->Options
.Flags
=ip_header
->ip_off
>> 13;
290 ier
->Options
.OptionsSize
=ip_header_len
-sizeof(struct ip
);
291 if (ier
->Options
.OptionsSize
!=0) {
292 ier
->Options
.OptionsData
=(unsigned char *) ier
->Data
-ier
->Options
.OptionsSize
;
293 /* FIXME: We are supposed to rearrange the option's 'source route' data */
294 memcpy(ier
->Options
.OptionsData
, ((char *)ip_header
)+ip_header_len
, ier
->Options
.OptionsSize
);
295 endbuf
=(char*)ier
->Options
.OptionsData
;
297 ier
->Options
.OptionsData
=NULL
;
301 /* Prepare for the next packet */
304 /* Check out whether there is more but don't wait this time */
308 res
=ier
-(ICMP_ECHO_REPLY
*)reply_buf
;
310 SetLastError(IP_REQ_TIMED_OUT
);
314 /* Move the data so there's no gap between it and the ICMP_ECHO_REPLY array */
315 DWORD gap_size
= endbuf
- (char*)ier
;
319 memmove(ier
, endbuf
, ((char*)reply_buf
+ reply_size
) - endbuf
);
321 /* Fix the pointers */
322 while (ier
-- != reply_buf
)
324 ier
->Data
= (char*)ier
->Data
- gap_size
;
325 if (ier
->Options
.OptionsData
)
326 ier
->Options
.OptionsData
-= gap_size
;
329 /* According to MSDN, the reply buffer needs to hold space for a IO_STATUS_BLOCK,
330 found at the very end of the reply. This is confirmed on Windows XP, but Vista
331 and later do not store it anywhere and in fact don't even require it at all.
333 However, in case old apps analyze this IO_STATUS_BLOCK and expect it, we mimic
334 it and write it out if there's enough space available in the buffer. */
335 if (gap_size
>= sizeof(IO_STATUS_BLOCK
))
337 IO_STATUS_BLOCK
*io
= (IO_STATUS_BLOCK
*)((char*)reply_buf
+ reply_size
- sizeof(IO_STATUS_BLOCK
));
339 io
->Pointer
= NULL
; /* Always NULL or STATUS_SUCCESS */
340 io
->Information
= reply_size
- gap_size
;
345 HeapFree(GetProcessHeap(), 0, buffer
);
346 TRACE("received %d replies\n",res
);
356 /***********************************************************************
357 * Icmp6CreateFile (IPHLPAPI.@)
359 HANDLE WINAPI
Icmp6CreateFile(VOID
)
363 int sid
=socket(AF_INET6
,SOCK_RAW
,IPPROTO_ICMPV6
);
366 /* Some systems (e.g. Linux 3.0+ and Mac OS X) support
367 non-privileged ICMP via SOCK_DGRAM type. */
368 sid
=socket(AF_INET6
,SOCK_DGRAM
,IPPROTO_ICMPV6
);
371 ERR_(winediag
)("Failed to use ICMPV6 (network ping), this requires special permissions.\n");
372 SetLastError(ERROR_ACCESS_DENIED
);
373 return INVALID_HANDLE_VALUE
;
376 icp
=HeapAlloc(GetProcessHeap(), 0, sizeof(*icp
));
379 SetLastError(IP_NO_RESOURCES
);
380 return INVALID_HANDLE_VALUE
;
383 icp
->default_opts
.OptionsSize
=IP_OPTS_UNKNOWN
;
388 /***********************************************************************
389 * Icmp6SendEcho2 (IPHLPAPI.@)
391 DWORD WINAPI
Icmp6SendEcho2(
394 PIO_APC_ROUTINE ApcRoutine
,
396 struct sockaddr_in6
* SourceAddress
,
397 struct sockaddr_in6
* DestinationAddress
,
400 PIP_OPTION_INFORMATION RequestOptions
,
406 FIXME("(%p, %p, %p, %p, %p, %p, %p, %d, %p, %p, %d, %d): stub\n", IcmpHandle
, Event
,
407 ApcRoutine
, ApcContext
, SourceAddress
, DestinationAddress
, RequestData
,
408 RequestSize
, RequestOptions
, ReplyBuffer
, ReplySize
, Timeout
);
409 SetLastError(ERROR_CALL_NOT_IMPLEMENTED
);
414 /***********************************************************************
415 * IcmpCreateFile (IPHLPAPI.@)
417 HANDLE WINAPI
IcmpCreateFile(VOID
)
421 int sid
=socket(AF_INET
,SOCK_RAW
,IPPROTO_ICMP
);
424 /* Some systems (e.g. Linux 3.0+ and Mac OS X) support
425 non-privileged ICMP via SOCK_DGRAM type. */
426 sid
=socket(AF_INET
,SOCK_DGRAM
,IPPROTO_ICMP
);
429 ERR_(winediag
)("Failed to use ICMP (network ping), this requires special permissions.\n");
430 SetLastError(ERROR_ACCESS_DENIED
);
431 return INVALID_HANDLE_VALUE
;
434 icp
=HeapAlloc(GetProcessHeap(), 0, sizeof(*icp
));
437 SetLastError(IP_NO_RESOURCES
);
438 return INVALID_HANDLE_VALUE
;
441 icp
->default_opts
.OptionsSize
=IP_OPTS_UNKNOWN
;
446 /***********************************************************************
447 * IcmpCloseHandle (IPHLPAPI.@)
449 BOOL WINAPI
IcmpCloseHandle(HANDLE IcmpHandle
)
451 icmp_t
* icp
=(icmp_t
*)IcmpHandle
;
452 if (IcmpHandle
==INVALID_HANDLE_VALUE
) {
453 /* FIXME: in fact win98 seems to ignore the handle value !!! */
454 SetLastError(ERROR_INVALID_HANDLE
);
459 HeapFree(GetProcessHeap (), 0, icp
);
464 /***********************************************************************
465 * IcmpSendEcho (IPHLPAPI.@)
467 DWORD WINAPI
IcmpSendEcho(
469 IPAddr DestinationAddress
,
472 PIP_OPTION_INFORMATION RequestOptions
,
478 return IcmpSendEcho2Ex(IcmpHandle
, NULL
, NULL
, NULL
, 0, DestinationAddress
,
479 RequestData
, RequestSize
, RequestOptions
, ReplyBuffer
, ReplySize
, Timeout
);
482 /***********************************************************************
483 * IcmpSendEcho2 (IPHLPAPI.@)
485 DWORD WINAPI
IcmpSendEcho2(
488 PIO_APC_ROUTINE ApcRoutine
,
490 IPAddr DestinationAddress
,
493 PIP_OPTION_INFORMATION RequestOptions
,
499 return IcmpSendEcho2Ex(IcmpHandle
, Event
, ApcRoutine
, ApcContext
, 0,
500 DestinationAddress
, RequestData
, RequestSize
, RequestOptions
,
501 ReplyBuffer
, ReplySize
, Timeout
);
504 /***********************************************************************
505 * IcmpSendEcho2Ex (IPHLPAPI.@)
507 DWORD WINAPI
IcmpSendEcho2Ex(
510 PIO_APC_ROUTINE ApcRoutine
,
512 IPAddr SourceAddress
,
513 IPAddr DestinationAddress
,
516 PIP_OPTION_INFORMATION RequestOptions
,
522 icmp_t
* icp
=(icmp_t
*)IcmpHandle
;
523 struct icmp
* icmp_header
;
524 struct sockaddr_in addr
;
525 unsigned short id
, seq
;
526 unsigned char *buffer
;
527 int reqsize
, repsize
;
530 TRACE("(%p, %p, %p, %p, %08x, %08x, %p, %d, %p, %p, %d, %d)\n", IcmpHandle
,
531 Event
, ApcRoutine
, ApcContext
, SourceAddress
, DestinationAddress
, RequestData
,
532 RequestSize
, RequestOptions
, ReplyBuffer
, ReplySize
, Timeout
);
534 if (IcmpHandle
==INVALID_HANDLE_VALUE
) {
535 /* FIXME: in fact win98 seems to ignore the handle value !!! */
536 SetLastError(ERROR_INVALID_PARAMETER
);
540 if (!ReplyBuffer
||!ReplySize
) {
541 SetLastError(ERROR_INVALID_PARAMETER
);
545 if (ReplySize
<sizeof(ICMP_ECHO_REPLY
)) {
546 SetLastError(IP_BUF_TOO_SMALL
);
549 /* check the request size against SO_MAX_MSG_SIZE using getsockopt */
551 if (!DestinationAddress
) {
552 SetLastError(ERROR_INVALID_NETNAME
);
558 FIXME("unsupported for events\n");
563 FIXME("unsupported for APCs\n");
568 FIXME("unsupported for source addresses\n");
572 /* Prepare the request */
573 id
=getpid() & 0xFFFF;
574 seq
=InterlockedIncrement(&icmp_sequence
) & 0xFFFF;
576 reqsize
=ICMP_MINLEN
+RequestSize
;
577 /* max ip header + max icmp header and error data + reply size(max 65535 on Windows) */
578 /* FIXME: request size of 65535 is not supported yet because max buffer size of raw socket on linux is 32767 */
579 repsize
= MAXIPLEN
+ MAXICMPLEN
+ min( 65535, ReplySize
);
580 buffer
= HeapAlloc(GetProcessHeap(), 0, max( repsize
, reqsize
));
581 if (buffer
== NULL
) {
582 SetLastError(ERROR_OUTOFMEMORY
);
586 icmp_header
=(struct icmp
*)buffer
;
587 icmp_header
->icmp_type
=ICMP_ECHO
;
588 icmp_header
->icmp_code
=0;
589 icmp_header
->icmp_cksum
=0;
590 icmp_header
->icmp_id
=id
;
591 icmp_header
->icmp_seq
=seq
;
592 memcpy(buffer
+ICMP_MINLEN
, RequestData
, RequestSize
);
593 icmp_header
->icmp_cksum
=in_cksum((u_short
*)buffer
,reqsize
);
595 addr
.sin_family
=AF_INET
;
596 addr
.sin_addr
.s_addr
=DestinationAddress
;
599 if (RequestOptions
!=NULL
) {
601 if (icp
->default_opts
.OptionsSize
==IP_OPTS_UNKNOWN
) {
603 /* Before we mess with the options, get the default values */
605 getsockopt(icp
->sid
,IPPROTO_IP
,IP_TTL
,(char *)&val
,&len
);
606 icp
->default_opts
.Ttl
=val
;
609 getsockopt(icp
->sid
,IPPROTO_IP
,IP_TOS
,(char *)&val
,&len
);
610 icp
->default_opts
.Tos
=val
;
611 /* FIXME: missing: handling of IP 'flags', and all the other options */
614 val
=RequestOptions
->Ttl
;
615 setsockopt(icp
->sid
,IPPROTO_IP
,IP_TTL
,(char *)&val
,sizeof(val
));
616 val
=RequestOptions
->Tos
;
617 setsockopt(icp
->sid
,IPPROTO_IP
,IP_TOS
,(char *)&val
,sizeof(val
));
618 /* FIXME: missing: handling of IP 'flags', and all the other options */
620 icp
->default_opts
.OptionsSize
=IP_OPTS_CUSTOM
;
621 } else if (icp
->default_opts
.OptionsSize
==IP_OPTS_CUSTOM
) {
624 /* Restore the default options */
625 val
=icp
->default_opts
.Ttl
;
626 setsockopt(icp
->sid
,IPPROTO_IP
,IP_TTL
,(char *)&val
,sizeof(val
));
627 val
=icp
->default_opts
.Tos
;
628 setsockopt(icp
->sid
,IPPROTO_IP
,IP_TOS
,(char *)&val
,sizeof(val
));
629 /* FIXME: missing: handling of IP 'flags', and all the other options */
631 icp
->default_opts
.OptionsSize
=IP_OPTS_DEFAULT
;
634 /* Send the packet */
635 TRACE("Sending %d bytes (RequestSize=%d) to %s\n", reqsize
, RequestSize
, inet_ntoa(addr
.sin_addr
));
639 printf("Output buffer:\n");
640 for (i
=0;i
<reqsize
;i
++)
641 printf("%2x,", buffer
[i
]);
646 send_time
= GetTickCount();
647 if (sendto(icp
->sid
, buffer
, reqsize
, 0, (struct sockaddr
*)&addr
, sizeof(addr
)) < 0) {
649 SetLastError(IP_PACKET_TOO_BIG
);
653 SetLastError(IP_DEST_NET_UNREACHABLE
);
656 SetLastError(IP_DEST_HOST_UNREACHABLE
);
659 TRACE("unknown error: errno=%d\n",errno
);
660 SetLastError(IP_GENERAL_FAILURE
);
663 HeapFree(GetProcessHeap(), 0, buffer
);
667 return icmp_get_reply(icp
->sid
, buffer
, send_time
, ReplyBuffer
, ReplySize
, Timeout
);
671 * Copyright (c) 1989 The Regents of the University of California.
672 * All rights reserved.
674 * This code is derived from software contributed to Berkeley by
677 * Redistribution and use in source and binary forms, with or without
678 * modification, are permitted provided that the following conditions
680 * 1. Redistributions of source code must retain the above copyright
681 * notice, this list of conditions and the following disclaimer.
682 * 2. Redistributions in binary form must reproduce the above copyright
683 * notice, this list of conditions and the following disclaimer in the
684 * documentation and/or other materials provided with the distribution.
685 * 3. Neither the name of the University nor the names of its contributors
686 * may be used to endorse or promote products derived from this software
687 * without specific prior written permission.
689 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
690 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
691 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
692 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
693 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
694 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
695 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
696 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
697 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
698 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF