xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/ip_match.c (revision a30b880ed60a24c405edba78187a04247f4d9d33)
1 /*	$NetBSD: ip_match.c,v 1.1.1.2 2013/01/02 18:59:13 tron Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	ip_match 3
6 /* SUMMARY
7 /*	IP address pattern matching
8 /* SYNOPSIS
9 /*	#include <ip_match.h>
10 /*
11 /*	char	*ip_match_parse(byte_codes, pattern)
12 /*	VSTRING	*byte_codes;
13 /*	char	*pattern;
14 /*
15 /*	char	*ip_match_save(byte_codes)
16 /*	const VSTRING *byte_codes;
17 /*
18 /*	int	ip_match_execute(byte_codes, addr_bytes)
19 /*	cost char *byte_codes;
20 /*	const char *addr_bytes;
21 /*
22 /*	char	*ip_match_dump(printable, byte_codes)
23 /*	VSTRING	*printable;
24 /*	const char *byte_codes;
25 /* DESCRIPTION
26 /*	This module supports IP address pattern matching. See below
27 /*	for a description of the supported address pattern syntax.
28 /*
29 /*	This implementation aims to minimize the cost of encoding
30 /*	the pattern in internal form, while still providing good
31 /*	matching performance in the typical case.   The first byte
32 /*	of an encoded pattern specifies the expected address family
33 /*	(for example, AF_INET); other details of the encoding are
34 /*	private and are subject to change.
35 /*
36 /*	ip_match_parse() converts the user-specified pattern to
37 /*	internal form. The result value is a null pointer in case
38 /*	of success, or a pointer into the byte_codes buffer with a
39 /*	detailed problem description.
40 /*
41 /*	ip_match_save() saves the result from ip_match_parse() for
42 /*	longer-term usage. The result should be passed to myfree().
43 /*
44 /*	ip_match_execute() matches a binary network in addr_bytes
45 /*	against a byte-code array in byte_codes. It is an error to
46 /*	use different address families for the byte_codes and addr_bytes
47 /*	arguments (the first byte-code value contains the expected
48 /*	address family).  The result is non-zero in case of success.
49 /*
50 /*	ip_match_dump() produces an ASCII dump of a byte-code array.
51 /*	The dump is supposed to be identical to the input pattern
52 /*	modulo upper/lower case or leading nulls with IPv6).  This
53 /*	function is primarily a debugging aid.
54 /*
55 /*	Arguments
56 /* .IP addr_bytes
57 /*	Binary network address in network-byte order.
58 /* .IP byte_codes
59 /*	Byte-code array produced by ip_match_parse().
60 /* .IP pattern
61 /*	Human-readable address pattern.
62 /* .IP printable
63 /*	storage for ASCII dump of a byte-code array.
64 /* IPV4 PATTERN SYNTAX
65 /* .ad
66 /* .fi
67 /*	An IPv4 address pattern has four fields separated by ".".
68 /*	Each field is either a decimal number, or a sequence inside
69 /*	"[]" that contains one or more ";"-separated decimal
70 /*	numbers or number..number ranges.
71 /*
72 /*	Examples of patterns are 1.2.3.4 (matches itself, as one
73 /*	would expect) and 1.2.3.[2,4,6..8] (matches 1.2.3.2, 1.2.3.4,
74 /*	1.2.3.6, 1.2.3.7, 1.2.3.8).
75 /*
76 /*	Thus, any pattern field can be a sequence inside "[]", but
77 /*	a "[]" sequence cannot span multiple address fields, and
78 /*	a pattern field cannot contain both a number and a "[]"
79 /*	sequence at the same time.
80 /*
81 /*	This means that the pattern 1.2.[3.4] is not valid (the
82 /*	sequence [3.4] cannot span two address fields) and the
83 /*	pattern 1.2.3.3[6..9] is also not valid (the last field
84 /*	cannot be both number 3 and sequence [6..9] at the same
85 /*	time).
86 /*
87 /*	The syntax for IPv4 patterns is as follows:
88 /*
89 /* .in +5
90 /*	v4pattern = v4field "." v4field "." v4field "." v4field
91 /* .br
92 /*	v4field = v4octet | "[" v4sequence "]"
93 /* .br
94 /*	v4octet = any decimal number in the range 0 through 255
95 /* .br
96 /*	v4sequence = v4seq_member | v4sequence ";" v4seq_member
97 /* .br
98 /*	v4seq_member = v4octet | v4octet ".." v4octet
99 /* .in
100 /* LICENSE
101 /* .ad
102 /* .fi
103 /*	The Secure Mailer license must be distributed with this
104 /*	software.
105 /* AUTHOR(S)
106 /*	Wietse Venema
107 /*	IBM T.J. Watson Research
108 /*	P.O. Box 704
109 /*	Yorktown Heights, NY 10598, USA
110 /*--*/
111 
112 /* System library. */
113 
114 #include <sys_defs.h>
115 #include <sys/socket.h>
116 #include <ctype.h>
117 #include <string.h>
118 
119 /* Utility library. */
120 
121 #include <msg.h>
122 #include <mymalloc.h>
123 #include <vstring.h>
124 #include <ip_match.h>
125 
126  /*
127   * Token values. The in-band values are also used as byte-code values.
128   */
129 #define IP_MATCH_CODE_OPEN	'['	/* in-band */
130 #define IP_MATCH_CODE_CLOSE	']'	/* in-band */
131 #define IP_MATCH_CODE_OVAL	'N'	/* in-band */
132 #define IP_MATCH_CODE_RANGE	'R'	/* in-band */
133 #define IP_MATCH_CODE_EOF	'\0'	/* in-band */
134 #define IP_MATCH_CODE_ERR	256	/* out-of-band */
135 
136  /*
137   * SLMs.
138   */
139 #define STR	vstring_str
140 #define LEN	VSTRING_LEN
141 
142 /* ip_match_save - make longer-term copy of byte code */
143 
ip_match_save(const VSTRING * byte_codes)144 char   *ip_match_save(const VSTRING *byte_codes)
145 {
146     char   *dst;
147 
148     dst = mymalloc(LEN(byte_codes));
149     return (memcpy(dst, STR(byte_codes), LEN(byte_codes)));
150 }
151 
152 /* ip_match_dump - byte-code pretty printer */
153 
ip_match_dump(VSTRING * printable,const char * byte_codes)154 char   *ip_match_dump(VSTRING *printable, const char *byte_codes)
155 {
156     const char *myname = "ip_match_dump";
157     const unsigned char *bp;
158     int     octet_count = 0;
159     int     ch;
160 
161     /*
162      * Sanity check. Use different dumping loops for AF_INET and AF_INET6.
163      */
164     if (*byte_codes != AF_INET)
165 	msg_panic("%s: malformed byte-code header", myname);
166 
167     /*
168      * Pretty-print and sanity-check the byte codes. Note: the loops in this
169      * code have no auto-increment at the end of the iteration. Instead, each
170      * byte-code handler bumps the byte-code pointer appropriately.
171      */
172     VSTRING_RESET(printable);
173     bp = (const unsigned char *) byte_codes + 1;
174     for (;;) {
175 
176 	/*
177 	 * Simple numeric field.
178 	 */
179 	if ((ch = *bp++) == IP_MATCH_CODE_OVAL) {
180 	    vstring_sprintf_append(printable, "%d", *bp);
181 	    bp += 1;
182 	}
183 
184 	/*
185 	 * Wild-card numeric field.
186 	 */
187 	else if (ch == IP_MATCH_CODE_OPEN) {
188 	    vstring_sprintf_append(printable, "[");
189 	    for (;;) {
190 		/* Numeric range. */
191 		if ((ch = *bp++) == IP_MATCH_CODE_RANGE) {
192 		    vstring_sprintf_append(printable, "%d..%d", bp[0], bp[1]);
193 		    bp += 2;
194 		}
195 		/* Number. */
196 		else if (ch == IP_MATCH_CODE_OVAL) {
197 		    vstring_sprintf_append(printable, "%d", *bp);
198 		    bp += 1;
199 		}
200 		/* End-of-wildcard. */
201 		else if (ch == IP_MATCH_CODE_CLOSE) {
202 		    break;
203 		}
204 		/* Corruption. */
205 		else {
206 		    msg_panic("%s: unexpected byte code (decimal %d) "
207 			      "after \"%s\"", myname, ch, STR(printable));
208 		}
209 		/* Output the wild-card field separator and repeat the loop. */
210 		if (*bp != IP_MATCH_CODE_CLOSE)
211 		    vstring_sprintf_append(printable, ";");
212 	    }
213 	    vstring_sprintf_append(printable, "]");
214 	}
215 
216 	/*
217 	 * Corruption.
218 	 */
219 	else {
220 	    msg_panic("%s: unexpected byte code (decimal %d) after \"%s\"",
221 		      myname, ch, STR(printable));
222 	}
223 
224 	/*
225 	 * Require four octets, not one more, not one less.
226 	 */
227 	if (++octet_count == 4) {
228 	    if (*bp != 0)
229 		msg_panic("%s: unexpected byte code (decimal %d) after \"%s\"",
230 			  myname, ch, STR(printable));
231 	    return (STR(printable));
232 	}
233 	if (*bp == 0)
234 	    msg_panic("%s: truncated byte code after \"%s\"",
235 		      myname, STR(printable));
236 
237 	/*
238 	 * Output the address field separator and repeat the loop.
239 	 */
240 	vstring_sprintf_append(printable, ".");
241     }
242 }
243 
244 /* ip_match_print_code_prefix - printable byte-code prefix */
245 
ip_match_print_code_prefix(const char * byte_codes,size_t len)246 static char *ip_match_print_code_prefix(const char *byte_codes, size_t len)
247 {
248     static VSTRING *printable = 0;
249     const char *fmt;
250     const char *bp;
251 
252     /*
253      * This is primarily for emergency debugging so we don't care about
254      * non-reentrancy.
255      */
256     if (printable == 0)
257 	printable = vstring_alloc(100);
258     else
259 	VSTRING_RESET(printable);
260 
261     /*
262      * Use decimal for IPv4 and hexadecimal otherwise, so that address octet
263      * values are easy to recognize.
264      */
265     fmt = (*byte_codes == AF_INET ? "%d " : "%02x ");
266     for (bp = byte_codes; bp < byte_codes + len; bp++)
267 	vstring_sprintf_append(printable, fmt, *(const unsigned char *) bp);
268 
269     return (STR(printable));
270 }
271 
272 /* ip_match_execute - byte-code matching engine */
273 
ip_match_execute(const char * byte_codes,const char * addr_bytes)274 int     ip_match_execute(const char *byte_codes, const char *addr_bytes)
275 {
276     const char *myname = "ip_match_execute";
277     const unsigned char *bp;
278     const unsigned char *ap;
279     int     octet_count = 0;
280     int     ch;
281     int     matched;
282 
283     /*
284      * Sanity check. Use different execute loops for AF_INET and AF_INET6.
285      */
286     if (*byte_codes != AF_INET)
287 	msg_panic("%s: malformed byte-code header (decimal %d)",
288 		  myname, *(const unsigned char *) byte_codes);
289 
290     /*
291      * Match the address bytes against the byte codes. Avoid problems with
292      * (char -> int) sign extension on architectures with signed characters.
293      */
294     bp = (const unsigned char *) byte_codes + 1;
295     ap = (const unsigned char *) addr_bytes;
296 
297     for (octet_count = 0; octet_count < 4; octet_count++, ap++) {
298 
299 	/*
300 	 * Simple numeric field.
301 	 */
302 	if ((ch = *bp++) == IP_MATCH_CODE_OVAL) {
303 	    if (*ap == *bp)
304 		bp += 1;
305 	    else
306 		return (0);
307 	}
308 
309 	/*
310 	 * Wild-card numeric field.
311 	 */
312 	else if (ch == IP_MATCH_CODE_OPEN) {
313 	    matched = 0;
314 	    for (;;) {
315 		/* Numeric range. */
316 		if ((ch = *bp++) == IP_MATCH_CODE_RANGE) {
317 		    if (!matched)
318 			matched = (*ap >= bp[0] && *ap <= bp[1]);
319 		    bp += 2;
320 		}
321 		/* Number. */
322 		else if (ch == IP_MATCH_CODE_OVAL) {
323 		    if (!matched)
324 			matched = (*ap == *bp);
325 		    bp += 1;
326 		}
327 		/* End-of-wildcard. */
328 		else if (ch == IP_MATCH_CODE_CLOSE) {
329 		    break;
330 		}
331 		/* Corruption. */
332 		else {
333 		    size_t  len = (const char *) bp - byte_codes - 1;
334 
335 		    msg_panic("%s: unexpected byte code (decimal %d) "
336 			      "after \"%s\"", myname, ch,
337 			      ip_match_print_code_prefix(byte_codes, len));
338 		}
339 	    }
340 	    if (matched == 0)
341 		return (0);
342 	}
343 
344 	/*
345 	 * Corruption.
346 	 */
347 	else {
348 	    size_t  len = (const char *) bp - byte_codes - 1;
349 
350 	    msg_panic("%s: unexpected byte code (decimal %d) after \"%s\"",
351 		   myname, ch, ip_match_print_code_prefix(byte_codes, len));
352 	}
353     }
354     return (1);
355 }
356 
357 /* ip_match_next_token - carve out the next token from input pattern */
358 
ip_match_next_token(char ** pstart,char ** psaved_start,int * poval)359 static int ip_match_next_token(char **pstart, char **psaved_start, int *poval)
360 {
361     unsigned char *cp;
362     int     oval;			/* octet value */
363     int     type;			/* token value */
364 
365     /*
366      * Return a literal, error, or EOF token. Update the read pointer to the
367      * start of the next token or leave it at the string terminator.
368      */
369 #define IP_MATCH_RETURN_TOK(next, type) \
370     do { *pstart = (char *) (next); return (type); } while (0)
371 
372     /*
373      * Return a token that contains an IPv4 address octet value.
374      */
375 #define IP_MATCH_RETURN_TOK_VAL(next, type, oval) do { \
376 	*poval = (oval); IP_MATCH_RETURN_TOK((next), type); \
377     } while (0)
378 
379     /*
380      * Light-weight tokenizer. Each result is an IPv4 address octet value, a
381      * literal character value, error, or EOF.
382      */
383     *psaved_start = *pstart;
384     cp = (unsigned char *) *pstart;
385     if (ISDIGIT(*cp)) {
386 	oval = *cp - '0';
387 	type = IP_MATCH_CODE_OVAL;
388 	for (cp += 1; ISDIGIT(*cp); cp++) {
389 	    oval *= 10;
390 	    oval += *cp - '0';
391 	    if (oval > 255)
392 		type = IP_MATCH_CODE_ERR;
393 	}
394 	IP_MATCH_RETURN_TOK_VAL(cp, type, oval);
395     } else {
396 	IP_MATCH_RETURN_TOK(*cp ? cp + 1 : cp, *cp);
397     }
398 }
399 
400 /* ipmatch_print_parse_error - formatted parsing error, with context */
401 
ipmatch_print_parse_error(VSTRING * reply,char * start,char * here,char * next,const char * fmt,...)402 static void PRINTFLIKE(5, 6) ipmatch_print_parse_error(VSTRING *reply,
403 						               char *start,
404 						               char *here,
405 						               char *next,
406 						        const char *fmt,...)
407 {
408     va_list ap;
409     int     start_width;
410     int     here_width;
411 
412     /*
413      * Format the error type.
414      */
415     va_start(ap, fmt);
416     vstring_vsprintf(reply, fmt, ap);
417     va_end(ap);
418 
419     /*
420      * Format the error context. The syntax is complex enough that it is
421      * worth the effort to precisely indicate what input is in error.
422      *
423      * XXX Workaround for %.*s to avoid output when a zero width is specified.
424      */
425     if (start != 0) {
426 	start_width = here - start;
427 	here_width = next - here;
428 	vstring_sprintf_append(reply, " at \"%.*s>%.*s<%s\"",
429 			       start_width, start_width == 0 ? "" : start,
430 			     here_width, here_width == 0 ? "" : here, next);
431     }
432 }
433 
434 /* ip_match_parse - parse an entire wild-card address pattern */
435 
ip_match_parse(VSTRING * byte_codes,char * pattern)436 char   *ip_match_parse(VSTRING *byte_codes, char *pattern)
437 {
438     int     octet_count;
439     char   *saved_cp;
440     char   *cp;
441     int     token_type;
442     int     look_ahead;
443     int     oval;
444     int     saved_oval;
445 
446     /*
447      * Simplify this if we change to {} for wildcard notation.
448      */
449 #define FIND_TERMINATOR(start, cp) do { \
450 	int _level = 0; \
451 	for (cp = (start) ; *cp; cp++) { \
452 	    if (*cp == '[') _level++; \
453 	    if (*cp != ']') continue; \
454 	    if (--_level == 0) break; \
455 	} \
456     } while (0)
457 
458     /*
459      * Strip [] around the entire pattern.
460      */
461     if (*pattern == '[') {
462 	FIND_TERMINATOR(pattern, cp);
463 	if (cp[0] == 0) {
464 	    vstring_sprintf(byte_codes, "missing \"]\" character");
465 	    return (STR(byte_codes));
466 	}
467 	if (cp[1] == 0) {
468 	    *cp = 0;
469 	    pattern += 1;
470 	}
471     }
472 
473     /*
474      * Sanity check. In this case we can't show any error context.
475      */
476     if (*pattern == 0) {
477 	vstring_sprintf(byte_codes, "empty address pattern");
478 	return (STR(byte_codes));
479     }
480 
481     /*
482      * Simple parser with on-the-fly encoding. For now, IPv4 support only.
483      * Use different parser loops for IPv4 and IPv6.
484      */
485     VSTRING_RESET(byte_codes);
486     VSTRING_ADDCH(byte_codes, AF_INET);
487     octet_count = 0;
488     cp = pattern;
489 
490     /*
491      * Require four address fields separated by ".", each field containing a
492      * numeric octet value or a sequence inside []. The loop head has no test
493      * and does not step the loop variable. The tokenizer advances the loop
494      * variable, and the loop termination logic is inside the loop.
495      */
496     for (;;) {
497 	switch (token_type = ip_match_next_token(&cp, &saved_cp, &oval)) {
498 
499 	    /*
500 	     * Numeric address field.
501 	     */
502 	case IP_MATCH_CODE_OVAL:
503 	    VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_OVAL);
504 	    VSTRING_ADDCH(byte_codes, oval);
505 	    break;
506 
507 	    /*
508 	     * Wild-card address field.
509 	     */
510 	case IP_MATCH_CODE_OPEN:
511 	    VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_OPEN);
512 	    /* Require ";"-separated numbers or numeric ranges. */
513 	    for (;;) {
514 		token_type = ip_match_next_token(&cp, &saved_cp, &oval);
515 		if (token_type == IP_MATCH_CODE_OVAL) {
516 		    saved_oval = oval;
517 		    look_ahead = ip_match_next_token(&cp, &saved_cp, &oval);
518 		    /* Numeric range. */
519 		    if (look_ahead == '.') {
520 			/* Brute-force parsing. */
521 			if (ip_match_next_token(&cp, &saved_cp, &oval) == '.'
522 			    && ip_match_next_token(&cp, &saved_cp, &oval)
523 			    == IP_MATCH_CODE_OVAL
524 			    && saved_oval <= oval) {
525 			    VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_RANGE);
526 			    VSTRING_ADDCH(byte_codes, saved_oval);
527 			    VSTRING_ADDCH(byte_codes, oval);
528 			    look_ahead =
529 				ip_match_next_token(&cp, &saved_cp, &oval);
530 			} else {
531 			    ipmatch_print_parse_error(byte_codes, pattern,
532 						      saved_cp, cp,
533 						      "numeric range error");
534 			    return (STR(byte_codes));
535 			}
536 		    }
537 		    /* Single number. */
538 		    else {
539 			VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_OVAL);
540 			VSTRING_ADDCH(byte_codes, saved_oval);
541 		    }
542 		    /* Require ";" or end-of-wildcard. */
543 		    token_type = look_ahead;
544 		    if (token_type == ';') {
545 			continue;
546 		    } else if (token_type == IP_MATCH_CODE_CLOSE) {
547 			break;
548 		    } else {
549 			ipmatch_print_parse_error(byte_codes, pattern,
550 						  saved_cp, cp,
551 						  "need \";\" or \"%c\"",
552 						  IP_MATCH_CODE_CLOSE);
553 			return (STR(byte_codes));
554 		    }
555 		} else {
556 		    ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp,
557 					      "need decimal number 0..255");
558 		    return (STR(byte_codes));
559 		}
560 	    }
561 	    VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_CLOSE);
562 	    break;
563 
564 	    /*
565 	     * Invalid field.
566 	     */
567 	default:
568 	    ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp,
569 				      "need decimal number 0..255 or \"%c\"",
570 				      IP_MATCH_CODE_OPEN);
571 	    return (STR(byte_codes));
572 	}
573 	octet_count += 1;
574 
575 	/*
576 	 * Require four address fields. Not one more, not one less.
577 	 */
578 	if (octet_count == 4) {
579 	    if (*cp != 0) {
580 		(void) ip_match_next_token(&cp, &saved_cp, &oval);
581 		ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp,
582 					  "garbage after pattern");
583 		return (STR(byte_codes));
584 	    }
585 	    VSTRING_ADDCH(byte_codes, 0);
586 	    return (0);
587 	}
588 
589 	/*
590 	 * Require "." before the next address field.
591 	 */
592 	if (ip_match_next_token(&cp, &saved_cp, &oval) != '.') {
593 	    ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp,
594 				      "need \".\"");
595 	    return (STR(byte_codes));
596 	}
597     }
598 }
599 
600 #ifdef TEST
601 
602  /*
603   * Dummy main program for regression tests.
604   */
605 #include <sys/socket.h>
606 #include <netinet/in.h>
607 #include <arpa/inet.h>
608 #include <stdlib.h>
609 #include <unistd.h>
610 #include <vstream.h>
611 #include <vstring_vstream.h>
612 #include <stringops.h>
613 
main(int argc,char ** argv)614 int     main(int argc, char **argv)
615 {
616     VSTRING *byte_codes = vstring_alloc(100);
617     VSTRING *line_buf = vstring_alloc(100);
618     char   *bufp;
619     char   *err;
620     char   *user_pattern;
621     char   *user_address;
622     int     echo_input = !isatty(0);
623 
624     /*
625      * Iterate over the input stream. The input format is a pattern, followed
626      * by optional addresses to match against.
627      */
628     while (vstring_fgets_nonl(line_buf, VSTREAM_IN)) {
629 	bufp = STR(line_buf);
630 	if (echo_input) {
631 	    vstream_printf("> %s\n", bufp);
632 	    vstream_fflush(VSTREAM_OUT);
633 	}
634 	if (*bufp == '#')
635 	    continue;
636 	if ((user_pattern = mystrtok(&bufp, " \t")) == 0)
637 	    continue;
638 
639 	/*
640 	 * Parse and dump the pattern.
641 	 */
642 	if ((err = ip_match_parse(byte_codes, user_pattern)) != 0) {
643 	    vstream_printf("Error: %s\n", err);
644 	} else {
645 	    vstream_printf("Code: %s\n",
646 			   ip_match_dump(line_buf, STR(byte_codes)));
647 	}
648 	vstream_fflush(VSTREAM_OUT);
649 
650 	/*
651 	 * Match the optional patterns.
652 	 */
653 	while ((user_address = mystrtok(&bufp, " \t")) != 0) {
654 	    struct in_addr netw_addr;
655 
656 	    switch (inet_pton(AF_INET, user_address, &netw_addr)) {
657 	    case 1:
658 		vstream_printf("Match %s: %s\n", user_address,
659 			       ip_match_execute(STR(byte_codes),
660 						(char *) &netw_addr.s_addr) ?
661 			       "yes" : "no");
662 		break;
663 	    case 0:
664 		vstream_printf("bad address syntax: %s\n", user_address);
665 		break;
666 	    case -1:
667 		vstream_printf("%s: %m\n", user_address);
668 		break;
669 	    }
670 	    vstream_fflush(VSTREAM_OUT);
671 	}
672     }
673     vstring_free(line_buf);
674     vstring_free(byte_codes);
675     exit(0);
676 }
677 
678 #endif
679