• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that: (1) source code distributions
7  * retain the above copyright notice and this paragraph in its entirety, (2)
8  * distributions including binary code include the above copyright notice and
9  * this paragraph in its entirety in the documentation or other materials
10  * provided with the distribution, and (3) all advertising materials mentioning
11  * features or use of this software display the following acknowledgement:
12  * ``This product includes software developed by the University of California,
13  * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
14  * the University nor the names of its contributors may be used to endorse
15  * or promote products derived from this software without specific prior
16  * written permission.
17  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
18  * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
19  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
20  */
21 
22 #ifndef lint
23 static const char rcsid[] _U_ =
24     "@(#) $Header: /tcpdump/master/tcpdump/print-ip.c,v 1.159 2007-09-14 01:29:28 guy Exp $ (LBL)";
25 #endif
26 
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30 
31 #include <tcpdump-stdinc.h>
32 
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 
37 #include "addrtoname.h"
38 #include "interface.h"
39 #include "extract.h"			/* must come after interface.h */
40 
41 #include "ip.h"
42 #include "ipproto.h"
43 
44 static const struct tok ip_option_values[] = {
45     { IPOPT_EOL, "EOL" },
46     { IPOPT_NOP, "NOP" },
47     { IPOPT_TS, "timestamp" },
48     { IPOPT_SECURITY, "security" },
49     { IPOPT_RR, "RR" },
50     { IPOPT_SSRR, "SSRR" },
51     { IPOPT_LSRR, "LSRR" },
52     { IPOPT_RA, "RA" },
53     { IPOPT_RFC1393, "traceroute" },
54     { 0, NULL }
55 };
56 
57 /*
58  * print the recorded route in an IP RR, LSRR or SSRR option.
59  */
60 static void
ip_printroute(register const u_char * cp,u_int length)61 ip_printroute(register const u_char *cp, u_int length)
62 {
63 	register u_int ptr;
64 	register u_int len;
65 
66 	if (length < 3) {
67 		printf(" [bad length %u]", length);
68 		return;
69 	}
70 	if ((length + 1) & 3)
71 		printf(" [bad length %u]", length);
72 	ptr = cp[2] - 1;
73 	if (ptr < 3 || ((ptr + 1) & 3) || ptr > length + 1)
74 		printf(" [bad ptr %u]", cp[2]);
75 
76 	for (len = 3; len < length; len += 4) {
77 		printf(" %s", ipaddr_string(&cp[len]));
78                 if (ptr > len)
79                         printf(",");
80 	}
81 }
82 
83 /*
84  * If source-routing is present and valid, return the final destination.
85  * Otherwise, return IP destination.
86  *
87  * This is used for UDP and TCP pseudo-header in the checksum
88  * calculation.
89  */
90 static u_int32_t
ip_finddst(const struct ip * ip)91 ip_finddst(const struct ip *ip)
92 {
93 	int length;
94 	int len;
95 	const u_char *cp;
96 	u_int32_t retval;
97 
98 	cp = (const u_char *)(ip + 1);
99 	length = (IP_HL(ip) << 2) - sizeof(struct ip);
100 
101 	for (; length > 0; cp += len, length -= len) {
102 		int tt;
103 
104 		TCHECK(*cp);
105 		tt = *cp;
106 		if (tt == IPOPT_EOL)
107 			break;
108 		else if (tt == IPOPT_NOP)
109 			len = 1;
110 		else {
111 			TCHECK(cp[1]);
112 			len = cp[1];
113 			if (len < 2)
114 				break;
115 		}
116 		TCHECK2(*cp, len);
117 		switch (tt) {
118 
119 		case IPOPT_SSRR:
120 		case IPOPT_LSRR:
121 			if (len < 7)
122 				break;
123 			UNALIGNED_MEMCPY(&retval, cp + len - 4, 4);
124 			return retval;
125 		}
126 	}
127 trunc:
128 	UNALIGNED_MEMCPY(&retval, &ip->ip_dst.s_addr, sizeof(u_int32_t));
129 	return retval;
130 }
131 
132 /*
133  * Compute a V4-style checksum by building a pseudoheader.
134  */
135 int
nextproto4_cksum(const struct ip * ip,const u_int8_t * data,u_int len,u_int next_proto)136 nextproto4_cksum(const struct ip *ip, const u_int8_t *data,
137 		 u_int len, u_int next_proto)
138 {
139 	struct phdr {
140 		u_int32_t src;
141 		u_int32_t dst;
142 		u_char mbz;
143 		u_char proto;
144 		u_int16_t len;
145 	} ph;
146 	struct cksum_vec vec[2];
147 
148 	/* pseudo-header.. */
149 	ph.len = htons((u_int16_t)len);
150 	ph.mbz = 0;
151 	ph.proto = next_proto;
152 	UNALIGNED_MEMCPY(&ph.src, &ip->ip_src.s_addr, sizeof(u_int32_t));
153 	if (IP_HL(ip) == 5)
154 		UNALIGNED_MEMCPY(&ph.dst, &ip->ip_dst.s_addr, sizeof(u_int32_t));
155 	else
156 		ph.dst = ip_finddst(ip);
157 
158 	vec[0].ptr = (const u_int8_t *)(void *)&ph;
159 	vec[0].len = sizeof(ph);
160 	vec[1].ptr = data;
161 	vec[1].len = len;
162 	return (in_cksum(vec, 2));
163 }
164 
165 static void
ip_printts(register const u_char * cp,u_int length)166 ip_printts(register const u_char *cp, u_int length)
167 {
168 	register u_int ptr;
169 	register u_int len;
170 	int hoplen;
171 	const char *type;
172 
173 	if (length < 4) {
174 		printf("[bad length %u]", length);
175 		return;
176 	}
177 	printf(" TS{");
178 	hoplen = ((cp[3]&0xF) != IPOPT_TS_TSONLY) ? 8 : 4;
179 	if ((length - 4) & (hoplen-1))
180 		printf("[bad length %u]", length);
181 	ptr = cp[2] - 1;
182 	len = 0;
183 	if (ptr < 4 || ((ptr - 4) & (hoplen-1)) || ptr > length + 1)
184 		printf("[bad ptr %u]", cp[2]);
185 	switch (cp[3]&0xF) {
186 	case IPOPT_TS_TSONLY:
187 		printf("TSONLY");
188 		break;
189 	case IPOPT_TS_TSANDADDR:
190 		printf("TS+ADDR");
191 		break;
192 	/*
193 	 * prespecified should really be 3, but some ones might send 2
194 	 * instead, and the IPOPT_TS_PRESPEC constant can apparently
195 	 * have both values, so we have to hard-code it here.
196 	 */
197 
198 	case 2:
199 		printf("PRESPEC2.0");
200 		break;
201 	case 3:			/* IPOPT_TS_PRESPEC */
202 		printf("PRESPEC");
203 		break;
204 	default:
205 		printf("[bad ts type %d]", cp[3]&0xF);
206 		goto done;
207 	}
208 
209 	type = " ";
210 	for (len = 4; len < length; len += hoplen) {
211 		if (ptr == len)
212 			type = " ^ ";
213 		printf("%s%d@%s", type, EXTRACT_32BITS(&cp[len+hoplen-4]),
214 		       hoplen!=8 ? "" : ipaddr_string(&cp[len]));
215 		type = " ";
216 	}
217 
218 done:
219 	printf("%s", ptr == len ? " ^ " : "");
220 
221 	if (cp[3]>>4)
222 		printf(" [%d hops not recorded]} ", cp[3]>>4);
223 	else
224 		printf("}");
225 }
226 
227 /*
228  * print IP options.
229  */
230 static void
ip_optprint(register const u_char * cp,u_int length)231 ip_optprint(register const u_char *cp, u_int length)
232 {
233 	register u_int option_len;
234 	const char *sep = "";
235 
236 	for (; length > 0; cp += option_len, length -= option_len) {
237 		u_int option_code;
238 
239 		printf("%s", sep);
240 		sep = ",";
241 
242 		TCHECK(*cp);
243 		option_code = *cp;
244 
245                 printf("%s",
246                         tok2str(ip_option_values,"unknown %u",option_code));
247 
248 		if (option_code == IPOPT_NOP ||
249                     option_code == IPOPT_EOL)
250 			option_len = 1;
251 
252 		else {
253 			TCHECK(cp[1]);
254 			option_len = cp[1];
255 			if (option_len < 2) {
256 		                printf(" [bad length %u]", option_len);
257 				return;
258 			}
259 		}
260 
261 		if (option_len > length) {
262 	                printf(" [bad length %u]", option_len);
263 			return;
264 		}
265 
266                 TCHECK2(*cp, option_len);
267 
268 		switch (option_code) {
269 		case IPOPT_EOL:
270 			return;
271 
272 		case IPOPT_TS:
273 			ip_printts(cp, option_len);
274 			break;
275 
276 		case IPOPT_RR:       /* fall through */
277 		case IPOPT_SSRR:
278 		case IPOPT_LSRR:
279 			ip_printroute(cp, option_len);
280 			break;
281 
282 		case IPOPT_RA:
283 			if (option_len < 4) {
284 				printf(" [bad length %u]", option_len);
285 				break;
286 			}
287                         TCHECK(cp[3]);
288                         if (EXTRACT_16BITS(&cp[2]) != 0)
289                             printf(" value %u", EXTRACT_16BITS(&cp[2]));
290 			break;
291 
292 		case IPOPT_NOP:       /* nothing to print - fall through */
293 		case IPOPT_SECURITY:
294 		default:
295 			break;
296 		}
297 	}
298 	return;
299 
300 trunc:
301 	printf("[|ip]");
302 }
303 
304 #define IP_RES 0x8000
305 
306 static const struct tok ip_frag_values[] = {
307         { IP_MF,        "+" },
308         { IP_DF,        "DF" },
309 	{ IP_RES,       "rsvd" }, /* The RFC3514 evil ;-) bit */
310         { 0,            NULL }
311 };
312 
313 struct ip_print_demux_state {
314 	const struct ip *ip;
315 	const u_char *cp;
316 	u_int   len, off;
317 	u_char  nh;
318 	int     advance;
319 };
320 
321 static void
ip_print_demux(netdissect_options * ndo,struct ip_print_demux_state * ipds)322 ip_print_demux(netdissect_options *ndo,
323 	       struct ip_print_demux_state *ipds)
324 {
325 	struct protoent *proto;
326 	struct cksum_vec vec[1];
327 
328 again:
329 	switch (ipds->nh) {
330 
331 	case IPPROTO_AH:
332 		ipds->nh = *ipds->cp;
333 		ipds->advance = ah_print(ipds->cp);
334 		if (ipds->advance <= 0)
335 			break;
336 		ipds->cp += ipds->advance;
337 		ipds->len -= ipds->advance;
338 		goto again;
339 
340 	case IPPROTO_ESP:
341 	{
342 		int enh, padlen;
343 		ipds->advance = esp_print(ndo, ipds->cp, ipds->len,
344 				    (const u_char *)ipds->ip,
345 				    &enh, &padlen);
346 		if (ipds->advance <= 0)
347 			break;
348 		ipds->cp += ipds->advance;
349 		ipds->len -= ipds->advance + padlen;
350 		ipds->nh = enh & 0xff;
351 		goto again;
352 	}
353 
354 	case IPPROTO_IPCOMP:
355 	{
356 		int enh;
357 		ipds->advance = ipcomp_print(ipds->cp, &enh);
358 		if (ipds->advance <= 0)
359 			break;
360 		ipds->cp += ipds->advance;
361 		ipds->len -= ipds->advance;
362 		ipds->nh = enh & 0xff;
363 		goto again;
364 	}
365 
366 	case IPPROTO_SCTP:
367 		sctp_print(ipds->cp, (const u_char *)ipds->ip, ipds->len);
368 		break;
369 
370 	case IPPROTO_DCCP:
371 		dccp_print(ipds->cp, (const u_char *)ipds->ip, ipds->len);
372 		break;
373 
374 	case IPPROTO_TCP:
375 		/* pass on the MF bit plus the offset to detect fragments */
376 		tcp_print(ipds->cp, ipds->len, (const u_char *)ipds->ip,
377 			  ipds->off & (IP_MF|IP_OFFMASK));
378 		break;
379 
380 	case IPPROTO_UDP:
381 		/* pass on the MF bit plus the offset to detect fragments */
382 		udp_print(ipds->cp, ipds->len, (const u_char *)ipds->ip,
383 			  ipds->off & (IP_MF|IP_OFFMASK));
384 		break;
385 
386 	case IPPROTO_ICMP:
387 		/* pass on the MF bit plus the offset to detect fragments */
388 		icmp_print(ipds->cp, ipds->len, (const u_char *)ipds->ip,
389 			   ipds->off & (IP_MF|IP_OFFMASK));
390 		break;
391 
392 	case IPPROTO_PIGP:
393 		/*
394 		 * XXX - the current IANA protocol number assignments
395 		 * page lists 9 as "any private interior gateway
396 		 * (used by Cisco for their IGRP)" and 88 as
397 		 * "EIGRP" from Cisco.
398 		 *
399 		 * Recent BSD <netinet/in.h> headers define
400 		 * IP_PROTO_PIGP as 9 and IP_PROTO_IGRP as 88.
401 		 * We define IP_PROTO_PIGP as 9 and
402 		 * IP_PROTO_EIGRP as 88; those names better
403 		 * match was the current protocol number
404 		 * assignments say.
405 		 */
406 		igrp_print(ipds->cp, ipds->len, (const u_char *)ipds->ip);
407 		break;
408 
409 	case IPPROTO_EIGRP:
410 		eigrp_print(ipds->cp, ipds->len);
411 		break;
412 
413 	case IPPROTO_ND:
414 		ND_PRINT((ndo, " nd %d", ipds->len));
415 		break;
416 
417 	case IPPROTO_EGP:
418 		egp_print(ipds->cp, ipds->len);
419 		break;
420 
421 	case IPPROTO_OSPF:
422 		ospf_print(ipds->cp, ipds->len, (const u_char *)ipds->ip);
423 		break;
424 
425 	case IPPROTO_IGMP:
426 		igmp_print(ipds->cp, ipds->len);
427 		break;
428 
429 	case IPPROTO_IPV4:
430 		/* DVMRP multicast tunnel (ip-in-ip encapsulation) */
431 		ip_print(ndo, ipds->cp, ipds->len);
432 		if (! vflag) {
433 			ND_PRINT((ndo, " (ipip-proto-4)"));
434 			return;
435 		}
436 		break;
437 
438 #ifdef INET6
439 	case IPPROTO_IPV6:
440 		/* ip6-in-ip encapsulation */
441 		ip6_print(ndo, ipds->cp, ipds->len);
442 		break;
443 #endif /*INET6*/
444 
445 	case IPPROTO_RSVP:
446 		rsvp_print(ipds->cp, ipds->len);
447 		break;
448 
449 	case IPPROTO_GRE:
450 		/* do it */
451 		gre_print(ipds->cp, ipds->len);
452 		break;
453 
454 	case IPPROTO_MOBILE:
455 		mobile_print(ipds->cp, ipds->len);
456 		break;
457 
458 	case IPPROTO_PIM:
459 		vec[0].ptr = ipds->cp;
460 		vec[0].len = ipds->len;
461 		pim_print(ipds->cp, ipds->len, in_cksum(vec, 1));
462 		break;
463 
464 	case IPPROTO_VRRP:
465 		if (packettype == PT_CARP) {
466 			if (vflag)
467 				(void)printf("carp %s > %s: ",
468 					     ipaddr_string(&ipds->ip->ip_src),
469 					     ipaddr_string(&ipds->ip->ip_dst));
470 			carp_print(ipds->cp, ipds->len, ipds->ip->ip_ttl);
471 		} else {
472 			if (vflag)
473 				(void)printf("vrrp %s > %s: ",
474 					     ipaddr_string(&ipds->ip->ip_src),
475 					     ipaddr_string(&ipds->ip->ip_dst));
476 			vrrp_print(ipds->cp, ipds->len, ipds->ip->ip_ttl);
477 		}
478 		break;
479 
480 	case IPPROTO_PGM:
481 		pgm_print(ipds->cp, ipds->len, (const u_char *)ipds->ip);
482 		break;
483 
484 	default:
485 		if (ndo->ndo_nflag==0 && (proto = getprotobynumber(ipds->nh)) != NULL)
486 			ND_PRINT((ndo, " %s", proto->p_name));
487 		else
488 			ND_PRINT((ndo, " ip-proto-%d", ipds->nh));
489 		ND_PRINT((ndo, " %d", ipds->len));
490 		break;
491 	}
492 }
493 
494 void
ip_print_inner(netdissect_options * ndo,const u_char * bp,u_int length,u_int nh,const u_char * bp2)495 ip_print_inner(netdissect_options *ndo,
496 	       const u_char *bp,
497 	       u_int length, u_int nh,
498 	       const u_char *bp2)
499 {
500 	struct ip_print_demux_state  ipd;
501 
502 	ipd.ip = (const struct ip *)bp2;
503 	ipd.cp = bp;
504 	ipd.len  = length;
505 	ipd.off  = 0;
506 	ipd.nh   = nh;
507 	ipd.advance = 0;
508 
509 	ip_print_demux(ndo, &ipd);
510 }
511 
512 
513 /*
514  * print an IP datagram.
515  */
516 void
ip_print(netdissect_options * ndo,const u_char * bp,u_int length)517 ip_print(netdissect_options *ndo,
518 	 const u_char *bp,
519 	 u_int length)
520 {
521 	struct ip_print_demux_state  ipd;
522 	struct ip_print_demux_state *ipds=&ipd;
523 	const u_char *ipend;
524 	u_int hlen;
525 	struct cksum_vec vec[1];
526 	u_int16_t sum, ip_sum;
527 	struct protoent *proto;
528 
529 	ipds->ip = (const struct ip *)bp;
530 	if (IP_V(ipds->ip) != 4) { /* print version if != 4 */
531 	    printf("IP%u ", IP_V(ipds->ip));
532 	    if (IP_V(ipds->ip) == 6)
533 		printf(", wrong link-layer encapsulation");
534 	}
535         else if (!eflag)
536 	    printf("IP ");
537 
538 	if ((u_char *)(ipds->ip + 1) > ndo->ndo_snapend) {
539 		printf("[|ip]");
540 		return;
541 	}
542 	if (length < sizeof (struct ip)) {
543 		(void)printf("truncated-ip %u", length);
544 		return;
545 	}
546 	hlen = IP_HL(ipds->ip) * 4;
547 	if (hlen < sizeof (struct ip)) {
548 		(void)printf("bad-hlen %u", hlen);
549 		return;
550 	}
551 
552 	ipds->len = EXTRACT_16BITS(&ipds->ip->ip_len);
553 	if (length < ipds->len)
554 		(void)printf("truncated-ip - %u bytes missing! ",
555 			ipds->len - length);
556 	if (ipds->len < hlen) {
557 #ifdef GUESS_TSO
558             if (ipds->len) {
559                 (void)printf("bad-len %u", ipds->len);
560                 return;
561             }
562             else {
563                 /* we guess that it is a TSO send */
564                 ipds->len = length;
565             }
566 #else
567             (void)printf("bad-len %u", ipds->len);
568             return;
569 #endif /* GUESS_TSO */
570 	}
571 
572 	/*
573 	 * Cut off the snapshot length to the end of the IP payload.
574 	 */
575 	ipend = bp + ipds->len;
576 	if (ipend < ndo->ndo_snapend)
577 		ndo->ndo_snapend = ipend;
578 
579 	ipds->len -= hlen;
580 
581 	ipds->off = EXTRACT_16BITS(&ipds->ip->ip_off);
582 
583         if (vflag) {
584             (void)printf("(tos 0x%x", (int)ipds->ip->ip_tos);
585             /* ECN bits */
586             if (ipds->ip->ip_tos & 0x03) {
587                 switch (ipds->ip->ip_tos & 0x03) {
588                 case 1:
589                     (void)printf(",ECT(1)");
590                     break;
591                 case 2:
592                     (void)printf(",ECT(0)");
593                     break;
594                 case 3:
595                     (void)printf(",CE");
596                 }
597             }
598 
599             if (ipds->ip->ip_ttl >= 1)
600                 (void)printf(", ttl %u", ipds->ip->ip_ttl);
601 
602 	    /*
603 	     * for the firewall guys, print id, offset.
604              * On all but the last stick a "+" in the flags portion.
605 	     * For unfragmented datagrams, note the don't fragment flag.
606 	     */
607 
608 	    (void)printf(", id %u, offset %u, flags [%s], proto %s (%u)",
609                          EXTRACT_16BITS(&ipds->ip->ip_id),
610                          (ipds->off & 0x1fff) * 8,
611                          bittok2str(ip_frag_values, "none", ipds->off&0xe000),
612                          tok2str(ipproto_values,"unknown",ipds->ip->ip_p),
613                          ipds->ip->ip_p);
614 
615             (void)printf(", length %u", EXTRACT_16BITS(&ipds->ip->ip_len));
616 
617             if ((hlen - sizeof(struct ip)) > 0) {
618                 printf(", options (");
619                 ip_optprint((u_char *)(ipds->ip + 1), hlen - sizeof(struct ip));
620                 printf(")");
621             }
622 
623 	    if (!Kflag && (u_char *)ipds->ip + hlen <= ndo->ndo_snapend) {
624 	        vec[0].ptr = (const u_int8_t *)(void *)ipds->ip;
625 	        vec[0].len = hlen;
626 	        sum = in_cksum(vec, 1);
627 		if (sum != 0) {
628 		    ip_sum = EXTRACT_16BITS(&ipds->ip->ip_sum);
629 		    (void)printf(", bad cksum %x (->%x)!", ip_sum,
630 			     in_cksum_shouldbe(ip_sum, sum));
631 		}
632 	    }
633 
634             printf(")\n    ");
635 	}
636 
637 	/*
638 	 * If this is fragment zero, hand it to the next higher
639 	 * level protocol.
640 	 */
641 	if ((ipds->off & 0x1fff) == 0) {
642 		ipds->cp = (const u_char *)ipds->ip + hlen;
643 		ipds->nh = ipds->ip->ip_p;
644 
645 		if (ipds->nh != IPPROTO_TCP && ipds->nh != IPPROTO_UDP &&
646 		    ipds->nh != IPPROTO_SCTP && ipds->nh != IPPROTO_DCCP) {
647 			(void)printf("%s > %s: ",
648 				     ipaddr_string(&ipds->ip->ip_src),
649 				     ipaddr_string(&ipds->ip->ip_dst));
650 		}
651 		ip_print_demux(ndo, ipds);
652 	} else {
653 	    /* Ultra quiet now means that all this stuff should be suppressed */
654 	    if (qflag > 1) return;
655 
656 	    /*
657 	     * if this isn't the first frag, we're missing the
658 	     * next level protocol header.  print the ip addr
659 	     * and the protocol.
660 	     */
661 	    if (ipds->off & 0x1fff) {
662 	        (void)printf("%s > %s:", ipaddr_string(&ipds->ip->ip_src),
663 			     ipaddr_string(&ipds->ip->ip_dst));
664 		if (!ndo->ndo_nflag && (proto = getprotobynumber(ipds->ip->ip_p)) != NULL)
665 		    (void)printf(" %s", proto->p_name);
666 		else
667 		    (void)printf(" ip-proto-%d", ipds->ip->ip_p);
668 	    }
669 	}
670 }
671 
672 void
ipN_print(register const u_char * bp,register u_int length)673 ipN_print(register const u_char *bp, register u_int length)
674 {
675 	struct ip hdr;
676 
677 	if (length < 4) {
678 		(void)printf("truncated-ip %d", length);
679 		return;
680 	}
681 	memcpy (&hdr, bp, 4);
682 	switch (IP_V(&hdr)) {
683 	case 4:
684 		ip_print (gndo, bp, length);
685 		return;
686 #ifdef INET6
687 	case 6:
688 		ip6_print (gndo, bp, length);
689 		return;
690 #endif
691 	default:
692 		(void)printf("unknown ip %d", IP_V(&hdr));
693 		return;
694 	}
695 }
696 
697 /*
698  * Local Variables:
699  * c-style: whitesmith
700  * c-basic-offset: 8
701  * End:
702  */
703 
704 
705