1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
23 * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
25 */
26
27 #pragma ident "%Z%%M% %I% %E% SMI"
28
29 /*
30 * SNMP PDU and packet transport related routines
31 */
32
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <sys/types.h>
37 #include "asn1.h"
38 #include "pdu.h"
39 #include "debug.h"
40
41 /*
42 * Static declarations
43 */
44 static int snmp_add_null_vars(snmp_pdu_t *, char *, int, int);
45 static oid *snmp_oidstr_to_oid(int, char *, int, size_t *);
46 static uchar_t *snmp_build_pdu(snmp_pdu_t *, uchar_t *, size_t *);
47 static uchar_t *snmp_build_variable(uchar_t *, size_t *, oid *, size_t,
48 uchar_t, void *, size_t);
49 static uchar_t *snmp_parse_pdu(int, uchar_t *, size_t *, snmp_pdu_t *);
50 static uchar_t *snmp_parse_variable(uchar_t *, size_t *, pdu_varlist_t *);
51 static void snmp_free_null_vars(pdu_varlist_t *);
52
53 static uchar_t *snmp_def_community = (uchar_t *)SNMP_DEF_COMMUNITY;
54
55 /*
56 * Allocates and creates a PDU for the specified SNMP command. Currently
57 * only SNMP_MSG_GET, SNMP_MSG_GETNEXT and SNMP_MSG_GETBULK are supported
58 */
59 snmp_pdu_t *
snmp_create_pdu(int cmd,int max_reps,char * oidstrs,int n_oids,int row)60 snmp_create_pdu(int cmd, int max_reps, char *oidstrs, int n_oids, int row)
61 {
62 snmp_pdu_t *pdu;
63
64 if ((cmd != SNMP_MSG_GET) && (cmd != SNMP_MSG_GETNEXT) &&
65 (cmd != SNMP_MSG_GETBULK)) {
66 return (NULL);
67 }
68
69 pdu = (snmp_pdu_t *)calloc(1, sizeof (snmp_pdu_t));
70 if (pdu == NULL)
71 return (NULL);
72
73 if (cmd == SNMP_MSG_GET || cmd == SNMP_MSG_GETNEXT) {
74 pdu->version = SNMP_VERSION_1;
75 pdu->errstat = 0;
76 pdu->errindex = 0;
77 } else if (cmd == SNMP_MSG_GETBULK) {
78 pdu->version = SNMP_VERSION_2c;
79 pdu->non_repeaters = 0;
80 pdu->max_repetitions = max_reps ?
81 max_reps : SNMP_DEF_MAX_REPETITIONS;
82 }
83
84 pdu->command = cmd;
85 pdu->reqid = snmp_get_reqid();
86 pdu->community = snmp_def_community;
87 pdu->community_len = SNMP_DEF_COMMUNITY_LEN;
88
89 if (snmp_add_null_vars(pdu, oidstrs, n_oids, row) < 0) {
90 free((void *) pdu);
91 return (NULL);
92 }
93
94 pdu->req_pkt = NULL;
95 pdu->req_pktsz = 0;
96 pdu->reply_pkt = NULL;
97 pdu->reply_pktsz = 0;
98
99 return (pdu);
100 }
101
102 /*
103 * Builds a complete ASN.1 encoded snmp message packet out of the PDU.
104 * Currently the maximum request packet is limited to SNMP_DEF_PKTBUF_SZ.
105 * Since we only send SNMP_MSG_GET, SNMP_MSG_GETNEXT and SNMP_MSG_GETBULK,
106 * as long as the number of bulk oids are not *too* many, we're safe with
107 * this limit (the typical packet size of a bulk request of 10 vars is
108 * around 250 bytes).
109 */
110 int
snmp_make_packet(snmp_pdu_t * pdu)111 snmp_make_packet(snmp_pdu_t *pdu)
112 {
113 uchar_t *buf, *p;
114 uchar_t *msg_seq_end;
115 uchar_t id;
116 size_t bufsz = SNMP_DEF_PKTBUF_SZ;
117 size_t seqlen;
118
119 if ((buf = (uchar_t *)calloc(1, SNMP_DEF_PKTBUF_SZ)) == NULL)
120 return (-1);
121
122 /*
123 * Let's start with the ASN sequence tag. Set the length
124 * to 0 initially and fill it up once the message packetizing
125 * is complete.
126 */
127 id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
128 if ((p = asn_build_sequence(buf, &bufsz, id, 0)) == NULL) {
129 free((void *) buf);
130 return (-1);
131 }
132 msg_seq_end = p;
133
134 /*
135 * Store the version
136 */
137 id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER;
138 if ((p = asn_build_int(p, &bufsz, id, pdu->version)) == NULL) {
139 free((void *) buf);
140 return (-1);
141 }
142
143 /*
144 * Store the community string
145 */
146 id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OCTET_STR;
147 p = asn_build_string(p, &bufsz, id, pdu->community, pdu->community_len);
148 if (p == NULL) {
149 free((void *) buf);
150 return (-1);
151 }
152
153 /*
154 * Build the PDU
155 */
156 if ((p = snmp_build_pdu(pdu, p, &bufsz)) == NULL) {
157 free((void *) buf);
158 return (-1);
159 }
160
161 /*
162 * Complete the message pkt by updating the message sequence length
163 */
164 seqlen = p - msg_seq_end;
165 id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
166 (void) asn_build_sequence(buf, NULL, id, seqlen);
167
168 /*
169 * Calculate packet size and return
170 */
171 pdu->req_pkt = buf;
172 pdu->req_pktsz = p - buf;
173
174 return (0);
175 }
176
177 /*
178 * Makes a PDU out of a reply packet. The reply message is parsed
179 * and if the reqid of the incoming packet does not match the reqid
180 * we're waiting for, an error is returned. The PDU is allocated
181 * inside this routine and must be freed by the caller once it is no
182 * longer needed.
183 */
184 snmp_pdu_t *
snmp_parse_reply(int reqid,uchar_t * reply_pkt,size_t reply_pktsz)185 snmp_parse_reply(int reqid, uchar_t *reply_pkt, size_t reply_pktsz)
186 {
187 snmp_pdu_t *reply_pdu;
188 uchar_t *p;
189 size_t msgsz = reply_pktsz;
190 uchar_t exp_id;
191
192 reply_pdu = (snmp_pdu_t *)calloc(1, sizeof (snmp_pdu_t));
193 if (reply_pdu == NULL)
194 return (NULL);
195
196 /*
197 * Try to parse the ASN sequence out of the beginning of the reply
198 * packet. If we don't find a sequence at the beginning, something's
199 * wrong.
200 */
201 exp_id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
202 if ((p = asn_parse_sequence(reply_pkt, &msgsz, exp_id)) == NULL) {
203 snmp_free_pdu(reply_pdu);
204 return (NULL);
205 }
206
207 /*
208 * Now try to parse the version out of the packet
209 */
210 if ((p = asn_parse_int(p, &msgsz, &reply_pdu->version)) == NULL) {
211 snmp_free_pdu(reply_pdu);
212 return (NULL);
213 }
214 if ((reply_pdu->version != SNMP_VERSION_1) &&
215 (reply_pdu->version != SNMP_VERSION_2c)) {
216 snmp_free_pdu(reply_pdu);
217 return (NULL);
218 }
219
220 /*
221 * Parse the community string (space allocated by asn_parse_string)
222 */
223 p = asn_parse_string(p, &msgsz, &reply_pdu->community,
224 &reply_pdu->community_len);
225 if (p == NULL) {
226 snmp_free_pdu(reply_pdu);
227 return (NULL);
228 }
229
230 /*
231 * Parse the PDU part of the message
232 */
233 if ((p = snmp_parse_pdu(reqid, p, &msgsz, reply_pdu)) == NULL) {
234 snmp_free_pdu(reply_pdu);
235 return (NULL);
236 }
237
238 return (reply_pdu);
239 }
240
241
242 /*
243 * Convert the OID strings into the standard PDU oid form (sequence of
244 * integer subids) and add them to the PDU's variable list. Note that
245 * this is used only for preparing the request messages (GET, GETNEXT
246 * and GETBULK), so the values of the variables are always null.
247 */
248 static int
snmp_add_null_vars(snmp_pdu_t * pdu,char * oidstrs,int n_oids,int row)249 snmp_add_null_vars(snmp_pdu_t *pdu, char *oidstrs, int n_oids, int row)
250 {
251 pdu_varlist_t *vp, *prev;
252 pdu_varlist_t *varblock_p = NULL;
253 char *p;
254 int i;
255
256 prev = NULL;
257 p = oidstrs;
258 for (i = 0; i < n_oids; i++) {
259 if ((vp = calloc(1, sizeof (pdu_varlist_t))) == NULL) {
260 snmp_free_null_vars(varblock_p);
261 return (-1);
262 } else if (i == 0) {
263 varblock_p = vp;
264 } else {
265 prev->nextvar = vp;
266 }
267
268 vp->name = snmp_oidstr_to_oid(pdu->command,
269 p, row, &vp->name_len);
270 if (vp->name == NULL) {
271 snmp_free_null_vars(varblock_p);
272 return (-1);
273 }
274 vp->val.str = NULL;
275 vp->val_len = 0;
276 vp->type = ASN_NULL;
277 vp->nextvar = NULL;
278
279 LOGVAR(TAG_NULL_VAR, vp);
280
281 prev = vp;
282 p += strlen(p) + 1;
283 }
284
285 /*
286 * append the varlist to the PDU
287 */
288 if (pdu->vars == NULL)
289 pdu->vars = varblock_p;
290 else {
291 for (vp = pdu->vars; vp->nextvar; vp = vp->nextvar)
292 ;
293 vp->nextvar = varblock_p;
294 }
295
296 return (0);
297 }
298
299 /*
300 * Some assumptions are in place here to eliminate unnecessary complexity.
301 * All OID strings passed are assumed to be in the numeric string form, have
302 * no leading/trailing '.' or spaces. Since PICL plugin is currently the
303 * only customer, this is quite reasonable.
304 */
305 static oid *
snmp_oidstr_to_oid(int cmd,char * oidstr,int row,size_t * n_subids)306 snmp_oidstr_to_oid(int cmd, char *oidstr, int row, size_t *n_subids)
307 {
308 int i, count;
309 char *p, *q;
310 char *oidstr_dup;
311 oid *objid;
312
313 if ((oidstr == NULL) || (n_subids == NULL))
314 return (NULL);
315
316 for (count = 1, p = oidstr; p; count++, p++) {
317 if ((p = strchr(p, '.')) == NULL)
318 break;
319 }
320
321 /*
322 * Add one more to count for 'row'. Need special processing
323 * for SNMP_MSG_GETNEXT and SNMP_MSG_GETBULK requests; see
324 * comment below.
325 */
326 if ((cmd == SNMP_MSG_GET) || (cmd == SNMP_MSG_GETBULK && row > 0) ||
327 (cmd == SNMP_MSG_GETNEXT && row >= 0)) {
328 count++;
329 }
330
331 if ((oidstr_dup = strdup(oidstr)) == NULL)
332 return (NULL);
333
334 objid = (oid *) calloc(count, sizeof (oid));
335 if (objid == NULL) {
336 free((void *) p);
337 return (NULL);
338 }
339
340 p = oidstr_dup;
341 for (i = 0; i < count - 1; i++) {
342 if (q = strchr(p, '.'))
343 *q = 0;
344 objid[i] = (oid) strtoul(p, NULL, 10);
345 p = q + 1;
346 }
347
348 /*
349 * For SNMP_MSG_GET, the leaf subid will simply be the row#.
350 *
351 * For SNMP_MSG_GETBULK, if the row# passed is greater than 0,
352 * we pass 'row-1' as the leaf subid, to include the item that
353 * is of interest to us. If the row# is less than or equal to 0,
354 * we will simply ignore it and pass only the prefix part of the
355 * oidstr. For this case, our count would have been 1 less than
356 * usual, and we are yet to save the last subid.
357 *
358 * For SNMP_MSG_GETNEXT, if the row# passed is less than 0,
359 * we'll simply ignore it and pass only the prefix part of the
360 * oidstr. For this case, our count would have been 1 less than
361 * usual, and we are yet to save the last subid. If the row#
362 * passed is greater than or equal to 0, we'll simply pass it
363 * verbatim, as the leaf subid.
364 */
365 switch (cmd) {
366 case SNMP_MSG_GET:
367 objid[i] = (oid) row;
368 break;
369
370 case SNMP_MSG_GETBULK:
371 if (row > 0)
372 objid[i] = (oid) (row - 1);
373 else
374 objid[i] = (oid) strtoul(p, NULL, 10);
375 break;
376
377 case SNMP_MSG_GETNEXT:
378 if (row < 0)
379 objid[i] = (oid) strtoul(p, NULL, 10);
380 else
381 objid[i] = (oid) row;
382 break;
383 }
384
385 *n_subids = count;
386
387 free((void *) oidstr_dup);
388
389 return (objid);
390 }
391
392 /*
393 * Builds the PDU part of the snmp message packet.
394 */
395 static uchar_t *
snmp_build_pdu(snmp_pdu_t * pdu,uchar_t * buf,size_t * bufsz_p)396 snmp_build_pdu(snmp_pdu_t *pdu, uchar_t *buf, size_t *bufsz_p)
397 {
398 uchar_t *p;
399 uchar_t *pdu_seq_begin, *pdu_seq_end;
400 uchar_t *varlist_seq_begin, *varlist_seq_end;
401 uchar_t id;
402 size_t seqlen;
403 pdu_varlist_t *vp;
404
405 /*
406 * Build ASN sequence for the PDU command (length will be
407 * updated later once the entire command is completely formed)
408 */
409 pdu_seq_begin = buf;
410 p = asn_build_sequence(buf, bufsz_p, (uchar_t)pdu->command, 0);
411 if (p == NULL)
412 return (NULL);
413 pdu_seq_end = p;
414
415 /*
416 * Build the request id
417 */
418 id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER;
419 if ((p = asn_build_int(p, bufsz_p, id, pdu->reqid)) == NULL)
420 return (NULL);
421
422 /*
423 * Build the non-repeaters and max-repetitions for SNMP_MSG_GETBULK
424 * (same as error status and error index for other message types)
425 */
426 id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER;
427 if ((p = asn_build_int(p, bufsz_p, id, pdu->non_repeaters)) == NULL)
428 return (NULL);
429
430 id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_INTEGER;
431 if ((p = asn_build_int(p, bufsz_p, id, pdu->max_repetitions)) == NULL)
432 return (NULL);
433
434 /*
435 * Build ASN sequence for the variables list (update length
436 * after building the varlist)
437 */
438 varlist_seq_begin = p;
439 id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
440 if ((p = asn_build_sequence(p, bufsz_p, id, 0)) == NULL)
441 return (NULL);
442 varlist_seq_end = p;
443
444 /*
445 * Build the variables list
446 */
447 for (vp = pdu->vars; vp; vp = vp->nextvar) {
448 p = snmp_build_variable(p, bufsz_p, vp->name, vp->name_len,
449 vp->type, vp->val.str, vp->val_len);
450 if (p == NULL)
451 return (NULL);
452 }
453
454 /*
455 * Now update the varlist sequence length
456 */
457 seqlen = p - varlist_seq_end;
458 id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
459 (void) asn_build_sequence(varlist_seq_begin, NULL, id, seqlen);
460
461 /*
462 * And finally, update the length for the PDU sequence
463 */
464 seqlen = p - pdu_seq_end;
465 (void) asn_build_sequence(pdu_seq_begin, NULL, (uchar_t)pdu->command,
466 seqlen);
467
468 return (p);
469 }
470
471 /*
472 * Builds an object variable into the snmp message packet. Although the
473 * code is here to build variables of basic types such as integer, object id
474 * and strings, the only type of variable we ever send via snmp request
475 * messages is the ASN_NULL type.
476 */
477 static uchar_t *
snmp_build_variable(uchar_t * buf,size_t * bufsz_p,oid * name,size_t name_len,uchar_t val_type,void * val,size_t val_len)478 snmp_build_variable(uchar_t *buf, size_t *bufsz_p, oid *name, size_t name_len,
479 uchar_t val_type, void *val, size_t val_len)
480 {
481 uchar_t *p, *varseq_end;
482 size_t seqlen;
483 uchar_t id;
484
485 /*
486 * Each variable binding is in turn defined as a 'SEQUENCE of' by
487 * the SNMP PDU format, so we'll prepare the sequence and fill up
488 * the length later. Sigh!
489 */
490 id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
491 if ((p = asn_build_sequence(buf, bufsz_p, id, 0)) == NULL)
492 return (NULL);
493 varseq_end = p;
494
495 /*
496 * Build the object id
497 */
498 id = ASN_UNIVERSAL | ASN_PRIMITIVE | ASN_OBJECT_ID;
499 if ((p = asn_build_objid(p, bufsz_p, id, name, name_len)) == NULL)
500 return (NULL);
501
502 /*
503 * Currently we only ever build ASN_NULL vars while sending requests,
504 * since we support only SNMP_MSG_GET, SNMP_MSG_GETNEXT and
505 * SNMP_MSG_GETBULK.
506 */
507 id = ASN_UNIVERSAL | ASN_PRIMITIVE | val_type;
508 switch (val_type) {
509 case ASN_INTEGER:
510 p = asn_build_int(p, bufsz_p, id, *((int *)val));
511 if (p == NULL)
512 return (NULL);
513 break;
514
515 case ASN_OBJECT_ID:
516 p = asn_build_objid(p, bufsz_p, id, val,
517 val_len / sizeof (oid));
518 if (p == NULL)
519 return (NULL);
520 break;
521
522 case ASN_OCTET_STR:
523 p = asn_build_string(p, bufsz_p, id, (uchar_t *)val, val_len);
524 if (p == NULL)
525 return (NULL);
526 break;
527
528 case ASN_NULL:
529 if ((p = asn_build_null(p, bufsz_p, id)) == NULL)
530 return (NULL);
531 break;
532
533 default:
534 return (NULL);
535 }
536
537 /*
538 * Rebuild the variable sequence length
539 */
540 seqlen = p - varseq_end;
541 id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
542 (void) asn_build_sequence(buf, NULL, id, seqlen);
543
544 return (p);
545 }
546
547 /*
548 * Parse the PDU portion of the incoming snmp message into the reply_pdu.
549 * Space for all structure members are allocated as needed and must be freed
550 * by the caller when these are no longer needed.
551 */
552 static uchar_t *
snmp_parse_pdu(int reqid,uchar_t * msg,size_t * msgsz_p,snmp_pdu_t * reply_pdu)553 snmp_parse_pdu(int reqid, uchar_t *msg, size_t *msgsz_p, snmp_pdu_t *reply_pdu)
554 {
555 uchar_t *p;
556 uchar_t id, exp_id;
557 pdu_varlist_t *newvp, *vp = NULL;
558
559 /*
560 * Parse the PDU header out of the message
561 */
562 if ((p = asn_parse_header(msg, msgsz_p, &id)) == NULL)
563 return (NULL);
564 if (id != SNMP_MSG_RESPONSE && id != SNMP_MSG_REPORT)
565 return (NULL);
566 reply_pdu->command = (int)id;
567
568 /*
569 * Parse the request id and verify that this is the response
570 * we're expecting.
571 */
572 if ((p = asn_parse_int(p, msgsz_p, &reply_pdu->reqid)) == NULL)
573 return (NULL);
574 if (reply_pdu->reqid != reqid)
575 return (NULL);
576
577 /*
578 * Parse the error-status and error-index values
579 */
580 if ((p = asn_parse_int(p, msgsz_p, &reply_pdu->errstat)) == NULL)
581 return (NULL);
582 if ((p = asn_parse_int(p, msgsz_p, &reply_pdu->errindex)) == NULL)
583 return (NULL);
584
585 /*
586 * Parse the header for the variables list sequence.
587 */
588 exp_id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
589 if ((p = asn_parse_sequence(p, msgsz_p, exp_id)) == NULL)
590 return (NULL);
591
592 while (((int)*msgsz_p) > 0) {
593 if ((newvp = calloc(1, sizeof (pdu_varlist_t))) == NULL)
594 return (NULL);
595
596 if (vp == NULL)
597 reply_pdu->vars = newvp;
598 else
599 vp->nextvar = newvp;
600
601 vp = newvp;
602 if ((p = snmp_parse_variable(p, msgsz_p, vp)) == NULL)
603 return (NULL);
604
605 LOGVAR(TAG_RESPONSE_VAR, vp);
606 }
607
608 return (p);
609 }
610
611 /*
612 * Allocate and parse the next variable into the varlist
613 */
614 static uchar_t *
snmp_parse_variable(uchar_t * msg,size_t * msgsz_p,pdu_varlist_t * vp)615 snmp_parse_variable(uchar_t *msg, size_t *msgsz_p, pdu_varlist_t *vp)
616 {
617 uchar_t *p;
618 uchar_t exp_id;
619
620 /*
621 * Parse this variable's sequence
622 */
623 exp_id = ASN_UNIVERSAL | ASN_CONSTRUCTOR | ASN_SEQUENCE;
624 if ((p = asn_parse_sequence(msg, msgsz_p, exp_id)) == NULL)
625 return (NULL);
626
627 /*
628 * Parse the variable's object identifier
629 */
630 p = asn_parse_objid(p, msgsz_p, &vp->name, &vp->name_len);
631 if (p == NULL)
632 return (NULL);
633
634 /*
635 * Parse the object's value
636 */
637 if ((p = asn_parse_objval(p, msgsz_p, vp)) == NULL)
638 return (NULL);
639
640 return (p);
641 }
642
643 void
snmp_free_pdu(snmp_pdu_t * pdu)644 snmp_free_pdu(snmp_pdu_t *pdu)
645 {
646 pdu_varlist_t *vp, *nxt;
647
648 if (pdu) {
649 if ((pdu->community) && (pdu->community != snmp_def_community))
650 free((void *) pdu->community);
651
652 for (vp = pdu->vars; vp; vp = nxt) {
653 nxt = vp->nextvar;
654
655 if (vp->name)
656 free((void *) vp->name);
657 if (vp->val.str)
658 free((void *) vp->val.str);
659 free((void *) vp);
660 }
661
662 if (pdu->req_pkt)
663 free((void *) pdu->req_pkt);
664
665 if (pdu->reply_pkt)
666 free((void *) pdu->reply_pkt);
667
668 free((void *) pdu);
669 }
670 }
671
672 static void
snmp_free_null_vars(pdu_varlist_t * varblock_p)673 snmp_free_null_vars(pdu_varlist_t *varblock_p)
674 {
675 pdu_varlist_t *vp, *nxt;
676
677 for (vp = varblock_p; vp; vp = nxt) {
678 nxt = vp->nextvar;
679
680 if (vp->name)
681 free(vp->name);
682 free(vp);
683 }
684 }
685