Branch data Line data Source code
1 : : /*
2 : : * See the file "COPYING" in the main distribution directory for copyright.
3 : : */
4 : : #ifndef lint
5 : : static const char rcsid[] =
6 : : "@(#) $Id: nb_dns.c 6219 2008-10-01 05:39:07Z vern $ (LBL)";
7 : : #endif
8 : : /*
9 : : * nb_dns - non-blocking dns routines
10 : : *
11 : : * This version works with BIND 9
12 : : *
13 : : * Note: The code here is way more complicated than it should be but
14 : : * although the interface to send requests is public, the routine to
15 : : * crack reply buffers is private.
16 : : */
17 : :
18 : : #include "config.h" /* must appear before first ifdef */
19 : :
20 : : #include <sys/types.h>
21 : : #include <sys/socket.h>
22 : :
23 : : #include <netinet/in.h>
24 : :
25 : : #include <arpa/inet.h>
26 : : #include <arpa/nameser.h>
27 : : #ifdef NEED_NAMESER_COMPAT_H
28 : : #include <arpa/nameser_compat.h>
29 : : #endif
30 : :
31 : : #include <errno.h>
32 : : #ifdef HAVE_MEMORY_H
33 : : #include <memory.h>
34 : : #endif
35 : : #include <netdb.h>
36 : : #include <resolv.h>
37 : : #include <stdio.h>
38 : : #include <stdlib.h>
39 : : #include <string.h>
40 : : #include <unistd.h>
41 : :
42 : : #ifdef notdef
43 : : #include "gnuc.h"
44 : : #ifdef HAVE_OS_PROTO_H
45 : : #include "os-proto.h"
46 : : #endif
47 : : #endif
48 : :
49 : : #include "nb_dns.h"
50 : :
51 : : #if PACKETSZ > 1024
52 : : #define MAXPACKET PACKETSZ
53 : : #else
54 : : #define MAXPACKET 1024
55 : : #endif
56 : :
57 : : #ifdef DO_SOCK_DECL
58 : : extern int socket(int, int, int);
59 : : extern int connect(int, const struct sockaddr *, int);
60 : : extern int send(int, const void *, int, int);
61 : : extern int recvfrom(int, void *, int, int, struct sockaddr *, int *);
62 : : #endif
63 : :
64 : : /* Private data */
65 : : struct nb_dns_entry {
66 : : struct nb_dns_entry *next;
67 : : char name[NS_MAXDNAME + 1];
68 : : int qtype; /* query type */
69 : : int atype; /* address family */
70 : : int asize; /* address size */
71 : : u_short id;
72 : : void *cookie;
73 : : };
74 : :
75 : : #ifndef MAXALIASES
76 : : #define MAXALIASES 35
77 : : #endif
78 : : #ifndef MAXADDRS
79 : : #define MAXADDRS 35
80 : : #endif
81 : :
82 : : struct nb_dns_hostent {
83 : : struct hostent hostent;
84 : : int numaliases;
85 : : int numaddrs;
86 : : char *host_aliases[MAXALIASES + 1];
87 : : char *h_addr_ptrs[MAXADDRS + 1];
88 : : char hostbuf[8 * 1024];
89 : : };
90 : :
91 : : struct nb_dns_info {
92 : : int s; /* Resolver file descriptor */
93 : : struct sockaddr_in server; /* server address to bind to */
94 : : struct nb_dns_entry *list; /* outstanding requests */
95 : : struct nb_dns_hostent dns_hostent;
96 : : };
97 : :
98 : : /* Forwards */
99 : : static int _nb_dns_mkquery(struct nb_dns_info *, const char *, int, int,
100 : : void *, char *);
101 : : static int _nb_dns_cmpsockaddr(struct sockaddr *, struct sockaddr *, char *);
102 : :
103 : : static char *
104 : : my_strerror(int errnum)
105 : 0 : {
106 : : #ifdef HAVE_STRERROR
107 : : extern char *strerror(int);
108 : 0 : return strerror(errnum);
109 : : #else
110 : : static char errnum_buf[32];
111 : : snprintf(errnum_buf, sizeof(errnum_buf), "errno %d", errnum);
112 : : return errnum_buf;
113 : : #endif
114 : : }
115 : :
116 : : struct nb_dns_info *
117 : : nb_dns_init(char *errstr)
118 : 3 : {
119 : : register struct nb_dns_info *nd;
120 : :
121 : 3 : nd = (struct nb_dns_info *)malloc(sizeof(*nd));
122 [ - + ]: 3 : if (nd == NULL) {
123 : 0 : snprintf(errstr, NB_DNS_ERRSIZE, "nb_dns_init: malloc(): %s",
124 : : my_strerror(errno));
125 : 0 : return (NULL);
126 : : }
127 [ - + ]: 3 : memset(nd, 0, sizeof(*nd));
128 : 3 : nd->s = -1;
129 : :
130 : : /* XXX should be able to init static hostent struct some other way */
131 : 3 : (void)gethostbyname("localhost.");
132 : :
133 [ + - - + ]: 3 : if ((_res.options & RES_INIT) == 0 && res_init() == -1) {
134 : 0 : snprintf(errstr, NB_DNS_ERRSIZE, "res_init() failed");
135 : 0 : free(nd);
136 : 0 : return (NULL);
137 : : }
138 : 3 : nd->s = socket(PF_INET, SOCK_DGRAM, 0);
139 [ - + ]: 3 : if (nd->s < 0) {
140 : 0 : snprintf(errstr, NB_DNS_ERRSIZE, "socket(): %s",
141 : : my_strerror(errno));
142 : 0 : free(nd);
143 : 0 : return (NULL);
144 : : }
145 : :
146 : : /* XXX should use resolver config */
147 : 3 : nd->server = _res.nsaddr_list[0];
148 : :
149 [ - + ]: 3 : if (connect(nd->s, (struct sockaddr *)&nd->server,
150 : : sizeof(struct sockaddr)) < 0) {
151 : 0 : snprintf(errstr, NB_DNS_ERRSIZE, "connect(%s): %s",
152 : : inet_ntoa(nd->server.sin_addr), my_strerror(errno));
153 : 0 : close(nd->s);
154 : 0 : free(nd);
155 : 0 : return (NULL);
156 : : }
157 : :
158 : 3 : return (nd);
159 : : }
160 : :
161 : : void
162 : : nb_dns_finish(struct nb_dns_info *nd)
163 : 2 : {
164 : : register struct nb_dns_entry *ne, *ne2;
165 : :
166 : 2 : ne = nd->list;
167 [ - + ]: 2 : while (ne != NULL) {
168 : 0 : ne2 = ne;
169 : 0 : ne = ne->next;
170 : 0 : free(ne2);
171 : : }
172 : 2 : close(nd->s);
173 : 2 : free(nd);
174 : 2 : }
175 : :
176 : : int
177 : : nb_dns_fd(struct nb_dns_info *nd)
178 : 912 : {
179 : :
180 : 912 : return (nd->s);
181 : : }
182 : :
183 : : static int
184 : : _nb_dns_cmpsockaddr(register struct sockaddr *sa1,
185 : : register struct sockaddr *sa2, register char *errstr)
186 : 57 : {
187 : : register struct sockaddr_in *sin1, *sin2;
188 : : #ifdef AF_INET6
189 : : register struct sockaddr_in6 *sin6a, *sin6b;
190 : : #endif
191 : : static const char serr[] = "answer from wrong nameserver (%d)";
192 : :
193 : : if (sa1->sa_family != sa1->sa_family) {
194 : : snprintf(errstr, NB_DNS_ERRSIZE, serr, 1);
195 : : return (-1);
196 : : }
197 [ + - - ]: 57 : switch (sa1->sa_family) {
198 : :
199 : : case AF_INET:
200 : 57 : sin1 = (struct sockaddr_in *)sa1;
201 : 57 : sin2 = (struct sockaddr_in *)sa2;
202 [ - + ]: 57 : if (sin1->sin_port != sin2->sin_port) {
203 : 0 : snprintf(errstr, NB_DNS_ERRSIZE, serr, 2);
204 : 0 : return (-1);
205 : : }
206 [ - + ]: 57 : if (sin1->sin_addr.s_addr != sin2->sin_addr.s_addr) {
207 : 0 : snprintf(errstr, NB_DNS_ERRSIZE, serr, 3);
208 : 0 : return (-1);
209 : : }
210 : 57 : break;
211 : :
212 : : #ifdef AF_INET6
213 : : case AF_INET6:
214 : 0 : sin6a = (struct sockaddr_in6 *)sa1;
215 : 0 : sin6b = (struct sockaddr_in6 *)sa2;
216 [ # # ]: 0 : if (sin6a->sin6_port != sin6b->sin6_port) {
217 : 0 : snprintf(errstr, NB_DNS_ERRSIZE, serr, 2);
218 : 0 : return (-1);
219 : : }
220 [ # # ]: 0 : if (memcmp(&sin6a->sin6_addr, &sin6b->sin6_addr,
221 : : sizeof(sin6a->sin6_addr)) != 0) {
222 : 0 : snprintf(errstr, NB_DNS_ERRSIZE, serr, 3);
223 : 0 : return (-1);
224 : : }
225 : 0 : break;
226 : : #endif
227 : :
228 : : default:
229 : 0 : snprintf(errstr, NB_DNS_ERRSIZE, serr, 4);
230 : 0 : return (-1);
231 : : }
232 : 57 : return (0);
233 : : }
234 : :
235 : : static int
236 : : _nb_dns_mkquery(register struct nb_dns_info *nd, register const char *name,
237 : : register int atype, register int qtype, register void * cookie,
238 : : register char *errstr)
239 : 57 : {
240 : : register struct nb_dns_entry *ne;
241 : : register HEADER *hp;
242 : : register int n;
243 : : u_long msg[MAXPACKET / sizeof(u_long)];
244 : :
245 : : /* Allocate an entry */
246 : 57 : ne = (struct nb_dns_entry *)malloc(sizeof(*ne));
247 [ - + ]: 57 : if (ne == NULL) {
248 : 0 : snprintf(errstr, NB_DNS_ERRSIZE, "malloc(): %s",
249 : : my_strerror(errno));
250 : 0 : return (-1);
251 : : }
252 [ - + ]: 57 : memset(ne, 0, sizeof(*ne));
253 [ - + ]: 57 : strncpy(ne->name, name, sizeof(ne->name));
254 : 57 : ne->name[sizeof(ne->name) - 1] = '\0';
255 : 57 : ne->qtype = qtype;
256 : 57 : ne->atype = atype;
257 [ + - - ]: 57 : switch (atype) {
258 : :
259 : : case AF_INET:
260 : 57 : ne->asize = NS_INADDRSZ;
261 : 57 : break;
262 : :
263 : : #ifdef AF_INET6
264 : : case AF_INET6:
265 : 0 : ne->asize = NS_IN6ADDRSZ;
266 : 0 : break;
267 : : #endif
268 : :
269 : : default:
270 : 0 : snprintf(errstr, NB_DNS_ERRSIZE,
271 : : "_nb_dns_mkquery: bad family %d", atype);
272 : 0 : return (-1);
273 : : }
274 : :
275 : : /* Build the request */
276 : 57 : n = res_mkquery(
277 : : ns_o_query, /* op code (query) */
278 : : name, /* domain name */
279 : : ns_c_in, /* query class (internet) */
280 : : qtype, /* query type */
281 : : NULL, /* data */
282 : : 0, /* length of data */
283 : : NULL, /* new rr */
284 : : (u_char *)msg, /* buffer */
285 : : sizeof(msg)); /* size of buffer */
286 [ - + ]: 57 : if (n < 0) {
287 : 0 : snprintf(errstr, NB_DNS_ERRSIZE, "res_mkquery() failed");
288 : 0 : free(ne);
289 : 0 : return (-1);
290 : : }
291 : :
292 : 57 : hp = (HEADER *)msg;
293 : 57 : ne->id = htons(hp->id);
294 : :
295 [ - + ]: 57 : if (send(nd->s, (char *)msg, n, 0) != n) {
296 : 0 : snprintf(errstr, NB_DNS_ERRSIZE, "send(): %s",
297 : : my_strerror(errno));
298 : 0 : free(ne);
299 : 0 : return (-1);
300 : : }
301 : :
302 : 57 : ne->next = nd->list;
303 : 57 : ne->cookie = cookie;
304 : 57 : nd->list = ne;
305 : :
306 : 57 : return(0);
307 : : }
308 : :
309 : : int
310 : : nb_dns_host_request(register struct nb_dns_info *nd, register const char *name,
311 : : register void *cookie, register char *errstr)
312 : 57 : {
313 : :
314 : 57 : return (nb_dns_host_request2(nd, name, AF_INET, cookie, errstr));
315 : : }
316 : :
317 : : int
318 : : nb_dns_host_request2(register struct nb_dns_info *nd, register const char *name,
319 : : register int af, register void *cookie, register char *errstr)
320 : 57 : {
321 : : register int qtype;
322 : :
323 [ + - - ]: 57 : switch (af) {
324 : :
325 : : case AF_INET:
326 : 57 : qtype = T_A;
327 : 57 : break;
328 : :
329 : : #ifdef AF_INET6
330 : : case AF_INET6:
331 : 0 : qtype = T_AAAA;
332 : 0 : break;
333 : : #endif
334 : :
335 : : default:
336 : 0 : snprintf(errstr, NB_DNS_ERRSIZE,
337 : : "nb_dns_host_request2(): uknown address family %d", af);
338 : 0 : return (-1);
339 : : }
340 : 57 : return (_nb_dns_mkquery(nd, name, af, qtype, cookie, errstr));
341 : : }
342 : :
343 : : int
344 : : nb_dns_addr_request(register struct nb_dns_info *nd, nb_uint32_t addr,
345 : : register void *cookie, register char *errstr)
346 : 0 : {
347 : :
348 : 0 : return (nb_dns_addr_request2(nd, (char *)&addr, AF_INET,
349 : : cookie, errstr));
350 : : }
351 : :
352 : : int
353 : : nb_dns_addr_request2(register struct nb_dns_info *nd, char *addrp,
354 : : register int af, register void *cookie, register char *errstr)
355 : 0 : {
356 : : #ifdef AF_INET6
357 : : register char *cp;
358 : : register int n, i;
359 : : register size_t size;
360 : : #endif
361 : : register u_char *uaddr;
362 : : char name[NS_MAXDNAME + 1];
363 : :
364 [ # # # ]: 0 : switch (af) {
365 : :
366 : : case AF_INET:
367 : 0 : uaddr = (u_char *)addrp;
368 : 0 : snprintf(name, sizeof(name), "%u.%u.%u.%u.in-addr.arpa",
369 : : (uaddr[3] & 0xff),
370 : : (uaddr[2] & 0xff),
371 : : (uaddr[1] & 0xff),
372 : : (uaddr[0] & 0xff));
373 : 0 : break;
374 : :
375 : : #ifdef AF_INET6
376 : : case AF_INET6:
377 : 0 : uaddr = (u_char *)addrp;
378 : 0 : cp = name;
379 : 0 : size = sizeof(name);
380 [ # # ]: 0 : for (n = NS_IN6ADDRSZ - 1; n >= 0; --n) {
381 : 0 : snprintf(cp, size, "%x.%x.",
382 : : (uaddr[n] & 0xf),
383 : : (uaddr[n] >> 4) & 0xf);
384 : 0 : i = strlen(cp);
385 : 0 : size -= i;
386 : 0 : cp += i;
387 : : }
388 : 0 : snprintf(cp, size, "ip6.int");
389 : 0 : break;
390 : : #endif
391 : :
392 : : default:
393 : 0 : snprintf(errstr, NB_DNS_ERRSIZE,
394 : : "nb_dns_addr_request2(): uknown address family %d", af);
395 : 0 : return (-1);
396 : : }
397 : :
398 : 0 : return (_nb_dns_mkquery(nd, name, af, T_PTR, cookie, errstr));
399 : : }
400 : :
401 : : int
402 : : nb_dns_abort_request(struct nb_dns_info *nd, void *cookie)
403 : 0 : {
404 : : register struct nb_dns_entry *ne, *lastne;
405 : :
406 : : /* Try to find this request on the outstanding request list */
407 : 0 : lastne = NULL;
408 [ # # ]: 0 : for (ne = nd->list; ne != NULL; ne = ne->next) {
409 [ # # ]: 0 : if (ne->cookie == cookie)
410 : 0 : break;
411 : 0 : lastne = ne;
412 : : }
413 : :
414 : : /* Not a currently pending request */
415 [ # # ]: 0 : if (ne == NULL)
416 : 0 : return (-1);
417 : :
418 : : /* Unlink this entry */
419 [ # # ]: 0 : if (lastne == NULL)
420 : 0 : nd->list = ne->next;
421 : : else
422 : 0 : lastne->next = ne->next;
423 : 0 : ne->next = NULL;
424 : :
425 : 0 : return (0);
426 : : }
427 : :
428 : : /* Returns 1 with an answer, 0 when reply was old, -1 on fatal errors */
429 : : int
430 : : nb_dns_activity(struct nb_dns_info *nd, struct nb_dns_result *nr, char *errstr)
431 : 57 : {
432 : : register int msglen, qtype, atype, n, i;
433 : : register struct nb_dns_entry *ne, *lastne;
434 : : socklen_t fromlen;
435 : : struct sockaddr from;
436 : : u_long msg[MAXPACKET / sizeof(u_long)];
437 : : register char *bp, *ep;
438 : : register char **ap, **hap;
439 : : register u_int16_t id;
440 : : register const u_char *rdata;
441 : : register struct hostent *he;
442 : : register size_t rdlen;
443 : : ns_msg handle;
444 : : ns_rr rr;
445 : :
446 : : /* This comes from the second half of do_query() */
447 : 57 : fromlen = sizeof(from);
448 : 57 : msglen = recvfrom(nd->s, (char *)msg, sizeof(msg), 0, &from, &fromlen);
449 [ - + ]: 57 : if (msglen <= 0) {
450 : 0 : snprintf(errstr, NB_DNS_ERRSIZE, "recvfrom(): %s",
451 : : my_strerror(errno));
452 : 0 : return (-1);
453 : : }
454 [ - + ]: 57 : if (msglen < HFIXEDSZ) {
455 : 0 : snprintf(errstr, NB_DNS_ERRSIZE, "recvfrom(): undersized: %d",
456 : : msglen);
457 : 0 : return (-1);
458 : : }
459 [ - + ]: 57 : if (ns_initparse((u_char *)msg, msglen, &handle) < 0) {
460 : 0 : snprintf(errstr, NB_DNS_ERRSIZE, "ns_initparse(): %s",
461 : : my_strerror(errno));
462 : 0 : nr->host_errno = NO_RECOVERY;
463 : 0 : return (-1);
464 : : }
465 : :
466 : : /* RES_INSECURE1 style check */
467 [ - + ]: 57 : if (_nb_dns_cmpsockaddr((struct sockaddr *)&nd->server, &from,
468 : : errstr) < 0) {
469 : 0 : nr->host_errno = NO_RECOVERY;
470 : 0 : return (-1);
471 : : }
472 : :
473 : : /* Search for this request */
474 : 57 : lastne = NULL;
475 : 57 : id = ns_msg_id(handle);
476 [ + - ]: 57 : for (ne = nd->list; ne != NULL; ne = ne->next) {
477 [ + - ]: 57 : if (ne->id == id)
478 : 57 : break;
479 : 0 : lastne = ne;
480 : : }
481 : :
482 : : /* Not an answer to a question we care about anymore */
483 [ - + ]: 57 : if (ne == NULL)
484 : 0 : return (0);
485 : :
486 : : /* Unlink this entry */
487 [ + - ]: 57 : if (lastne == NULL)
488 : 57 : nd->list = ne->next;
489 : : else
490 : 0 : lastne->next = ne->next;
491 : 57 : ne->next = NULL;
492 : :
493 : : /* RES_INSECURE2 style check */
494 : : /* XXX not implemented */
495 : :
496 : : /* Initialize result struct */
497 [ - + ]: 57 : memset(nr, 0, sizeof(*nr));
498 : 57 : nr->cookie = ne->cookie;
499 : 57 : qtype = ne->qtype;
500 : :
501 : : /* Deal with various errors */
502 [ - - + - ]: 57 : switch (ns_msg_getflag(handle, ns_f_rcode)) {
503 : :
504 : : case ns_r_nxdomain:
505 : 0 : nr->host_errno = HOST_NOT_FOUND;
506 : 0 : free(ne);
507 : 0 : return (1);
508 : :
509 : : case ns_r_servfail:
510 : 0 : nr->host_errno = TRY_AGAIN;
511 : 0 : free(ne);
512 : 0 : return (1);
513 : :
514 : : case ns_r_noerror:
515 : : break;
516 : :
517 : : case ns_r_formerr:
518 : : case ns_r_notimpl:
519 : : case ns_r_refused:
520 : : default:
521 : 0 : nr->host_errno = NO_RECOVERY;
522 : 0 : free(ne);
523 : 0 : return (1);
524 : : }
525 : :
526 : : /* Loop through records in packet */
527 : 57 : memset(&rr, 0, sizeof(rr));
528 [ - + ]: 57 : memset(&nd->dns_hostent, 0, sizeof(nd->dns_hostent));
529 : 57 : he = &nd->dns_hostent.hostent;
530 : : /* XXX no support for aliases */
531 : 57 : he->h_aliases = nd->dns_hostent.host_aliases;
532 : 57 : he->h_addr_list = nd->dns_hostent.h_addr_ptrs;
533 : 57 : he->h_addrtype = ne->atype;
534 : 57 : he->h_length = ne->asize;
535 : 57 : free(ne);
536 : :
537 : 57 : bp = nd->dns_hostent.hostbuf;
538 : 57 : ep = bp + sizeof(nd->dns_hostent.hostbuf);
539 : 57 : hap = he->h_addr_list;
540 : 57 : ap = he->h_aliases;
541 : :
542 [ + + ]: 116 : for (i = 0; i < ns_msg_count(handle, ns_s_an); i++) {
543 : : /* Parse next record */
544 [ - + ]: 59 : if (ns_parserr(&handle, ns_s_an, i, &rr) < 0) {
545 [ # # ]: 0 : if (errno != ENODEV) {
546 : 0 : nr->host_errno = NO_RECOVERY;
547 : 0 : return (1);
548 : : }
549 : : /* All done */
550 : 0 : break;
551 : : }
552 : :
553 : : /* Ignore records that don't answer our query (e.g. CNAMEs) */
554 : 59 : atype = ns_rr_type(rr);
555 [ + + ]: 59 : if (atype != qtype)
556 : 2 : continue;
557 : :
558 : 57 : rdata = ns_rr_rdata(rr);
559 : 57 : rdlen = ns_rr_rdlen(rr);
560 [ + - - ]: 57 : switch (atype) {
561 : :
562 : : case T_A:
563 : : case T_AAAA:
564 [ - + ]: 57 : if (rdlen != (unsigned int) he->h_length) {
565 : 0 : snprintf(errstr, NB_DNS_ERRSIZE,
566 : : "nb_dns_activity(): bad rdlen %d",
567 : : (int) rdlen);
568 : 0 : nr->host_errno = NO_RECOVERY;
569 : 0 : return (-1);
570 : : }
571 : :
572 [ - + ]: 57 : if (bp + rdlen >= ep) {
573 : 0 : snprintf(errstr, NB_DNS_ERRSIZE,
574 : : "nb_dns_activity(): overflow 1");
575 : 0 : nr->host_errno = NO_RECOVERY;
576 : 0 : return (-1);
577 : : }
578 [ - + ]: 57 : if (nd->dns_hostent.numaddrs + 1 >= MAXADDRS) {
579 : 0 : snprintf(errstr, NB_DNS_ERRSIZE,
580 : : "nb_dns_activity(): overflow 2");
581 : 0 : nr->host_errno = NO_RECOVERY;
582 : 0 : return (-1);
583 : : }
584 [ - + ]: 57 : memcpy(bp, rdata, rdlen);
585 : 57 : *hap++ = bp;
586 : 57 : bp += rdlen;
587 : 57 : ++nd->dns_hostent.numaddrs;
588 : :
589 : : /* Keep looking for more A records */
590 : 57 : break;
591 : :
592 : : case T_PTR:
593 : 0 : n = dn_expand((const u_char *)msg,
594 : : (const u_char *)msg + msglen, rdata, bp, ep - bp);
595 [ # # ]: 0 : if (n < 0) {
596 : : /* XXX return -1 here ??? */
597 : 0 : nr->host_errno = NO_RECOVERY;
598 : 0 : return (1);
599 : : }
600 : 0 : he->h_name = bp;
601 : : /* XXX check for overflow */
602 : 0 : bp += n; /* returned len includes EOS */
603 : :
604 : : /* "Find first satisfactory answer" */
605 : 0 : nr->hostent = he;
606 : 0 : return (1);
607 : : }
608 : : }
609 : :
610 : 57 : nr->hostent = he;
611 : 57 : return (1);
612 : : }
|