xref: /netbsd-src/external/bsd/tcpdump/dist/print-esp.c (revision b7b7574d3bf8eeb51a1fa3977b59142ec6434a55)
1 /*	NetBSD: print-ah.c,v 1.4 1996/05/20 00:41:16 fvdl Exp 	*/
2 
3 /*
4  * Copyright (c) 1988, 1989, 1990, 1991, 1992, 1993, 1994
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that: (1) source code distributions
9  * retain the above copyright notice and this paragraph in its entirety, (2)
10  * distributions including binary code include the above copyright notice and
11  * this paragraph in its entirety in the documentation or other materials
12  * provided with the distribution, and (3) all advertising materials mentioning
13  * features or use of this software display the following acknowledgement:
14  * ``This product includes software developed by the University of California,
15  * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
16  * the University nor the names of its contributors may be used to endorse
17  * or promote products derived from this software without specific prior
18  * written permission.
19  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
20  * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
21  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
22  */
23 
24 #include <sys/cdefs.h>
25 #ifndef lint
26 #if 0
27 static const char rcsid[] _U_ =
28     "@(#) Header: /tcpdump/master/tcpdump/print-esp.c,v 1.58 2007-12-07 00:03:07 mcr Exp  (LBL)";
29 #else
30 __RCSID("$NetBSD: print-esp.c,v 1.5 2013/12/31 17:33:31 christos Exp $");
31 #endif
32 #endif
33 
34 #ifdef HAVE_CONFIG_H
35 #include "config.h"
36 #endif
37 
38 #include <string.h>
39 
40 #include <tcpdump-stdinc.h>
41 
42 #include <stdlib.h>
43 
44 /* Any code in this file that depends on HAVE_LIBCRYPTO depends on
45  * HAVE_OPENSSL_EVP_H too. Undefining the former when the latter isn't defined
46  * is the simplest way of handling the dependency.
47  */
48 #ifdef HAVE_LIBCRYPTO
49 #ifdef HAVE_OPENSSL_EVP_H
50 #include <openssl/evp.h>
51 #else
52 #undef HAVE_LIBCRYPTO
53 #endif
54 #endif
55 
56 #include <stdio.h>
57 
58 #include "ip.h"
59 #include "esp.h"
60 #ifdef INET6
61 #include "ip6.h"
62 #endif
63 
64 #include "netdissect.h"
65 #include "addrtoname.h"
66 #include "extract.h"
67 
68 #ifndef HAVE_SOCKADDR_STORAGE
69 #ifdef INET6
70 struct sockaddr_storage {
71 	union {
72 		struct sockaddr_in sin;
73 		struct sockaddr_in6 sin6;
74 	} un;
75 };
76 #else
77 #define sockaddr_storage sockaddr
78 #endif
79 #endif /* HAVE_SOCKADDR_STORAGE */
80 
81 #ifdef HAVE_LIBCRYPTO
82 struct sa_list {
83 	struct sa_list	*next;
84 	struct sockaddr_storage daddr;
85 	u_int32_t	spi;          /* if == 0, then IKEv2 */
86 	int             initiator;
87 	u_char          spii[8];      /* for IKEv2 */
88 	u_char          spir[8];
89 	const EVP_CIPHER *evp;
90 	int		ivlen;
91 	int		authlen;
92 	u_char          authsecret[256];
93 	int             authsecret_len;
94 	u_char		secret[256];  /* is that big enough for all secrets? */
95 	int		secretlen;
96 };
97 
98 /*
99  * this will adjust ndo_packetp and ndo_snapend to new buffer!
100  */
101 USES_APPLE_DEPRECATED_API
102 int esp_print_decrypt_buffer_by_ikev2(netdissect_options *ndo,
103 				      int initiator,
104 				      u_char spii[8], u_char spir[8],
105 				      u_char *buf, u_char *end)
106 {
107 	struct sa_list *sa;
108 	u_char *iv;
109 	int len;
110 	EVP_CIPHER_CTX ctx;
111 
112 	/* initiator arg is any non-zero value */
113 	if(initiator) initiator=1;
114 
115 	/* see if we can find the SA, and if so, decode it */
116 	for (sa = ndo->ndo_sa_list_head; sa != NULL; sa = sa->next) {
117 		if (sa->spi == 0
118 		    && initiator == sa->initiator
119 		    && memcmp(spii, sa->spii, 8) == 0
120 		    && memcmp(spir, sa->spir, 8) == 0)
121 			break;
122 	}
123 
124 	if(sa == NULL) return 0;
125 	if(sa->evp == NULL) return 0;
126 
127 	/*
128 	 * remove authenticator, and see if we still have something to
129 	 * work with
130 	 */
131 	end = end - sa->authlen;
132 	iv  = buf;
133 	buf = buf + sa->ivlen;
134 	len = end-buf;
135 
136 	if(end <= buf) return 0;
137 
138 	memset(&ctx, 0, sizeof(ctx));
139 	if (EVP_CipherInit(&ctx, sa->evp, sa->secret, NULL, 0) < 0)
140 		(*ndo->ndo_warning)(ndo, "espkey init failed");
141 	EVP_CipherInit(&ctx, NULL, NULL, iv, 0);
142 	EVP_Cipher(&ctx, buf, buf, len);
143 	EVP_CIPHER_CTX_cleanup(&ctx);
144 
145 	ndo->ndo_packetp = buf;
146 	ndo->ndo_snapend = end;
147 
148 	return 1;
149 
150 }
151 USES_APPLE_RST
152 
153 static void esp_print_addsa(netdissect_options *ndo,
154 			    struct sa_list *sa, int sa_def)
155 {
156 	/* copy the "sa" */
157 
158 	struct sa_list *nsa;
159 
160 	nsa = (struct sa_list *)malloc(sizeof(struct sa_list));
161 	if (nsa == NULL)
162 		(*ndo->ndo_error)(ndo, "ran out of memory to allocate sa structure");
163 
164 	*nsa = *sa;
165 
166 	if (sa_def)
167 		ndo->ndo_sa_default = nsa;
168 
169 	nsa->next = ndo->ndo_sa_list_head;
170 	ndo->ndo_sa_list_head = nsa;
171 }
172 
173 
174 static u_int hexdigit(netdissect_options *ndo, char hex)
175 {
176 	if (hex >= '0' && hex <= '9')
177 		return (hex - '0');
178 	else if (hex >= 'A' && hex <= 'F')
179 		return (hex - 'A' + 10);
180 	else if (hex >= 'a' && hex <= 'f')
181 		return (hex - 'a' + 10);
182 	else {
183 		(*ndo->ndo_error)(ndo, "invalid hex digit %c in espsecret\n", hex);
184 		return 0;
185 	}
186 }
187 
188 static u_int hex2byte(netdissect_options *ndo, char *hexstring)
189 {
190 	u_int byte;
191 
192 	byte = (hexdigit(ndo, hexstring[0]) << 4) + hexdigit(ndo, hexstring[1]);
193 	return byte;
194 }
195 
196 /*
197  * returns size of binary, 0 on failure.
198  */
199 static
200 int espprint_decode_hex(netdissect_options *ndo,
201 			u_char *binbuf, unsigned int binbuf_len,
202 			char *hex)
203 {
204 	unsigned int len;
205 	int i;
206 
207 	len = strlen(hex) / 2;
208 
209 	if (len > binbuf_len) {
210 		(*ndo->ndo_warning)(ndo, "secret is too big: %d\n", len);
211 		return 0;
212 	}
213 
214 	i = 0;
215 	while (hex[0] != '\0' && hex[1]!='\0') {
216 		binbuf[i] = hex2byte(ndo, hex);
217 		hex += 2;
218 		i++;
219 	}
220 
221 	return i;
222 }
223 
224 /*
225  * decode the form:    SPINUM@IP <tab> ALGONAME:0xsecret
226  */
227 
228 USES_APPLE_DEPRECATED_API
229 static int
230 espprint_decode_encalgo(netdissect_options *ndo,
231 			char *decode, struct sa_list *sa)
232 {
233 	size_t i;
234 	const EVP_CIPHER *evp;
235 	int authlen = 0;
236 	char *colon, *p;
237 
238 	colon = strchr(decode, ':');
239 	if (colon == NULL) {
240 		(*ndo->ndo_warning)(ndo, "failed to decode espsecret: %s\n", decode);
241 		return 0;
242 	}
243 	*colon = '\0';
244 
245 	if (strlen(decode) > strlen("-hmac96") &&
246 	    !strcmp(decode + strlen(decode) - strlen("-hmac96"),
247 		    "-hmac96")) {
248 		p = strstr(decode, "-hmac96");
249 		*p = '\0';
250 		authlen = 12;
251 	}
252 	if (strlen(decode) > strlen("-cbc") &&
253 	    !strcmp(decode + strlen(decode) - strlen("-cbc"), "-cbc")) {
254 		p = strstr(decode, "-cbc");
255 		*p = '\0';
256 	}
257 	evp = EVP_get_cipherbyname(decode);
258 
259 	if (!evp) {
260 		(*ndo->ndo_warning)(ndo, "failed to find cipher algo %s\n", decode);
261 		sa->evp = NULL;
262 		sa->authlen = 0;
263 		sa->ivlen = 0;
264 		return 0;
265 	}
266 
267 	sa->evp = evp;
268 	sa->authlen = authlen;
269 	sa->ivlen = EVP_CIPHER_iv_length(evp);
270 
271 	colon++;
272 	if (colon[0] == '0' && colon[1] == 'x') {
273 		/* decode some hex! */
274 
275 		colon += 2;
276 		sa->secretlen = espprint_decode_hex(ndo, sa->secret, sizeof(sa->secret), colon);
277 		if(sa->secretlen == 0) return 0;
278 	} else {
279 		i = strlen(colon);
280 
281 		if (i < sizeof(sa->secret)) {
282 			memcpy(sa->secret, colon, i);
283 			sa->secretlen = i;
284 		} else {
285 			memcpy(sa->secret, colon, sizeof(sa->secret));
286 			sa->secretlen = sizeof(sa->secret);
287 		}
288 	}
289 
290 	return 1;
291 }
292 USES_APPLE_RST
293 
294 /*
295  * for the moment, ignore the auth algorith, just hard code the authenticator
296  * length. Need to research how openssl looks up HMAC stuff.
297  */
298 static int
299 espprint_decode_authalgo(netdissect_options *ndo,
300 			 char *decode, struct sa_list *sa)
301 {
302 	char *colon;
303 
304 	colon = strchr(decode, ':');
305 	if (colon == NULL) {
306 		(*ndo->ndo_warning)(ndo, "failed to decode espsecret: %s\n", decode);
307 		return 0;
308 	}
309 	*colon = '\0';
310 
311 	if(strcasecmp(colon,"sha1") == 0 ||
312 	   strcasecmp(colon,"md5") == 0) {
313 		sa->authlen = 12;
314 	}
315 	return 1;
316 }
317 
318 static void esp_print_decode_ikeline(netdissect_options *ndo, char *line,
319 				     const char *file, int lineno)
320 {
321 	/* it's an IKEv2 secret, store it instead */
322 	struct sa_list sa1;
323 
324 	char *init;
325 	char *icookie, *rcookie;
326 	int   ilen, rlen;
327 	char *authkey;
328 	char *enckey;
329 
330 	init = strsep(&line, " \t");
331 	icookie = strsep(&line, " \t");
332 	rcookie = strsep(&line, " \t");
333 	authkey = strsep(&line, " \t");
334 	enckey  = strsep(&line, " \t");
335 
336 	/* if any fields are missing */
337 	if(!init || !icookie || !rcookie || !authkey || !enckey) {
338 		(*ndo->ndo_warning)(ndo, "print_esp: failed to find all fields for ikev2 at %s:%u",
339 				    file, lineno);
340 
341 		return;
342 	}
343 
344 	ilen = strlen(icookie);
345 	rlen = strlen(rcookie);
346 
347 	if((init[0]!='I' && init[0]!='R')
348 	   || icookie[0]!='0' || icookie[1]!='x'
349 	   || rcookie[0]!='0' || rcookie[1]!='x'
350 	   || ilen!=18
351 	   || rlen!=18) {
352 		(*ndo->ndo_warning)(ndo, "print_esp: line %s:%u improperly formatted.",
353 				    file, lineno);
354 
355 		(*ndo->ndo_warning)(ndo, "init=%s icookie=%s(%u) rcookie=%s(%u)",
356 				    init, icookie, ilen, rcookie, rlen);
357 
358 		return;
359 	}
360 
361 	sa1.spi = 0;
362 	sa1.initiator = (init[0] == 'I');
363 	if(espprint_decode_hex(ndo, sa1.spii, sizeof(sa1.spii), icookie+2)!=8)
364 		return;
365 
366 	if(espprint_decode_hex(ndo, sa1.spir, sizeof(sa1.spir), rcookie+2)!=8)
367 		return;
368 
369 	if(!espprint_decode_encalgo(ndo, enckey, &sa1)) return;
370 
371 	if(!espprint_decode_authalgo(ndo, authkey, &sa1)) return;
372 
373 	esp_print_addsa(ndo, &sa1, FALSE);
374 }
375 
376 /*
377  *
378  * special form: file /name
379  * causes us to go read from this file instead.
380  *
381  */
382 static void esp_print_decode_onesecret(netdissect_options *ndo, char *line,
383 				       const char *file, int lineno)
384 {
385 	struct sa_list sa1;
386 	int sa_def;
387 
388 	char *spikey;
389 	char *decode;
390 
391 	spikey = strsep(&line, " \t");
392 	sa_def = 0;
393 	memset(&sa1, 0, sizeof(struct sa_list));
394 
395 	/* if there is only one token, then it is an algo:key token */
396 	if (line == NULL) {
397 		decode = spikey;
398 		spikey = NULL;
399 		/* memset(&sa1.daddr, 0, sizeof(sa1.daddr)); */
400 		/* sa1.spi = 0; */
401 		sa_def    = 1;
402 	} else
403 		decode = line;
404 
405 	if (spikey && strcasecmp(spikey, "file") == 0) {
406 		/* open file and read it */
407 		FILE *secretfile;
408 		char  fileline[1024];
409 		int   lineno=0;
410 		char  *nl;
411 		char *filename = line;
412 
413 		secretfile = fopen(filename, FOPEN_READ_TXT);
414 		if (secretfile == NULL) {
415 			perror(filename);
416 			exit(3);
417 		}
418 
419 		while (fgets(fileline, sizeof(fileline)-1, secretfile) != NULL) {
420 			lineno++;
421 			/* remove newline from the line */
422 			nl = strchr(fileline, '\n');
423 			if (nl)
424 				*nl = '\0';
425 			if (fileline[0] == '#') continue;
426 			if (fileline[0] == '\0') continue;
427 
428 			esp_print_decode_onesecret(ndo, fileline, filename, lineno);
429 		}
430 		fclose(secretfile);
431 
432 		return;
433 	}
434 
435 	if (spikey && strcasecmp(spikey, "ikev2") == 0) {
436 		esp_print_decode_ikeline(ndo, line, file, lineno);
437 		return;
438 	}
439 
440 	if (spikey) {
441 
442 		char *spistr, *foo;
443 		u_int32_t spino;
444 		struct sockaddr_in *sin;
445 #ifdef INET6
446 		struct sockaddr_in6 *sin6;
447 #endif
448 
449 		spistr = strsep(&spikey, "@");
450 
451 		spino = strtoul(spistr, &foo, 0);
452 		if (spistr == foo || !spikey) {
453 			(*ndo->ndo_warning)(ndo, "print_esp: failed to decode spi# %s\n", foo);
454 			return;
455 		}
456 
457 		sa1.spi = spino;
458 
459 		sin = (struct sockaddr_in *)&sa1.daddr;
460 #ifdef INET6
461 		sin6 = (struct sockaddr_in6 *)&sa1.daddr;
462 		if (inet_pton(AF_INET6, spikey, &sin6->sin6_addr) == 1) {
463 #ifdef HAVE_SOCKADDR_SA_LEN
464 			sin6->sin6_len = sizeof(struct sockaddr_in6);
465 #endif
466 			sin6->sin6_family = AF_INET6;
467 		} else
468 #endif
469 			if (inet_pton(AF_INET, spikey, &sin->sin_addr) == 1) {
470 #ifdef HAVE_SOCKADDR_SA_LEN
471 				sin->sin_len = sizeof(struct sockaddr_in);
472 #endif
473 				sin->sin_family = AF_INET;
474 			} else {
475 				(*ndo->ndo_warning)(ndo, "print_esp: can not decode IP# %s\n", spikey);
476 				return;
477 			}
478 	}
479 
480 	if (decode) {
481 		/* skip any blank spaces */
482 		while (isspace((unsigned char)*decode))
483 			decode++;
484 
485 		if(!espprint_decode_encalgo(ndo, decode, &sa1)) {
486 			return;
487 		}
488 	}
489 
490 	esp_print_addsa(ndo, &sa1, sa_def);
491 }
492 
493 USES_APPLE_DEPRECATED_API
494 static void esp_init(netdissect_options *ndo _U_)
495 {
496 
497 	OpenSSL_add_all_algorithms();
498 	EVP_add_cipher_alias(SN_des_ede3_cbc, "3des");
499 }
500 USES_APPLE_RST
501 
502 void esp_print_decodesecret(netdissect_options *ndo)
503 {
504 	char *line;
505 	char *p;
506 	static int initialized = 0;
507 
508 	if (!initialized) {
509 		esp_init(ndo);
510 		initialized = 1;
511 	}
512 
513 	p = ndo->ndo_espsecret;
514 
515 	while (p && p[0] != '\0') {
516 		/* pick out the first line or first thing until a comma */
517 		if ((line = strsep(&p, "\n,")) == NULL) {
518 			line = p;
519 			p = NULL;
520 		}
521 
522 		esp_print_decode_onesecret(ndo, line, "cmdline", 0);
523 	}
524 
525 	ndo->ndo_espsecret = NULL;
526 }
527 
528 #endif
529 
530 #ifdef HAVE_LIBCRYPTO
531 USES_APPLE_DEPRECATED_API
532 #endif
533 int
534 esp_print(netdissect_options *ndo,
535 	  const u_char *bp, const int length, const u_char *bp2
536 #ifndef HAVE_LIBCRYPTO
537 	_U_
538 #endif
539 	,
540 	int *nhdr
541 #ifndef HAVE_LIBCRYPTO
542 	_U_
543 #endif
544 	,
545 	int *padlen
546 #ifndef HAVE_LIBCRYPTO
547 	_U_
548 #endif
549 	)
550 {
551 	register const struct newesp *esp;
552 	register const u_char *ep;
553 #ifdef HAVE_LIBCRYPTO
554 	struct ip *ip;
555 	struct sa_list *sa = NULL;
556 #ifdef INET6
557 	struct ip6_hdr *ip6 = NULL;
558 #endif
559 	int advance;
560 	int len;
561 	u_char *secret;
562 	int ivlen = 0;
563 	u_char *ivoff;
564 	u_char *p;
565 	EVP_CIPHER_CTX ctx;
566 #endif
567 
568 	esp = (struct newesp *)bp;
569 
570 #ifdef HAVE_LIBCRYPTO
571 	secret = NULL;
572 	advance = 0;
573 #endif
574 
575 #if 0
576 	/* keep secret out of a register */
577 	p = (u_char *)&secret;
578 #endif
579 
580 	/* 'ep' points to the end of available data. */
581 	ep = ndo->ndo_snapend;
582 
583 	if ((u_char *)(esp + 1) >= ep) {
584 		fputs("[|ESP]", stdout);
585 		goto fail;
586 	}
587 	(*ndo->ndo_printf)(ndo, "ESP(spi=0x%08x", EXTRACT_32BITS(&esp->esp_spi));
588 	(*ndo->ndo_printf)(ndo, ",seq=0x%x)", EXTRACT_32BITS(&esp->esp_seq));
589         (*ndo->ndo_printf)(ndo, ", length %u", length);
590 
591 #ifndef HAVE_LIBCRYPTO
592 	goto fail;
593 #else
594 	/* initiailize SAs */
595 	if (ndo->ndo_sa_list_head == NULL) {
596 		if (!ndo->ndo_espsecret)
597 			goto fail;
598 
599 		esp_print_decodesecret(ndo);
600 	}
601 
602 	if (ndo->ndo_sa_list_head == NULL)
603 		goto fail;
604 
605 	ip = (struct ip *)bp2;
606 	switch (IP_V(ip)) {
607 #ifdef INET6
608 	case 6:
609 		ip6 = (struct ip6_hdr *)bp2;
610 		/* we do not attempt to decrypt jumbograms */
611 		if (!EXTRACT_16BITS(&ip6->ip6_plen))
612 			goto fail;
613 		/* if we can't get nexthdr, we do not need to decrypt it */
614 		len = sizeof(struct ip6_hdr) + EXTRACT_16BITS(&ip6->ip6_plen);
615 
616 		/* see if we can find the SA, and if so, decode it */
617 		for (sa = ndo->ndo_sa_list_head; sa != NULL; sa = sa->next) {
618 			struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&sa->daddr;
619 			if (sa->spi == EXTRACT_32BITS(&esp->esp_spi) &&
620 			    sin6->sin6_family == AF_INET6 &&
621 			    memcmp(&sin6->sin6_addr, &ip6->ip6_dst,
622 				   sizeof(struct in6_addr)) == 0) {
623 				break;
624 			}
625 		}
626 		break;
627 #endif /*INET6*/
628 	case 4:
629 		/* nexthdr & padding are in the last fragment */
630 		if (EXTRACT_16BITS(&ip->ip_off) & IP_MF)
631 			goto fail;
632 		len = EXTRACT_16BITS(&ip->ip_len);
633 
634 		/* see if we can find the SA, and if so, decode it */
635 		for (sa = ndo->ndo_sa_list_head; sa != NULL; sa = sa->next) {
636 			struct sockaddr_in *sin = (struct sockaddr_in *)&sa->daddr;
637 			if (sa->spi == EXTRACT_32BITS(&esp->esp_spi) &&
638 			    sin->sin_family == AF_INET &&
639 			    sin->sin_addr.s_addr == ip->ip_dst.s_addr) {
640 				break;
641 			}
642 		}
643 		break;
644 	default:
645 		goto fail;
646 	}
647 
648 	/* if we didn't find the specific one, then look for
649 	 * an unspecified one.
650 	 */
651 	if (sa == NULL)
652 		sa = ndo->ndo_sa_default;
653 
654 	/* if not found fail */
655 	if (sa == NULL)
656 		goto fail;
657 
658 	/* if we can't get nexthdr, we do not need to decrypt it */
659 	if (ep - bp2 < len)
660 		goto fail;
661 	if (ep - bp2 > len) {
662 		/* FCS included at end of frame (NetBSD 1.6 or later) */
663 		ep = bp2 + len;
664 	}
665 
666 	ivoff = (u_char *)(esp + 1) + 0;
667 	ivlen = sa->ivlen;
668 	secret = sa->secret;
669 	ep = ep - sa->authlen;
670 
671 	if (sa->evp) {
672 		memset(&ctx, 0, sizeof(ctx));
673 		if (EVP_CipherInit(&ctx, sa->evp, secret, NULL, 0) < 0)
674 			(*ndo->ndo_warning)(ndo, "espkey init failed");
675 
676 		(void)EVP_CIPHER_CTX_block_size(&ctx);
677 
678 		p = ivoff;
679 		EVP_CipherInit(&ctx, NULL, NULL, p, 0);
680 		EVP_Cipher(&ctx, p + ivlen, p + ivlen, ep - (p + ivlen));
681 		EVP_CIPHER_CTX_cleanup(&ctx);
682 		advance = ivoff - (u_char *)esp + ivlen;
683 	} else
684 		advance = sizeof(struct newesp);
685 
686 	/* sanity check for pad length */
687 	if (ep - bp < *(ep - 2))
688 		goto fail;
689 
690 	if (padlen)
691 		*padlen = *(ep - 2) + 2;
692 
693 	if (nhdr)
694 		*nhdr = *(ep - 1);
695 
696 	(ndo->ndo_printf)(ndo, ": ");
697 	return advance;
698 #endif
699 
700 fail:
701 	return -1;
702 }
703 #ifdef HAVE_LIBCRYPTO
704 USES_APPLE_RST
705 #endif
706 
707 /*
708  * Local Variables:
709  * c-style: whitesmith
710  * c-basic-offset: 8
711  * End:
712  */
713