1 /* $OpenBSD: parse.y,v 1.78 2021/10/15 15:01:28 naddy Exp $ */
2
3 /*
4 * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
5 * Copyright (c) 2001 Markus Friedl. All rights reserved.
6 * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
7 * Copyright (c) 2001 Theo de Raadt. All rights reserved.
8 *
9 * Permission to use, copy, modify, and distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
12 *
13 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20 */
21
22 %{
23 #include <sys/types.h>
24 #include <sys/socket.h>
25 #include <netinet/in.h>
26 #include <arpa/inet.h>
27
28 #include <ctype.h>
29 #include <errno.h>
30 #include <limits.h>
31 #include <stdarg.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <syslog.h>
36
37 #include "ntpd.h"
38
39 TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
40 static struct file {
41 TAILQ_ENTRY(file) entry;
42 FILE *stream;
43 char *name;
44 int lineno;
45 int errors;
46 } *file, *topfile;
47 struct file *pushfile(const char *);
48 int popfile(void);
49 int yyparse(void);
50 int yylex(void);
51 int yyerror(const char *, ...)
52 __attribute__((__format__ (printf, 1, 2)))
53 __attribute__((__nonnull__ (1)));
54 int kw_cmp(const void *, const void *);
55 int lookup(char *);
56 int lgetc(int);
57 int lungetc(int);
58 int findeol(void);
59
60 struct sockaddr_in query_addr4;
61 struct sockaddr_in6 query_addr6;
62 int poolseqnum;
63
64 struct opts {
65 int weight;
66 int correction;
67 int stratum;
68 int rtable;
69 int trusted;
70 char *refstr;
71 } opts;
72 void opts_default(void);
73
74 typedef struct {
75 union {
76 int64_t number;
77 char *string;
78 struct ntp_addr_wrap *addr;
79 struct opts opts;
80 } v;
81 int lineno;
82 } YYSTYPE;
83
84 %}
85
86 %token LISTEN ON CONSTRAINT CONSTRAINTS FROM QUERY TRUSTED
87 %token SERVER SERVERS SENSOR CORRECTION RTABLE REFID STRATUM WEIGHT
88 %token ERROR
89 %token <v.string> STRING
90 %token <v.number> NUMBER
91 %type <v.addr> address url urllist
92 %type <v.opts> listen_opts listen_opts_l listen_opt
93 %type <v.opts> server_opts server_opts_l server_opt
94 %type <v.opts> sensor_opts sensor_opts_l sensor_opt
95 %type <v.opts> correction
96 %type <v.opts> rtable
97 %type <v.opts> refid
98 %type <v.opts> stratum
99 %type <v.opts> weight
100 %type <v.opts> trusted
101 %%
102
103 grammar : /* empty */
104 | grammar '\n'
105 | grammar main '\n'
106 | grammar error '\n' { file->errors++; }
107 ;
108
109 main : LISTEN ON address listen_opts {
110 struct listen_addr *la;
111 struct ntp_addr *h, *next;
112
113 if ((h = $3->a) == NULL &&
114 (host_dns($3->name, 0, &h) == -1 || !h)) {
115 yyerror("could not resolve \"%s\"", $3->name);
116 free($3->name);
117 free($3);
118 YYERROR;
119 }
120
121 for (; h != NULL; h = next) {
122 next = h->next;
123 la = calloc(1, sizeof(struct listen_addr));
124 if (la == NULL)
125 fatal("listen on calloc");
126 la->fd = -1;
127 la->rtable = $4.rtable;
128 memcpy(&la->sa, &h->ss,
129 sizeof(struct sockaddr_storage));
130 TAILQ_INSERT_TAIL(&conf->listen_addrs, la,
131 entry);
132 free(h);
133 }
134 free($3->name);
135 free($3);
136 }
137 | QUERY FROM STRING {
138 struct sockaddr_in sin4;
139 struct sockaddr_in6 sin6;
140
141 memset(&sin4, 0, sizeof(sin4));
142 sin4.sin_family = AF_INET;
143 sin4.sin_len = sizeof(struct sockaddr_in);
144 memset(&sin6, 0, sizeof(sin6));
145 sin6.sin6_family = AF_INET6;
146 sin6.sin6_len = sizeof(struct sockaddr_in6);
147
148 if (inet_pton(AF_INET, $3, &sin4.sin_addr) == 1)
149 memcpy(&query_addr4, &sin4, sin4.sin_len);
150 else if (inet_pton(AF_INET6, $3, &sin6.sin6_addr) == 1)
151 memcpy(&query_addr6, &sin6, sin6.sin6_len);
152 else {
153 yyerror("invalid IPv4 or IPv6 address: %s\n",
154 $3);
155 free($3);
156 YYERROR;
157 }
158
159 free($3);
160 }
161 | SERVERS address server_opts {
162 struct ntp_peer *p;
163 struct ntp_addr *h, *next;
164
165 h = $2->a;
166 do {
167 if (h != NULL) {
168 next = h->next;
169 if (h->ss.ss_family != AF_INET &&
170 h->ss.ss_family != AF_INET6) {
171 yyerror("IPv4 or IPv6 address "
172 "or hostname expected");
173 free(h);
174 free($2->name);
175 free($2);
176 YYERROR;
177 }
178 h->next = NULL;
179 } else
180 next = NULL;
181
182 p = new_peer();
183 p->weight = $3.weight;
184 p->trusted = $3.trusted;
185 conf->trusted_peers = conf->trusted_peers ||
186 $3.trusted;
187 p->query_addr4 = query_addr4;
188 p->query_addr6 = query_addr6;
189 p->addr = h;
190 p->addr_head.a = h;
191 p->addr_head.pool = ++poolseqnum;
192 p->addr_head.name = strdup($2->name);
193 if (p->addr_head.name == NULL)
194 fatal(NULL);
195 if (p->addr != NULL)
196 p->state = STATE_DNS_DONE;
197 TAILQ_INSERT_TAIL(&conf->ntp_peers, p, entry);
198 h = next;
199 } while (h != NULL);
200
201 free($2->name);
202 free($2);
203 }
204 | SERVER address server_opts {
205 struct ntp_peer *p;
206 struct ntp_addr *h, *next;
207
208 p = new_peer();
209 for (h = $2->a; h != NULL; h = next) {
210 next = h->next;
211 if (h->ss.ss_family != AF_INET &&
212 h->ss.ss_family != AF_INET6) {
213 yyerror("IPv4 or IPv6 address "
214 "or hostname expected");
215 free(h);
216 free(p);
217 free($2->name);
218 free($2);
219 YYERROR;
220 }
221 h->next = p->addr;
222 p->addr = h;
223 }
224
225 p->weight = $3.weight;
226 p->trusted = $3.trusted;
227 conf->trusted_peers = conf->trusted_peers ||
228 $3.trusted;
229 p->query_addr4 = query_addr4;
230 p->query_addr6 = query_addr6;
231 p->addr_head.a = p->addr;
232 p->addr_head.pool = 0;
233 p->addr_head.name = strdup($2->name);
234 if (p->addr_head.name == NULL)
235 fatal(NULL);
236 if (p->addr != NULL)
237 p->state = STATE_DNS_DONE;
238 TAILQ_INSERT_TAIL(&conf->ntp_peers, p, entry);
239 free($2->name);
240 free($2);
241 }
242 | CONSTRAINTS FROM url {
243 struct constraint *p;
244 struct ntp_addr *h, *next;
245
246 h = $3->a;
247 do {
248 if (h != NULL) {
249 next = h->next;
250 if (h->ss.ss_family != AF_INET &&
251 h->ss.ss_family != AF_INET6) {
252 yyerror("IPv4 or IPv6 address "
253 "or hostname expected");
254 free(h);
255 free($3->name);
256 free($3->path);
257 free($3);
258 YYERROR;
259 }
260 h->next = NULL;
261 } else
262 next = NULL;
263
264 p = new_constraint();
265 p->addr = h;
266 p->addr_head.a = h;
267 p->addr_head.pool = ++poolseqnum;
268 p->addr_head.name = strdup($3->name);
269 p->addr_head.path = strdup($3->path);
270 if (p->addr_head.name == NULL ||
271 p->addr_head.path == NULL)
272 fatal(NULL);
273 if (p->addr != NULL)
274 p->state = STATE_DNS_DONE;
275 constraint_add(p);
276 h = next;
277 } while (h != NULL);
278
279 free($3->name);
280 free($3);
281 }
282 | CONSTRAINT FROM urllist {
283 struct constraint *p;
284 struct ntp_addr *h, *next;
285
286 p = new_constraint();
287 for (h = $3->a; h != NULL; h = next) {
288 next = h->next;
289 if (h->ss.ss_family != AF_INET &&
290 h->ss.ss_family != AF_INET6) {
291 yyerror("IPv4 or IPv6 address "
292 "or hostname expected");
293 free(h);
294 free(p);
295 free($3->name);
296 free($3->path);
297 free($3);
298 YYERROR;
299 }
300 h->next = p->addr;
301 p->addr = h;
302 }
303
304 p->addr_head.a = p->addr;
305 p->addr_head.pool = 0;
306 p->addr_head.name = strdup($3->name);
307 p->addr_head.path = strdup($3->path);
308 if (p->addr_head.name == NULL ||
309 p->addr_head.path == NULL)
310 fatal(NULL);
311 if (p->addr != NULL)
312 p->state = STATE_DNS_DONE;
313 constraint_add(p);
314 free($3->name);
315 free($3);
316 }
317 | SENSOR STRING sensor_opts {
318 struct ntp_conf_sensor *s;
319
320 s = new_sensor($2);
321 s->weight = $3.weight;
322 s->correction = $3.correction;
323 s->refstr = $3.refstr;
324 s->stratum = $3.stratum;
325 s->trusted = $3.trusted;
326 conf->trusted_sensors = conf->trusted_sensors ||
327 $3.trusted;
328 free($2);
329 TAILQ_INSERT_TAIL(&conf->ntp_conf_sensors, s, entry);
330 }
331 ;
332
333 address : STRING {
334 if (($$ = calloc(1, sizeof(struct ntp_addr_wrap))) ==
335 NULL)
336 fatal(NULL);
337 host($1, &$$->a);
338 $$->name = $1;
339 }
340 ;
341
342 urllist : urllist address {
343 struct ntp_addr *p, *q = NULL;
344 struct in_addr ina;
345 struct in6_addr in6a;
346
347 if (inet_pton(AF_INET, $2->name, &ina) != 1 &&
348 inet_pton(AF_INET6, $2->name, &in6a) != 1) {
349 yyerror("url can only be followed by IP "
350 "addresses");
351 free($2->name);
352 free($2);
353 YYERROR;
354 }
355 p = $2->a;
356 while (p != NULL) {
357 q = p;
358 p = p->next;
359 }
360 if (q != NULL) {
361 q->next = $1->a;
362 $1->a = $2->a;
363 free($2);
364 }
365 $$ = $1;
366 }
367 | url {
368 $$ = $1;
369 }
370 ;
371
372 url : STRING {
373 char *hname, *path;
374
375 if (($$ = calloc(1, sizeof(struct ntp_addr_wrap))) ==
376 NULL)
377 fatal("calloc");
378
379 if (strncmp("https://", $1,
380 strlen("https://")) != 0) {
381 host($1, &$$->a);
382 $$->name = $1;
383 } else {
384 hname = $1 + strlen("https://");
385
386 path = hname + strcspn(hname, "/\\");
387 if (*path != '\0') {
388 if (($$->path = strdup(path)) == NULL)
389 fatal("strdup");
390 *path = '\0';
391 }
392 host(hname, &$$->a);
393 if (($$->name = strdup(hname)) == NULL)
394 fatal("strdup");
395 }
396 if ($$->path == NULL &&
397 ($$->path = strdup("/")) == NULL)
398 fatal("strdup");
399 }
400 ;
401
402 listen_opts : { opts_default(); }
403 listen_opts_l
404 { $$ = opts; }
405 | { opts_default(); $$ = opts; }
406 ;
407 listen_opts_l : listen_opts_l listen_opt
408 | listen_opt
409 ;
410 listen_opt : rtable
411 ;
412
413 server_opts : { opts_default(); }
414 server_opts_l
415 { $$ = opts; }
416 | { opts_default(); $$ = opts; }
417 ;
418 server_opts_l : server_opts_l server_opt
419 | server_opt
420 ;
421 server_opt : weight
422 | trusted
423 ;
424
425 sensor_opts : { opts_default(); }
426 sensor_opts_l
427 { $$ = opts; }
428 | { opts_default(); $$ = opts; }
429 ;
430 sensor_opts_l : sensor_opts_l sensor_opt
431 | sensor_opt
432 ;
433 sensor_opt : correction
434 | refid
435 | stratum
436 | weight
437 | trusted
438 ;
439
440 correction : CORRECTION NUMBER {
441 if ($2 < -127000000 || $2 > 127000000) {
442 yyerror("correction must be between "
443 "-127000000 and 127000000 microseconds");
444 YYERROR;
445 }
446 opts.correction = $2;
447 }
448 ;
449
450 refid : REFID STRING {
451 size_t l = strlen($2);
452
453 if (l < 1 || l > 4) {
454 yyerror("refid must be 1 to 4 characters");
455 free($2);
456 YYERROR;
457 }
458 opts.refstr = $2;
459 }
460 ;
461
462 stratum : STRATUM NUMBER {
463 if ($2 < 1 || $2 > 15) {
464 yyerror("stratum must be between "
465 "1 and 15");
466 YYERROR;
467 }
468 opts.stratum = $2;
469 }
470 ;
471
472 weight : WEIGHT NUMBER {
473 if ($2 < 1 || $2 > 10) {
474 yyerror("weight must be between 1 and 10");
475 YYERROR;
476 }
477 opts.weight = $2;
478 }
479 rtable : RTABLE NUMBER {
480 if ($2 < 0 || $2 > RT_TABLEID_MAX) {
481 yyerror("rtable must be between 1"
482 " and RT_TABLEID_MAX");
483 YYERROR;
484 }
485 opts.rtable = $2;
486 }
487 ;
488
489 trusted : TRUSTED {
490 opts.trusted = 1;
491 }
492
493 %%
494
495 void
496 opts_default(void)
497 {
498 memset(&opts, 0, sizeof opts);
499 opts.weight = 1;
500 opts.stratum = 1;
501 }
502
503 struct keywords {
504 const char *k_name;
505 int k_val;
506 };
507
508 int
yyerror(const char * fmt,...)509 yyerror(const char *fmt, ...)
510 {
511 va_list ap;
512 char *msg;
513
514 file->errors++;
515 va_start(ap, fmt);
516 if (vasprintf(&msg, fmt, ap) == -1)
517 fatalx("yyerror vasprintf");
518 va_end(ap);
519 log_warnx("%s:%d: %s", file->name, yylval.lineno, msg);
520 free(msg);
521 return (0);
522 }
523
524 int
kw_cmp(const void * k,const void * e)525 kw_cmp(const void *k, const void *e)
526 {
527 return (strcmp(k, ((const struct keywords *)e)->k_name));
528 }
529
530 int
lookup(char * s)531 lookup(char *s)
532 {
533 /* this has to be sorted always */
534 static const struct keywords keywords[] = {
535 { "constraint", CONSTRAINT},
536 { "constraints", CONSTRAINTS},
537 { "correction", CORRECTION},
538 { "from", FROM},
539 { "listen", LISTEN},
540 { "on", ON},
541 { "query", QUERY},
542 { "refid", REFID},
543 { "rtable", RTABLE},
544 { "sensor", SENSOR},
545 { "server", SERVER},
546 { "servers", SERVERS},
547 { "stratum", STRATUM},
548 { "trusted", TRUSTED},
549 { "weight", WEIGHT}
550 };
551 const struct keywords *p;
552
553 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
554 sizeof(keywords[0]), kw_cmp);
555
556 if (p)
557 return (p->k_val);
558 else
559 return (STRING);
560 }
561
562 #define MAXPUSHBACK 128
563
564 char *parsebuf;
565 int parseindex;
566 char pushback_buffer[MAXPUSHBACK];
567 int pushback_index = 0;
568
569 int
lgetc(int quotec)570 lgetc(int quotec)
571 {
572 int c, next;
573
574 if (parsebuf) {
575 /* Read character from the parsebuffer instead of input. */
576 if (parseindex >= 0) {
577 c = (unsigned char)parsebuf[parseindex++];
578 if (c != '\0')
579 return (c);
580 parsebuf = NULL;
581 } else
582 parseindex++;
583 }
584
585 if (pushback_index)
586 return ((unsigned char)pushback_buffer[--pushback_index]);
587
588 if (quotec) {
589 if ((c = getc(file->stream)) == EOF) {
590 yyerror("reached end of file while parsing "
591 "quoted string");
592 if (file == topfile || popfile() == EOF)
593 return (EOF);
594 return (quotec);
595 }
596 return (c);
597 }
598
599 while ((c = getc(file->stream)) == '\\') {
600 next = getc(file->stream);
601 if (next != '\n') {
602 c = next;
603 break;
604 }
605 yylval.lineno = file->lineno;
606 file->lineno++;
607 }
608
609 while (c == EOF) {
610 if (file == topfile || popfile() == EOF)
611 return (EOF);
612 c = getc(file->stream);
613 }
614 return (c);
615 }
616
617 int
lungetc(int c)618 lungetc(int c)
619 {
620 if (c == EOF)
621 return (EOF);
622 if (parsebuf) {
623 parseindex--;
624 if (parseindex >= 0)
625 return (c);
626 }
627 if (pushback_index + 1 >= MAXPUSHBACK)
628 return (EOF);
629 pushback_buffer[pushback_index++] = c;
630 return (c);
631 }
632
633 int
findeol(void)634 findeol(void)
635 {
636 int c;
637
638 parsebuf = NULL;
639
640 /* skip to either EOF or the first real EOL */
641 while (1) {
642 if (pushback_index)
643 c = (unsigned char)pushback_buffer[--pushback_index];
644 else
645 c = lgetc(0);
646 if (c == '\n') {
647 file->lineno++;
648 break;
649 }
650 if (c == EOF)
651 break;
652 }
653 return (ERROR);
654 }
655
656 int
yylex(void)657 yylex(void)
658 {
659 char buf[8096];
660 char *p;
661 int quotec, next, c;
662 int token;
663
664 p = buf;
665 while ((c = lgetc(0)) == ' ' || c == '\t')
666 ; /* nothing */
667
668 yylval.lineno = file->lineno;
669 if (c == '#')
670 while ((c = lgetc(0)) != '\n' && c != EOF)
671 ; /* nothing */
672
673 switch (c) {
674 case '\'':
675 case '"':
676 quotec = c;
677 while (1) {
678 if ((c = lgetc(quotec)) == EOF)
679 return (0);
680 if (c == '\n') {
681 file->lineno++;
682 continue;
683 } else if (c == '\\') {
684 if ((next = lgetc(quotec)) == EOF)
685 return (0);
686 if (next == quotec || next == ' ' ||
687 next == '\t')
688 c = next;
689 else if (next == '\n') {
690 file->lineno++;
691 continue;
692 } else
693 lungetc(next);
694 } else if (c == quotec) {
695 *p = '\0';
696 break;
697 } else if (c == '\0') {
698 yyerror("syntax error");
699 return (findeol());
700 }
701 if (p + 1 >= buf + sizeof(buf) - 1) {
702 yyerror("string too long");
703 return (findeol());
704 }
705 *p++ = c;
706 }
707 yylval.v.string = strdup(buf);
708 if (yylval.v.string == NULL)
709 fatal("yylex: strdup");
710 return (STRING);
711 }
712
713 #define allowed_to_end_number(x) \
714 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
715
716 if (c == '-' || isdigit(c)) {
717 do {
718 *p++ = c;
719 if ((size_t)(p-buf) >= sizeof(buf)) {
720 yyerror("string too long");
721 return (findeol());
722 }
723 } while ((c = lgetc(0)) != EOF && isdigit(c));
724 lungetc(c);
725 if (p == buf + 1 && buf[0] == '-')
726 goto nodigits;
727 if (c == EOF || allowed_to_end_number(c)) {
728 const char *errstr = NULL;
729
730 *p = '\0';
731 yylval.v.number = strtonum(buf, LLONG_MIN,
732 LLONG_MAX, &errstr);
733 if (errstr) {
734 yyerror("\"%s\" invalid number: %s",
735 buf, errstr);
736 return (findeol());
737 }
738 return (NUMBER);
739 } else {
740 nodigits:
741 while (p > buf + 1)
742 lungetc((unsigned char)*--p);
743 c = (unsigned char)*--p;
744 if (c == '-')
745 return (c);
746 }
747 }
748
749 #define allowed_in_string(x) \
750 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
751 x != '{' && x != '}' && x != '<' && x != '>' && \
752 x != '!' && x != '=' && x != '/' && x != '#' && \
753 x != ','))
754
755 if (isalnum(c) || c == ':' || c == '_' || c == '*') {
756 do {
757 *p++ = c;
758 if ((size_t)(p-buf) >= sizeof(buf)) {
759 yyerror("string too long");
760 return (findeol());
761 }
762 } while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
763 lungetc(c);
764 *p = '\0';
765 if ((token = lookup(buf)) == STRING)
766 if ((yylval.v.string = strdup(buf)) == NULL)
767 fatal("yylex: strdup");
768 return (token);
769 }
770 if (c == '\n') {
771 yylval.lineno = file->lineno;
772 file->lineno++;
773 }
774 if (c == EOF)
775 return (0);
776 return (c);
777 }
778
779 struct file *
pushfile(const char * name)780 pushfile(const char *name)
781 {
782 struct file *nfile;
783
784 if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
785 log_warn("%s", __func__);
786 return (NULL);
787 }
788 if ((nfile->name = strdup(name)) == NULL) {
789 log_warn("%s", __func__);
790 free(nfile);
791 return (NULL);
792 }
793 if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
794 log_warn("%s: %s", __func__, nfile->name);
795 free(nfile->name);
796 free(nfile);
797 return (NULL);
798 }
799 nfile->lineno = 1;
800 TAILQ_INSERT_TAIL(&files, nfile, entry);
801 return (nfile);
802 }
803
804 int
popfile(void)805 popfile(void)
806 {
807 struct file *prev;
808
809 if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
810 prev->errors += file->errors;
811
812 TAILQ_REMOVE(&files, file, entry);
813 fclose(file->stream);
814 free(file->name);
815 free(file);
816 file = prev;
817 return (file ? 0 : EOF);
818 }
819
820 int
parse_config(const char * filename,struct ntpd_conf * xconf)821 parse_config(const char *filename, struct ntpd_conf *xconf)
822 {
823 int errors = 0;
824
825 conf = xconf;
826 TAILQ_INIT(&conf->listen_addrs);
827 TAILQ_INIT(&conf->ntp_peers);
828 TAILQ_INIT(&conf->ntp_conf_sensors);
829 TAILQ_INIT(&conf->constraints);
830
831 if ((file = pushfile(filename)) == NULL) {
832 return (-1);
833 }
834 topfile = file;
835
836 yyparse();
837 errors = file->errors;
838 popfile();
839
840 return (errors ? -1 : 0);
841 }
842