xref: /openbsd-src/usr.sbin/syslogd/privsep.c (revision 8500990981f885cbe5e6a4958549cacc238b5ae6)
1 /*	$OpenBSD: privsep.c,v 1.9 2003/10/26 18:21:49 avsm Exp $	*/
2 
3 /*
4  * Copyright (c) 2003 Anil Madhavapeddy <anil@recoil.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 #include <sys/ioctl.h>
19 #include <sys/param.h>
20 #include <sys/queue.h>
21 #include <sys/uio.h>
22 #include <sys/types.h>
23 #include <sys/socket.h>
24 #include <sys/stat.h>
25 #include <sys/wait.h>
26 #include <err.h>
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <netdb.h>
30 #include <paths.h>
31 #include <pwd.h>
32 #include <signal.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37 #include <util.h>
38 #include <utmp.h>
39 #include "syslogd.h"
40 
41 /*
42  * syslogd can only go forward in these states; each state should represent
43  * less privilege.   After STATE_INIT, the child is allowed to parse its
44  * config file once, and communicate the information regarding what logfiles
45  * it needs access to back to the parent.  When that is done, it sends a
46  * message to the priv parent revoking this access, moving to STATE_RUNNING.
47  * In this state, any log-files not in the access list are rejected.
48  *
49  * This allows a HUP signal to the child to reopen its log files, and
50  * the config file to be parsed if it hasn't been changed (this is still
51  * useful to force resoluton of remote syslog servers again).
52  * If the config file has been modified, then the child dies, and
53  * the priv parent restarts itself.
54  */
55 enum priv_state {
56 	STATE_INIT,		/* just started up */
57 	STATE_CONFIG,		/* parsing config file for first time */
58 	STATE_RUNNING,		/* running and accepting network traffic */
59 	STATE_QUIT,		/* shutting down */
60 };
61 
62 enum cmd_types {
63 	PRIV_OPEN_TTY,		/* open terminal or console device */
64 	PRIV_OPEN_LOG,		/* open logfile for appending */
65 	PRIV_OPEN_UTMP,		/* open utmp for reading only */
66 	PRIV_OPEN_CONFIG,	/* open config file for reading only */
67 	PRIV_CONFIG_MODIFIED,	/* check if config file has been modified */
68 	PRIV_GETHOSTBYNAME,	/* resolve hostname into numerical address */
69 	PRIV_GETHOSTBYADDR,	/* resolve numeric address into hostname */
70 	PRIV_DONE_CONFIG_PARSE	/* signal that the initial config parse is done */
71 };
72 
73 static int priv_fd = -1;
74 static pid_t child_pid = -1;
75 static char config_file[MAXPATHLEN];
76 static struct stat cf_info;
77 static int allow_gethostbyaddr = 0;
78 static volatile sig_atomic_t cur_state = STATE_INIT;
79 
80 /* Queue for the allowed logfiles */
81 struct logname {
82 	char path[MAXPATHLEN];
83 	TAILQ_ENTRY(logname) next;
84 };
85 static TAILQ_HEAD(, logname) lognames;
86 
87 static void check_log_name(char *, size_t);
88 static void check_tty_name(char *, size_t);
89 static void increase_state(int);
90 static void sig_pass_to_chld(int);
91 static void sig_got_chld(int);
92 static void must_read(int, void *, size_t);
93 static void must_write(int, void *, size_t);
94 static int  may_read(int, void *, size_t);
95 
96 int
97 priv_init(char *conf, int numeric, int lockfd, int nullfd, char *argv[])
98 {
99 	int i, fd, socks[2], cmd, addr_len, addr_af, result, restart;
100 	size_t path_len, hostname_len;
101 	char path[MAXPATHLEN], hostname[MAXHOSTNAMELEN];
102 	struct stat cf_stat;
103 	struct hostent *hp;
104 	struct passwd *pw;
105 
106 	/* Create sockets */
107 	if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, socks) == -1)
108 		err(1, "socketpair() failed");
109 
110 	pw = getpwnam("_syslogd");
111 	if (pw == NULL)
112 		errx(1, "unknown user _syslogd");
113 
114 	child_pid = fork();
115 	if (child_pid < 0)
116 		err(1, "fork() failed");
117 
118 	if (!child_pid) {
119 		gid_t gidset[1];
120 
121 		/* Child - drop privileges and return */
122 		if (chroot(pw->pw_dir) != 0)
123 			err(1, "unable to chroot");
124 		chdir("/");
125 
126 		gidset[0] = pw->pw_gid;
127 		if (setgroups(1, gidset) == -1)
128 			err(1, "setgroups() failed");
129 		if (setegid(pw->pw_gid) == -1)
130 			err(1, "setegid() failed");
131 		if (setgid(pw->pw_gid) == -1)
132 			err(1, "setgid() failed");
133 		if (seteuid(pw->pw_uid) == -1)
134 			err(1, "seteuid() failed");
135 		if (setuid(pw->pw_uid) == -1)
136 			err(1, "setuid() failed");
137 		close(socks[0]);
138 		priv_fd = socks[1];
139 		return 0;
140 	}
141 
142 	close(lockfd);
143 	if (!Debug) {
144 		dup2(nullfd, STDIN_FILENO);
145 		dup2(nullfd, STDOUT_FILENO);
146 		dup2(nullfd, STDERR_FILENO);
147 	}
148 
149 	if (nullfd > 2)
150 		close(nullfd);
151 
152 	/* Father */
153 	for (i = 1; i <= _NSIG; i++)
154 		signal(i, SIG_DFL);
155 
156 	/* Pass TERM/HUP through to child, and accept CHLD */
157 	signal(SIGTERM, sig_pass_to_chld);
158 	signal(SIGHUP, sig_pass_to_chld);
159 	signal(SIGCHLD, sig_got_chld);
160 
161 	setproctitle("[priv]");
162 	close(socks[1]);
163 
164 	/* Close descriptors that only the unpriv child needs */
165 	for (i = 0; i < nfunix; i++)
166 		if (funix[i] != -1)
167 			close(funix[i]);
168 	if (finet != -1)
169 		close(finet);
170 	if (fklog != -1)
171 		close(fklog);
172 
173 	/* Save the config file specified by the child process */
174 	if (strlcpy(config_file, conf, sizeof config_file) >= sizeof(config_file))
175 		errx(1, "config_file truncation");
176 
177 	if (stat(config_file, &cf_info) < 0)
178 		err(1, "stat config file failed");
179 
180 	/* Save whether or not the child can have access to gethostbyaddr(3) */
181 	if (numeric > 0)
182 		allow_gethostbyaddr = 0;
183 	else
184 		allow_gethostbyaddr = 1;
185 
186 	TAILQ_INIT(&lognames);
187 	increase_state(STATE_CONFIG);
188 	restart = 0;
189 
190 	while (cur_state < STATE_QUIT) {
191 		if (may_read(socks[0], &cmd, sizeof(int)))
192 			break;
193 		switch (cmd) {
194 		case PRIV_OPEN_TTY:
195 			dprintf("[priv]: msg PRIV_OPEN_TTY received\n");
196 			/* Expecting: length, path */
197 			must_read(socks[0], &path_len, sizeof(size_t));
198 			if (path_len == 0 || path_len > sizeof(path))
199 				_exit(0);
200 			must_read(socks[0], &path, path_len);
201 			path[path_len - 1] = '\0';
202 			check_tty_name(path, path_len);
203 			fd = open(path, O_WRONLY|O_NONBLOCK, 0);
204 			if (fd < 0)
205 				warnx("priv_open_tty failed");
206 			send_fd(socks[0], fd);
207 			close(fd);
208 			break;
209 
210 		case PRIV_OPEN_LOG:
211 			dprintf("[priv]: msg PRIV_OPEN_LOG received\n");
212 			/* Expecting: length, path */
213 			must_read(socks[0], &path_len, sizeof(size_t));
214 			if (path_len == 0 || path_len > sizeof(path))
215 				_exit(0);
216 			must_read(socks[0], &path, path_len);
217 			path[path_len - 1] = '\0';
218 			check_log_name(path, path_len);
219 			fd = open(path, O_WRONLY|O_APPEND|O_NONBLOCK, 0);
220 			if (fd < 0)
221 				warnx("priv_open_log failed");
222 			send_fd(socks[0], fd);
223 			close(fd);
224 			break;
225 
226 		case PRIV_OPEN_UTMP:
227 			dprintf("[priv]: msg PRIV_OPEN_UTMP received\n");
228 			fd = open(_PATH_UTMP, O_RDONLY|O_NONBLOCK, 0);
229 			if (fd < 0)
230 				warnx("priv_open_utmp failed");
231 			send_fd(socks[0], fd);
232 			close(fd);
233 			break;
234 
235 		case PRIV_OPEN_CONFIG:
236 			dprintf("[priv]: msg PRIV_OPEN_CONFIG received\n");
237 			stat(config_file, &cf_info);
238 			fd = open(config_file, O_RDONLY|O_NONBLOCK, 0);
239 			if (fd < 0)
240 				warnx("priv_open_config failed");
241 			send_fd(socks[0], fd);
242 			close(fd);
243 			break;
244 
245 		case PRIV_CONFIG_MODIFIED:
246 			dprintf("[priv]: msg PRIV_CONFIG_MODIFIED received\n");
247 			if (stat(config_file, &cf_stat) < 0 ||
248 			    timespeccmp(&cf_info.st_mtimespec,
249 			    &cf_stat.st_mtimespec, <) ||
250 			    cf_info.st_size != cf_stat.st_size) {
251 				dprintf("config file modified: restarting\n");
252 				restart = result = 1;
253 				must_write(socks[0], &result, sizeof(int));
254 			} else {
255 				result = 0;
256 				must_write(socks[0], &result, sizeof(int));
257 			}
258 			break;
259 
260 		case PRIV_DONE_CONFIG_PARSE:
261 			dprintf("[priv]: msg PRIV_DONE_CONFIG_PARSE received\n");
262 			increase_state(STATE_RUNNING);
263 			break;
264 
265 		case PRIV_GETHOSTBYNAME:
266 			dprintf("[priv]: msg PRIV_GETHOSTBYNAME received\n");
267 			/* Expecting: length, hostname */
268 			must_read(socks[0], &hostname_len, sizeof(size_t));
269 			if (hostname_len == 0 || hostname_len > sizeof(hostname))
270 				_exit(0);
271 			must_read(socks[0], &hostname, hostname_len);
272 			hostname[hostname_len - 1] = '\0';
273 			hp = gethostbyname(hostname);
274 			if (hp == NULL) {
275 				addr_len = 0;
276 				must_write(socks[0], &addr_len, sizeof(int));
277 			} else {
278 				must_write(socks[0], &hp->h_length, sizeof(int));
279 				must_write(socks[0], hp->h_addr, hp->h_length);
280 			}
281 			break;
282 
283 		case PRIV_GETHOSTBYADDR:
284 			dprintf("[priv]: msg PRIV_GETHOSTBYADDR received\n");
285 			if (!allow_gethostbyaddr)
286 				errx(1, "rejected attempt to gethostbyaddr");
287 			/* Expecting: length, address, address family */
288 			must_read(socks[0], &addr_len, sizeof(int));
289 			if (addr_len <= 0 || addr_len > sizeof(hostname))
290 				_exit(0);
291 			must_read(socks[0], hostname, addr_len);
292 			must_read(socks[0], &addr_af, sizeof(int));
293 			hp = gethostbyaddr(hostname, addr_len, addr_af);
294 			if (hp == NULL) {
295 				addr_len = 0;
296 				must_write(socks[0], &addr_len, sizeof(int));
297 			} else {
298 				addr_len = strlen(hp->h_name) + 1;
299 				must_write(socks[0], &addr_len, sizeof(int));
300 				must_write(socks[0], hp->h_name, addr_len);
301 			}
302 			break;
303 		default:
304 			errx(1, "unknown command %d", cmd);
305 			break;
306 		}
307 	}
308 
309 	/* Unlink any domain sockets that have been opened */
310 	for (i = 0; i < nfunix; i++)
311 		if (funixn[i] && funix[i] != -1)
312 			(void)unlink(funixn[i]);
313 
314 	if (restart) {
315 		int r;
316 
317 		wait(&r);
318 		execvp(argv[0], argv);
319 	}
320 	_exit(1);
321 }
322 
323 /* Check that the terminal device is ok, and if not, rewrite to /dev/null.
324  * Either /dev/console or /dev/tty* are allowed.
325  */
326 static void
327 check_tty_name(char *tty, size_t ttylen)
328 {
329 	const char ttypre[] = "/dev/tty";
330 	char *p;
331 
332 	/* Any path containing '..' is invalid.  */
333 	for (p = tty; *p && (p - tty) < ttylen; p++)
334 		if (*p == '.' && *(p + 1) == '.')
335 			goto bad_path;
336 
337 	if (strcmp(_PATH_CONSOLE, tty) && strncmp(tty, ttypre, strlen(ttypre)))
338 		goto bad_path;
339 	return;
340 
341 bad_path:
342 	warnx ("%s: invalid attempt to open %s: rewriting to /dev/null",
343 	    __func__, tty);
344 	strlcpy(tty, "/dev/null", ttylen);
345 }
346 
347 /* If we are in the initial configuration state, accept a logname and add
348  * it to the list of acceptable logfiles.  Otherwise, check against this list
349  * and rewrite to /dev/null if it's a bad path.
350  */
351 static void
352 check_log_name(char *log, size_t loglen)
353 {
354 	struct logname *lg;
355 	char *p;
356 
357 	/* Any path containing '..' is invalid.  */
358 	for (p = log; *p && (p - log) < loglen; p++)
359 		if (*p == '.' && *(p + 1) == '.')
360 			goto bad_path;
361 
362 	switch (cur_state) {
363 	case STATE_CONFIG:
364 		lg = malloc(sizeof(struct logname));
365 		if (!lg)
366 			err(1, "check_log_name() malloc");
367 		strlcpy(lg->path, log, MAXPATHLEN);
368 		TAILQ_INSERT_TAIL(&lognames, lg, next);
369 		break;
370 	case STATE_RUNNING:
371 		TAILQ_FOREACH(lg, &lognames, next)
372 			if (!strcmp(lg->path, log))
373 				return;
374 		goto bad_path;
375 		break;
376 	default:
377 		/* Any other state should just refuse the request */
378 		goto bad_path;
379 		break;
380 	}
381 	return;
382 
383 bad_path:
384 	warnx("%s: invalid attempt to open %s: rewriting to /dev/null",
385 	    __func__, log);
386 	strlcpy(log, "/dev/null", loglen);
387 }
388 
389 /* Crank our state into less permissive modes */
390 static void
391 increase_state(int state)
392 {
393 	if (state <= cur_state)
394 		errx(1, "attempt to decrease or match current state");
395 	if (state < STATE_INIT || state > STATE_QUIT)
396 		errx(1, "attempt to switch to invalid state");
397 	cur_state = state;
398 }
399 
400 /* Open console or a terminal device for writing */
401 int
402 priv_open_tty(const char *tty)
403 {
404 	char path[MAXPATHLEN];
405 	int cmd, fd;
406 	size_t path_len;
407 
408 	if (priv_fd < 0)
409 		errx(1, "%s: called from privileged portion", __func__);
410 
411 	if (strlcpy(path, tty, sizeof path) >= sizeof(path))
412 		return -1;
413 	path_len = strlen(path) + 1;
414 
415 	cmd = PRIV_OPEN_TTY;
416 	must_write(priv_fd, &cmd, sizeof(int));
417 	must_write(priv_fd, &path_len, sizeof(size_t));
418 	must_write(priv_fd, path, path_len);
419 	fd = receive_fd(priv_fd);
420 	return fd;
421 }
422 
423 /* Open log-file */
424 int
425 priv_open_log(const char *log)
426 {
427 	char path[MAXPATHLEN];
428 	int cmd, fd;
429 	size_t path_len;
430 
431 	if (priv_fd < 0)
432 		errx(1, "%s: called from privileged child", __func__);
433 
434 	if (strlcpy(path, log, sizeof path) >= sizeof(path))
435 		return -1;
436 	path_len = strlen(path) + 1;
437 
438 	cmd = PRIV_OPEN_LOG;
439 	must_write(priv_fd, &cmd, sizeof(int));
440 	must_write(priv_fd, &path_len, sizeof(size_t));
441 	must_write(priv_fd, path, path_len);
442 	fd = receive_fd(priv_fd);
443 	return fd;
444 }
445 
446 /* Open utmp for reading */
447 FILE *
448 priv_open_utmp(void)
449 {
450 	int cmd, fd;
451 	FILE *fp;
452 
453 	if (priv_fd < 0)
454 		errx(1, "%s: called from privileged portion", __func__);
455 
456 	cmd = PRIV_OPEN_UTMP;
457 	must_write(priv_fd, &cmd, sizeof(int));
458 	fd = receive_fd(priv_fd);
459 	if (fd < 0)
460 		return NULL;
461 
462 	fp = fdopen(fd, "r");
463 	if (!fp) {
464 		warn("priv_open_utmp: fdopen() failed");
465 		close(fd);
466 		return NULL;
467 	}
468 
469 	return fp;
470 }
471 
472 /* Open syslog config file for reading */
473 FILE *
474 priv_open_config(void)
475 {
476 	int cmd, fd;
477 	FILE *fp;
478 
479 	if (priv_fd < 0)
480 		errx(1, "%s: called from privileged portion", __func__);
481 
482 	cmd = PRIV_OPEN_CONFIG;
483 	must_write(priv_fd, &cmd, sizeof(int));
484 	fd = receive_fd(priv_fd);
485 	if (fd < 0)
486 		return NULL;
487 
488 	fp = fdopen(fd, "r");
489 	if (!fp) {
490 		warn("priv_open_config: fdopen() failed");
491 		close(fd);
492 		return NULL;
493 	}
494 
495 	return fp;
496 }
497 
498 /* Ask if config file has been modified since last attempt to read it */
499 int
500 priv_config_modified()
501 {
502 	int cmd, res;
503 
504 	if (priv_fd < 0)
505 		errx(1, "%s: called from privileged portion", __func__);
506 
507 	cmd = PRIV_CONFIG_MODIFIED;
508 	must_write(priv_fd, &cmd, sizeof(int));
509 
510 	/* Expect back integer signalling 1 for modification */
511 	must_read(priv_fd, &res, sizeof(int));
512 	return res;
513 }
514 
515 /* Child can signal that its initial parsing is done, so that parent
516  * can revoke further logfile permissions.  This call only works once. */
517 void
518 priv_config_parse_done(void)
519 {
520 	int cmd;
521 
522 	if (priv_fd < 0)
523 		errx(1, "%s: called from privileged portion", __func__);
524 
525 	cmd = PRIV_DONE_CONFIG_PARSE;
526 	must_write(priv_fd, &cmd, sizeof(int));
527 }
528 
529 /* Resolve hostname into address.  Response is placed into addr, and
530  * the length is returned (zero on error) */
531 int
532 priv_gethostbyname(char *host, char *addr, size_t addr_len)
533 {
534 	char hostcpy[MAXHOSTNAMELEN];
535 	int cmd, ret_len;
536 	size_t hostname_len;
537 
538 	if (priv_fd < 0)
539 		errx(1, "%s: called from privileged portion", __func__);
540 
541 	if (strlcpy(hostcpy, host, sizeof hostcpy) >= sizeof(hostcpy))
542 		errx(1, "%s: overflow attempt in hostname", __func__);
543 	hostname_len = strlen(hostcpy) + 1;
544 
545 	cmd = PRIV_GETHOSTBYNAME;
546 	must_write(priv_fd, &cmd, sizeof(int));
547 	must_write(priv_fd, &hostname_len, sizeof(size_t));
548 	must_write(priv_fd, hostcpy, hostname_len);
549 
550 	/* Expect back an integer size, and then a string of that length */
551 	must_read(priv_fd, &ret_len, sizeof(int));
552 
553 	/* Check there was no error (indicated by a return of 0) */
554 	if (!ret_len)
555 		return 0;
556 
557 	/* Make sure we aren't overflowing the passed in buffer */
558 	if (addr_len < ret_len)
559 		errx(1, "%s: overflow attempt in return", __func__);
560 
561 	/* Read the resolved address and make sure we got all of it */
562 	must_read(priv_fd, addr, ret_len);
563 	return ret_len;
564 }
565 
566 /* Reverse address resolution; response is placed into res, and length of
567  * response is returned (zero on error) */
568 int
569 priv_gethostbyaddr(char *addr, int addr_len, int af, char *res, size_t res_len)
570 {
571 	int cmd, ret_len;
572 
573 	if (priv_fd < 0)
574 		errx(1, "%s called from privileged portion", __func__);
575 
576 	cmd = PRIV_GETHOSTBYADDR;
577 	must_write(priv_fd, &cmd, sizeof(int));
578 	must_write(priv_fd, &addr_len, sizeof(int));
579 	must_write(priv_fd, addr, addr_len);
580 	must_write(priv_fd, &af, sizeof(int));
581 
582 	/* Expect back an integer size, and then a string of that length */
583 	must_read(priv_fd, &ret_len, sizeof(int));
584 
585 	/* Check there was no error (indicated by a return of 0) */
586 	if (!ret_len)
587 		return 0;
588 
589 	/* Check we don't overflow the passed in buffer */
590 	if (res_len < ret_len)
591 		errx(1, "%s: overflow attempt in return", __func__);
592 
593 	/* Read the resolved hostname */
594 	must_read(priv_fd, res, ret_len);
595 	return ret_len;
596 }
597 
598 /* If priv parent gets a TERM or HUP, pass it through to child instead */
599 static void
600 sig_pass_to_chld(int sig)
601 {
602 	if (child_pid != -1)
603 		kill(child_pid, sig);
604 }
605 
606 /* When child dies, move into the shutdown state */
607 static void
608 sig_got_chld(int sig)
609 {
610 	if (cur_state < STATE_QUIT)
611 		cur_state = STATE_QUIT;
612 }
613 
614 /* Read all data or return 1 for error.  */
615 static int
616 may_read(int fd, void *buf, size_t n)
617 {
618 	char *s = buf;
619 	ssize_t res, pos = 0;
620 
621 	while (n > pos) {
622 		res = read(fd, s + pos, n - pos);
623 		switch (res) {
624 		case -1:
625 			if (errno == EINTR || errno == EAGAIN)
626 				continue;
627 		case 0:
628 			return (1);
629 		default:
630 			pos += res;
631 		}
632 	}
633 	return (0);
634 }
635 
636 /* Read data with the assertion that it all must come through, or
637  * else abort the process.  Based on atomicio() from openssh. */
638 static void
639 must_read(int fd, void *buf, size_t n)
640 {
641 	char *s = buf;
642 	ssize_t res, pos = 0;
643 
644 	while (n > pos) {
645 		res = read(fd, s + pos, n - pos);
646 		switch (res) {
647 		case -1:
648 			if (errno == EINTR || errno == EAGAIN)
649 				continue;
650 		case 0:
651 			_exit(0);
652 		default:
653 			pos += res;
654 		}
655 	}
656 }
657 
658 /* Write data with the assertion that it all has to be written, or
659  * else abort the process.  Based on atomicio() from openssh. */
660 static void
661 must_write(int fd, void *buf, size_t n)
662 {
663 	char *s = buf;
664 	ssize_t res, pos = 0;
665 
666 	while (n > pos) {
667 		res = write(fd, s + pos, n - pos);
668 		switch (res) {
669 		case -1:
670 			if (errno == EINTR || errno == EAGAIN)
671 				continue;
672 		case 0:
673 			_exit(0);
674 		default:
675 			pos += res;
676 		}
677 	}
678 }
679