1/*++
2/* NAME
3/*	inet_addr_local 3
4/* SUMMARY
5/*	determine if IP address is local
6/* SYNOPSIS
7/*	#include <inet_addr_local.h>
8/*
9/*	int	inet_addr_local(addr_list, mask_list, addr_family_list)
10/*	INET_ADDR_LIST *addr_list;
11/*	INET_ADDR_LIST *mask_list;
12/*	unsigned *addr_family;
13/* DESCRIPTION
14/*	inet_addr_local() determines all active IP interface addresses
15/*	of the local system. Any address found is appended to the
16/*	specified address list. The result value is the number of
17/*	active interfaces found.
18/*
19/*	The mask_list is either a null pointer, or it is a list that
20/*	receives the netmasks of the interface addresses that were found.
21/*
22/*	The addr_family_list specifies one or more of AF_INET or AF_INET6.
23/* DIAGNOSTICS
24/*	Fatal errors: out of memory.
25/* SEE ALSO
26/*	inet_addr_list(3) address list management
27/* LICENSE
28/* .ad
29/* .fi
30/*	The Secure Mailer license must be distributed with this software.
31/* AUTHOR(S)
32/*	Wietse Venema
33/*	IBM T.J. Watson Research
34/*	P.O. Box 704
35/*	Yorktown Heights, NY 10598, USA
36/*
37/*	Dean C. Strik
38/*	Department ICT
39/*	Eindhoven University of Technology
40/*	P.O. Box 513
41/*	5600 MB  Eindhoven, Netherlands
42/*	E-mail: <dean@ipnet6.org>
43/*--*/
44
45/* System library. */
46
47#include <sys_defs.h>
48#include <sys/socket.h>
49#include <sys/time.h>
50#include <netinet/in.h>
51#include <net/if.h>
52#include <sys/ioctl.h>
53#include <arpa/inet.h>
54#include <unistd.h>
55#ifdef USE_SYS_SOCKIO_H
56#include <sys/sockio.h>
57#endif
58#include <errno.h>
59#include <string.h>
60#ifdef HAS_IPV6				/* Linux only? */
61#include <netdb.h>
62#include <stdio.h>
63#endif
64#ifdef HAVE_GETIFADDRS
65#include <ifaddrs.h>
66#endif
67
68/* Utility library. */
69
70#include <msg.h>
71#include <mymalloc.h>
72#include <vstring.h>
73#include <inet_addr_list.h>
74#include <inet_addr_local.h>
75#include <myaddrinfo.h>
76#include <sock_addr.h>
77#include <mask_addr.h>
78#include <hex_code.h>
79
80 /*
81  * Postfix needs its own interface address information to determine whether
82  * or not it is an MX host for some destination; without this information,
83  * mail would loop between MX hosts. Postfix also needs its interface
84  * addresses to figure out whether or not it is final destination for
85  * addresses of the form username@[ipaddress].
86  *
87  * Postfix needs its own interface netmask information when no explicit
88  * mynetworks setting is given in main.cf, and "mynetworks_style = subnet".
89  * The mynetworks parameter controls, among others, what mail clients are
90  * allowed to relay mail through Postfix.
91  *
92  * Different systems have different ways to find out this information. We will
93  * therefore use OS dependent methods. An overview:
94  *
95  * - Use getifaddrs() when available.  This supports both IPv4/IPv6 addresses.
96  * The implementation however is not present in all major operating systems.
97  *
98  * - Use SIOCGLIFCONF when available. This supports both IPv4/IPv6 addresses.
99  * With SIOCGLIFNETMASK we can obtain the netmask for either address family.
100  * Again, this is not present in all major operating systems.
101  *
102  * - On Linux, glibc's getifaddrs(3) has returned IPv4 information for some
103  * time, but IPv6 information was not returned until 2.3.3. With older Linux
104  * versions we get IPv4 interface information with SIOCGIFCONF, and read
105  * IPv6 address/prefix information from a file in the /proc filesystem.
106  *
107  * - On other systems we expect SIOCGIFCONF to return IPv6 addresses. Since
108  * SIOCGIFNETMASK does not work reliably for IPv6 addresses, we always set
109  * the prefix length to /128 (host), and expect the user to configure a more
110  * appropriate mynetworks setting if needed.
111  *
112  * XXX: Each lookup method is implemented by its own function, so we duplicate
113  * some code. In this case, I think this is better than really drowning in
114  * the #ifdefs...
115  *
116  * -- Dean Strik (dcs)
117  */
118
119/* ial_socket - make socket for ioctl() operations */
120
121static int ial_socket(int af)
122{
123    const char *myname = "inet_addr_local[socket]";
124    int     sock;
125
126    /*
127     * The host may not be actually configured with IPv6. When IPv6 support
128     * is not actually in the kernel, don't consider failure to create an
129     * IPv6 socket as fatal. This could be tuned better though. For other
130     * families, the error is fatal.
131     *
132     * XXX Now that Postfix controls protocol support centrally with the
133     * inet_proto(3) module, this workaround should no longer be needed.
134     */
135    if ((sock = socket(af, SOCK_DGRAM, 0)) < 0) {
136#ifdef HAS_IPV6
137	if (af == AF_INET6) {
138	    if (msg_verbose)
139		msg_warn("%s: socket: %m", myname);
140	    return (-1);
141	}
142#endif
143	msg_fatal("%s: socket: %m", myname);
144    }
145    return (sock);
146}
147
148#ifdef HAVE_GETIFADDRS
149
150/*
151 * The getifaddrs(3) function, introduced by BSD/OS, provides a
152 * platform-independent way of requesting interface addresses,
153 * including IPv6 addresses. The implementation however is not
154 * present in all major operating systems.
155 */
156
157/* ial_getifaddrs - determine IP addresses using getifaddrs(3) */
158
159static int ial_getifaddrs(INET_ADDR_LIST *addr_list,
160			          INET_ADDR_LIST *mask_list,
161			          int af)
162{
163    const char *myname = "inet_addr_local[getifaddrs]";
164    struct ifaddrs *ifap, *ifa;
165    struct sockaddr *sa, *sam;
166
167    if (getifaddrs(&ifap) < 0)
168	msg_fatal("%s: getifaddrs: %m", myname);
169
170    /*
171     * Get the address of each IP network interface. According to BIND we
172     * must include interfaces that are down because the machine may still
173     * receive packets for that address (yes, via some other interface).
174     * Having no way to verify this claim on every machine, I will give them
175     * the benefit of the doubt.
176     *
177     * FIX 200501: The IPv6 patch did not report NetBSD loopback interfaces;
178     * fixed by replacing IFF_RUNNING by IFF_UP.
179     *
180     * FIX 200501: The IPV6 patch did not skip wild-card interface addresses
181     * (tested on FreeBSD).
182     */
183    for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
184	if (!(ifa->ifa_flags & IFF_UP) || ifa->ifa_addr == 0)
185	    continue;
186	sa = ifa->ifa_addr;
187	if (af != AF_UNSPEC && sa->sa_family != af)
188	    continue;
189	sam = ifa->ifa_netmask;
190	if (sam == 0) {
191	    /* XXX In mynetworks, a null netmask would match everyone. */
192	    msg_warn("ignoring interface with null netmask, address family %d",
193		     sa->sa_family);
194	    continue;
195	}
196	switch (sa->sa_family) {
197	case AF_INET:
198	    if (SOCK_ADDR_IN_ADDR(sa).s_addr == INADDR_ANY)
199		continue;
200	    break;
201#ifdef HAS_IPV6
202	case AF_INET6:
203	    if (IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa)))
204		continue;
205	    break;
206#endif
207	default:
208	    continue;
209	}
210
211	inet_addr_list_append(addr_list, sa);
212	if (mask_list != 0) {
213
214	    /*
215	     * Unfortunately, sa_len/sa_family may be broken in the netmask
216	     * sockaddr structure. We must fix this manually to have correct
217	     * addresses.   --dcs
218	     */
219#ifdef HAS_SA_LEN
220	    sam->sa_len = sa->sa_family == AF_INET6 ?
221		sizeof(struct sockaddr_in6) :
222		sizeof(struct sockaddr_in);
223#endif
224	    sam->sa_family = sa->sa_family;
225	    inet_addr_list_append(mask_list, sam);
226	}
227    }
228    freeifaddrs(ifap);
229    return (0);
230}
231
232#endif					/* HAVE_GETIFADDRS */
233
234#ifdef HAS_SIOCGLIF
235
236/*
237 * The SIOCLIF* ioctls are the successors of SIOCGIF* on the Solaris
238 * and HP/UX operating systems. The data is stored in sockaddr_storage
239 * structure. Both IPv4 and IPv6 addresses are returned though these
240 * calls.
241 */
242#define NEXT_INTERFACE(lifr)	(lifr + 1)
243#define LIFREQ_SIZE(lifr)	sizeof(lifr[0])
244
245/* ial_siocglif - determine IP addresses using ioctl(SIOCGLIF*) */
246
247static int ial_siocglif(INET_ADDR_LIST *addr_list,
248			        INET_ADDR_LIST *mask_list,
249			        int af)
250{
251    const char *myname = "inet_addr_local[siocglif]";
252    struct lifconf lifc;
253    struct lifreq *lifr;
254    struct lifreq *lifr_mask;
255    struct lifreq *the_end;
256    struct sockaddr *sa;
257    int     sock;
258    VSTRING *buf;
259
260    /*
261     * See also comments in ial_siocgif()
262     */
263    if (af != AF_INET && af != AF_INET6)
264	msg_fatal("%s: address family was %d, must be AF_INET (%d) or "
265		  "AF_INET6 (%d)", myname, af, AF_INET, AF_INET6);
266    sock = ial_socket(af);
267    if (sock < 0)
268	return (0);
269    buf = vstring_alloc(1024);
270    for (;;) {
271	memset(&lifc, 0, sizeof(lifc));
272	lifc.lifc_family = AF_UNSPEC;		/* XXX Why??? */
273	lifc.lifc_len = vstring_avail(buf);
274	lifc.lifc_buf = vstring_str(buf);
275	if (ioctl(sock, SIOCGLIFCONF, (char *) &lifc) < 0) {
276	    if (errno != EINVAL)
277		msg_fatal("%s: ioctl SIOCGLIFCONF: %m", myname);
278	} else if (lifc.lifc_len < vstring_avail(buf) / 2)
279	    break;
280	VSTRING_SPACE(buf, vstring_avail(buf) * 2);
281    }
282
283    the_end = (struct lifreq *) (lifc.lifc_buf + lifc.lifc_len);
284    for (lifr = lifc.lifc_req; lifr < the_end;) {
285	sa = (struct sockaddr *) & lifr->lifr_addr;
286	if (sa->sa_family != af) {
287	    lifr = NEXT_INTERFACE(lifr);
288	    continue;
289	}
290	if (af == AF_INET) {
291	    if (SOCK_ADDR_IN_ADDR(sa).s_addr == INADDR_ANY) {
292		lifr = NEXT_INTERFACE(lifr);
293		continue;
294	    }
295#ifdef HAS_IPV6
296	} else if (af == AF_INET6) {
297	    if (IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa))) {
298		lifr = NEXT_INTERFACE(lifr);
299		continue;
300	    }
301	}
302#endif
303	inet_addr_list_append(addr_list, sa);
304	if (mask_list) {
305	    lifr_mask = (struct lifreq *) mymalloc(sizeof(struct lifreq));
306	    memcpy((char *) lifr_mask, (char *) lifr, sizeof(struct lifreq));
307	    if (ioctl(sock, SIOCGLIFNETMASK, lifr_mask) < 0)
308		msg_fatal("%s: ioctl(SIOCGLIFNETMASK): %m", myname);
309	    /* XXX: Check whether sa_len/family are honoured --dcs */
310	    inet_addr_list_append(mask_list,
311				(struct sockaddr *) & lifr_mask->lifr_addr);
312	    myfree((char *) lifr_mask);
313	}
314	lifr = NEXT_INTERFACE(lifr);
315    }
316    vstring_free(buf);
317    (void) close(sock);
318    return (0);
319}
320
321#else					/* HAVE_SIOCGLIF */
322
323/*
324 * The classic SIOCGIF* ioctls. Modern BSD operating systems will
325 * also return IPv6 addresses through these structure. Note however
326 * that recent versions of these operating systems have getifaddrs.
327 */
328#if defined(_SIZEOF_ADDR_IFREQ)
329#define NEXT_INTERFACE(ifr)	((struct ifreq *) \
330	((char *) ifr + _SIZEOF_ADDR_IFREQ(*ifr)))
331#define IFREQ_SIZE(ifr)	_SIZEOF_ADDR_IFREQ(*ifr)
332#elif defined(HAS_SA_LEN)
333#define NEXT_INTERFACE(ifr)	((struct ifreq *) \
334	((char *) ifr + sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len))
335#define IFREQ_SIZE(ifr)	(sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len)
336#else
337#define NEXT_INTERFACE(ifr)	(ifr + 1)
338#define IFREQ_SIZE(ifr)	sizeof(ifr[0])
339#endif
340
341/* ial_siocgif - determine IP addresses using ioctl(SIOCGIF*) */
342
343static int ial_siocgif(INET_ADDR_LIST *addr_list,
344		               INET_ADDR_LIST *mask_list,
345		               int af)
346{
347    const char *myname = "inet_addr_local[siocgif]";
348    struct in_addr addr;
349    struct ifconf ifc;
350    struct ifreq *ifr;
351    struct ifreq *ifr_mask;
352    struct ifreq *the_end;
353    int     sock;
354    VSTRING *buf;
355
356    /*
357     * Get the network interface list. XXX The socket API appears to have no
358     * function that returns the number of network interfaces, so we have to
359     * guess how much space is needed to store the result.
360     *
361     * On BSD-derived systems, ioctl SIOCGIFCONF returns as much information as
362     * possible, leaving it up to the application to repeat the request with
363     * a larger buffer if the result caused a tight fit.
364     *
365     * Other systems, such as Solaris 2.5, generate an EINVAL error when the
366     * buffer is too small for the entire result. Workaround: ignore EINVAL
367     * errors and repeat the request with a larger buffer. The downside is
368     * that the program can run out of memory due to a non-memory problem,
369     * making it more difficult than necessary to diagnose the real problem.
370     */
371    sock = ial_socket(af);
372    if (sock < 0)
373	return (0);
374    buf = vstring_alloc(1024);
375    for (;;) {
376	ifc.ifc_len = vstring_avail(buf);
377	ifc.ifc_buf = vstring_str(buf);
378	if (ioctl(sock, SIOCGIFCONF, (char *) &ifc) < 0) {
379	    if (errno != EINVAL)
380		msg_fatal("%s: ioctl SIOCGIFCONF: %m", myname);
381	} else if (ifc.ifc_len < vstring_avail(buf) / 2)
382	    break;
383	VSTRING_SPACE(buf, vstring_avail(buf) * 2);
384    }
385
386    the_end = (struct ifreq *) (ifc.ifc_buf + ifc.ifc_len);
387    for (ifr = ifc.ifc_req; ifr < the_end;) {
388	if (ifr->ifr_addr.sa_family != af) {
389	    ifr = NEXT_INTERFACE(ifr);
390	    continue;
391	}
392	if (af == AF_INET) {
393	    addr = ((struct sockaddr_in *) & ifr->ifr_addr)->sin_addr;
394	    if (addr.s_addr != INADDR_ANY) {
395		inet_addr_list_append(addr_list, &ifr->ifr_addr);
396		if (mask_list) {
397		    ifr_mask = (struct ifreq *) mymalloc(IFREQ_SIZE(ifr));
398		    memcpy((char *) ifr_mask, (char *) ifr, IFREQ_SIZE(ifr));
399		    if (ioctl(sock, SIOCGIFNETMASK, ifr_mask) < 0)
400			msg_fatal("%s: ioctl SIOCGIFNETMASK: %m", myname);
401
402		    /*
403		     * Note that this SIOCGIFNETMASK has truly screwed up the
404		     * contents of sa_len/sa_family. We must fix this
405		     * manually to have correct addresses.   --dcs
406		     */
407		    ifr_mask->ifr_addr.sa_family = af;
408#ifdef HAS_SA_LEN
409		    ifr_mask->ifr_addr.sa_len = sizeof(struct sockaddr_in);
410#endif
411		    inet_addr_list_append(mask_list, &ifr_mask->ifr_addr);
412		    myfree((char *) ifr_mask);
413		}
414	    }
415	}
416#ifdef HAS_IPV6
417	else if (af == AF_INET6) {
418	    struct sockaddr *sa;
419
420	    sa = SOCK_ADDR_PTR(&ifr->ifr_addr);
421	    if (!(IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa)))) {
422		inet_addr_list_append(addr_list, sa);
423		if (mask_list) {
424		    /* XXX Assume /128 for everything */
425		    struct sockaddr_in6 mask6;
426
427		    mask6 = *SOCK_ADDR_IN6_PTR(sa);
428		    memset((char *) &mask6.sin6_addr, ~0,
429			   sizeof(mask6.sin6_addr));
430		    inet_addr_list_append(mask_list, SOCK_ADDR_PTR(&mask6));
431		}
432	    }
433	}
434#endif
435	ifr = NEXT_INTERFACE(ifr);
436    }
437    vstring_free(buf);
438    (void) close(sock);
439    return (0);
440}
441
442#endif					/* HAVE_SIOCGLIF */
443
444#ifdef HAS_PROCNET_IFINET6
445
446/*
447 * Older Linux versions lack proper calls to retrieve IPv6 interface
448 * addresses. Instead, the addresses can be read from a file in the
449 * /proc tree. The most important issue with this approach however
450 * is that the /proc tree may not always be available, for example
451 * in a chrooted environment or in "hardened" (sic) installations.
452 */
453
454/* ial_procnet_ifinet6 - determine IPv6 addresses using /proc/net/if_inet6 */
455
456static int ial_procnet_ifinet6(INET_ADDR_LIST *addr_list,
457			               INET_ADDR_LIST *mask_list)
458{
459    const char *myname = "inet_addr_local[procnet_ifinet6]";
460    FILE   *fp;
461    char    buf[BUFSIZ];
462    unsigned plen;
463    VSTRING *addrbuf;
464    struct sockaddr_in6 addr;
465    struct sockaddr_in6 mask;
466
467    /*
468     * Example: 00000000000000000000000000000001 01 80 10 80 lo
469     *
470     * Fields: address, interface index, prefix length, scope value
471     * (net/ipv6.h), interface flags (linux/rtnetlink.h), device name.
472     *
473     * FIX 200501 The IPv6 patch used fscanf(), which will hang on unexpected
474     * input. Use fgets() + sscanf() instead.
475     */
476    if ((fp = fopen(_PATH_PROCNET_IFINET6, "r")) != 0) {
477	addrbuf = vstring_alloc(MAI_V6ADDR_BYTES + 1);
478	memset((char *) &addr, 0, sizeof(addr));
479	addr.sin6_family = AF_INET6;
480#ifdef HAS_SA_LEN
481	addr.sin6_len = sizeof(addr);
482#endif
483	mask = addr;
484	while (fgets(buf, sizeof(buf), fp) != 0) {
485	    /* 200501 hex_decode() is light-weight compared to getaddrinfo(). */
486	    if (hex_decode(addrbuf, buf, MAI_V6ADDR_BYTES * 2) == 0
487		|| sscanf(buf + MAI_V6ADDR_BYTES * 2, " %*x %x", &plen) != 1
488		|| plen > MAI_V6ADDR_BITS) {
489		msg_warn("unexpected data in %s - skipping IPv6 configuration",
490			 _PATH_PROCNET_IFINET6);
491		break;
492	    }
493	    /* vstring_str(addrbuf) has worst-case alignment. */
494	    addr.sin6_addr = *(struct in6_addr *) vstring_str(addrbuf);
495	    inet_addr_list_append(addr_list, SOCK_ADDR_PTR(&addr));
496
497	    memset((char *) &mask.sin6_addr, ~0, sizeof(mask.sin6_addr));
498	    mask_addr((unsigned char *) &mask.sin6_addr,
499		      sizeof(mask.sin6_addr), plen);
500	    inet_addr_list_append(mask_list, SOCK_ADDR_PTR(&mask));
501	}
502	vstring_free(addrbuf);
503	fclose(fp);				/* FIX 200501 */
504    } else {
505	msg_warn("can't open %s (%m) - skipping IPv6 configuration",
506		 _PATH_PROCNET_IFINET6);
507    }
508    return (0);
509}
510
511#endif					/* HAS_PROCNET_IFINET6 */
512
513/* inet_addr_local - find all IP addresses for this host */
514
515int     inet_addr_local(INET_ADDR_LIST *addr_list, INET_ADDR_LIST *mask_list,
516			        unsigned *addr_family_list)
517{
518    const char *myname = "inet_addr_local";
519    int     initial_count = addr_list->used;
520    unsigned family;
521    int     count;
522
523    while ((family = *addr_family_list++) != 0) {
524
525	/*
526	 * IP Version 4
527	 */
528	if (family == AF_INET) {
529	    count = addr_list->used;
530#if defined(HAVE_GETIFADDRS)
531	    ial_getifaddrs(addr_list, mask_list, AF_INET);
532#elif defined (HAS_SIOCGLIF)
533	    ial_siocglif(addr_list, mask_list, AF_INET);
534#else
535	    ial_siocgif(addr_list, mask_list, AF_INET);
536#endif
537	    if (msg_verbose)
538		msg_info("%s: configured %d IPv4 addresses",
539			 myname, addr_list->used - count);
540	}
541
542	/*
543	 * IP Version 6
544	 */
545#ifdef HAS_IPV6
546	else if (family == AF_INET6) {
547	    count = addr_list->used;
548#if defined(HAVE_GETIFADDRS)
549	    ial_getifaddrs(addr_list, mask_list, AF_INET6);
550#elif defined(HAS_PROCNET_IFINET6)
551	    ial_procnet_ifinet6(addr_list, mask_list);
552#elif defined(HAS_SIOCGLIF)
553	    ial_siocglif(addr_list, mask_list, AF_INET6);
554#else
555	    ial_siocgif(addr_list, mask_list, AF_INET6);
556#endif
557	    if (msg_verbose)
558		msg_info("%s: configured %d IPv6 addresses", myname,
559			 addr_list->used - count);
560	}
561#endif
562
563	/*
564	 * Something's not right.
565	 */
566	else
567	    msg_panic("%s: unknown address family %d", myname, family);
568    }
569    return (addr_list->used - initial_count);
570}
571
572#ifdef TEST
573
574#include <string.h>
575#include <vstream.h>
576#include <msg_vstream.h>
577#include <inet_proto.h>
578
579int     main(int unused_argc, char **argv)
580{
581    INET_ADDR_LIST addr_list;
582    INET_ADDR_LIST mask_list;
583    MAI_HOSTADDR_STR hostaddr;
584    MAI_HOSTADDR_STR hostmask;
585    struct sockaddr *sa;
586    int     i;
587    INET_PROTO_INFO *proto_info;
588
589    msg_vstream_init(argv[0], VSTREAM_ERR);
590    msg_verbose = 1;
591
592    proto_info = inet_proto_init(argv[0],
593				 argv[1] ? argv[1] : INET_PROTO_NAME_ALL);
594    inet_addr_list_init(&addr_list);
595    inet_addr_list_init(&mask_list);
596    inet_addr_local(&addr_list, &mask_list, proto_info->ai_family_list);
597
598    if (addr_list.used == 0)
599	msg_fatal("cannot find any active network interfaces");
600
601    if (addr_list.used == 1)
602	msg_warn("found only one active network interface");
603
604    for (i = 0; i < addr_list.used; i++) {
605	sa = SOCK_ADDR_PTR(addr_list.addrs + i);
606	SOCKADDR_TO_HOSTADDR(SOCK_ADDR_PTR(sa), SOCK_ADDR_LEN(sa),
607			     &hostaddr, (MAI_SERVPORT_STR *) 0, 0);
608	sa = SOCK_ADDR_PTR(mask_list.addrs + i);
609	SOCKADDR_TO_HOSTADDR(SOCK_ADDR_PTR(sa), SOCK_ADDR_LEN(sa),
610			     &hostmask, (MAI_SERVPORT_STR *) 0, 0);
611	vstream_printf("%s/%s\n", hostaddr.buf, hostmask.buf);
612	vstream_fflush(VSTREAM_OUT);
613    }
614    inet_addr_list_free(&addr_list);
615    inet_addr_list_free(&mask_list);
616    return (0);
617}
618
619#endif
620