From 4ad0cde98b4120cf7c56cf9751af7b5cfc9cd6b5 Mon Sep 17 00:00:00 2001 From: henning <> Date: Mon, 31 May 2004 13:46:16 +0000 Subject: [PATCH] initial cut at ntpd. it is just capable of answering (s)ntp4 requests with the local time for now. imsg/buffer and logging framework from bgpd, ntp protocol hackery with Alexander Guy --- src/usr.sbin/ntpd/Makefile | 15 ++ src/usr.sbin/ntpd/buffer.c | 182 ++++++++++++++++++ src/usr.sbin/ntpd/imsg.c | 215 +++++++++++++++++++++ src/usr.sbin/ntpd/log.c | 154 +++++++++++++++ src/usr.sbin/ntpd/ntp.c | 385 +++++++++++++++++++++++++++++++++++++ src/usr.sbin/ntpd/ntp.h | 134 +++++++++++++ src/usr.sbin/ntpd/ntpd.c | 245 +++++++++++++++++++++++ src/usr.sbin/ntpd/ntpd.h | 112 +++++++++++ 8 files changed, 1442 insertions(+) create mode 100644 src/usr.sbin/ntpd/Makefile create mode 100644 src/usr.sbin/ntpd/buffer.c create mode 100644 src/usr.sbin/ntpd/imsg.c create mode 100644 src/usr.sbin/ntpd/log.c create mode 100644 src/usr.sbin/ntpd/ntp.c create mode 100644 src/usr.sbin/ntpd/ntp.h create mode 100644 src/usr.sbin/ntpd/ntpd.c create mode 100644 src/usr.sbin/ntpd/ntpd.h diff --git a/src/usr.sbin/ntpd/Makefile b/src/usr.sbin/ntpd/Makefile new file mode 100644 index 00000000..cd151b6a --- /dev/null +++ b/src/usr.sbin/ntpd/Makefile @@ -0,0 +1,15 @@ +# $OpenBSD: Makefile,v 1.1 2004/05/31 13:46:16 henning Exp $ + +.PATH: ${.CURDIR}/.. + +PROG= ntpd +SRCS= ntpd.c buffer.c log.c imsg.c ntp.c +CFLAGS+= -Wall -I${.CURDIR} +CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare +YFLAGS= +NOMAN= + +.include diff --git a/src/usr.sbin/ntpd/buffer.c b/src/usr.sbin/ntpd/buffer.c new file mode 100644 index 00000000..efc1bfff --- /dev/null +++ b/src/usr.sbin/ntpd/buffer.c @@ -0,0 +1,182 @@ +/* $OpenBSD: buffer.c,v 1.1 2004/05/31 13:46:16 henning Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "ntpd.h" + +int buf_write(int, struct buf *); +void buf_enqueue(struct msgbuf *, struct buf *); +void buf_dequeue(struct msgbuf *, struct buf *); + +struct buf * +buf_open(ssize_t len) +{ + struct buf *buf; + + if ((buf = calloc(1, sizeof(struct buf))) == NULL) + return (NULL); + if ((buf->buf = malloc(len)) == NULL) { + free(buf); + return (NULL); + } + buf->size = len; + + return (buf); +} + +int +buf_add(struct buf *buf, void *data, ssize_t len) +{ + if (buf->wpos + len > buf->size) + return (-1); + + memcpy(buf->buf + buf->wpos, data, len); + buf->wpos += len; + return (0); +} + +int +buf_close(struct msgbuf *msgbuf, struct buf *buf) +{ + buf_enqueue(msgbuf, buf); + return (1); +} + +int +buf_write(int sock, struct buf *buf) +{ + ssize_t n; + + if ((n = write(sock, buf->buf + buf->rpos, + buf->size - buf->rpos)) == -1) { + if (errno == EAGAIN) /* cannot write immediately */ + return (0); + else + return (-1); + } + + if (n == 0) { /* connection closed */ + errno = 0; + return (-2); + } + + if (n < buf->size - buf->rpos) { /* not all data written yet */ + buf->rpos += n; + return (0); + } else + return (1); +} + +void +buf_free(struct buf *buf) +{ + free(buf->buf); + free(buf); +} + +void +msgbuf_init(struct msgbuf *msgbuf) +{ + msgbuf->queued = 0; + msgbuf->fd = -1; + TAILQ_INIT(&msgbuf->bufs); +} + +void +msgbuf_clear(struct msgbuf *msgbuf) +{ + struct buf *buf; + + while ((buf = TAILQ_FIRST(&msgbuf->bufs)) != NULL) + buf_dequeue(msgbuf, buf); +} + +int +msgbuf_write(struct msgbuf *msgbuf) +{ + /* + * possible race here + * when we cannot write out data completely from a buffer, + * we MUST return and NOT try to write out stuff from later buffers - + * the socket might have become writeable again + */ + struct iovec iov[IOV_MAX]; + struct buf *buf, *next; + int i = 0; + ssize_t n; + + bzero(&iov, sizeof(iov)); + TAILQ_FOREACH(buf, &msgbuf->bufs, entries) { + if (i >= IOV_MAX) + break; + iov[i].iov_base = buf->buf + buf->rpos; + iov[i].iov_len = buf->size - buf->rpos; + i++; + } + + if ((n = writev(msgbuf->fd, iov, i)) == -1) { + if (errno == EAGAIN) /* cannot write immediately */ + return (0); + else + return (-1); + } + + if (n == 0) { /* connection closed */ + errno = 0; + return (-2); + } + + for (buf = TAILQ_FIRST(&msgbuf->bufs); buf != NULL && n > 0; + buf = next) { + next = TAILQ_NEXT(buf, entries); + if (n >= buf->size - buf->rpos) { + n -= buf->size - buf->rpos; + buf_dequeue(msgbuf, buf); + } else { + buf->rpos += n; + n = 0; + } + } + + return (0); +} + +void +buf_enqueue(struct msgbuf *msgbuf, struct buf *buf) +{ + TAILQ_INSERT_TAIL(&msgbuf->bufs, buf, entries); + msgbuf->queued++; +} + +void +buf_dequeue(struct msgbuf *msgbuf, struct buf *buf) +{ + TAILQ_REMOVE(&msgbuf->bufs, buf, entries); + msgbuf->queued--; + buf_free(buf); +} diff --git a/src/usr.sbin/ntpd/imsg.c b/src/usr.sbin/ntpd/imsg.c new file mode 100644 index 00000000..8dfaa893 --- /dev/null +++ b/src/usr.sbin/ntpd/imsg.c @@ -0,0 +1,215 @@ +/* $OpenBSD: imsg.c,v 1.1 2004/05/31 13:46:16 henning Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include + +#include "ntpd.h" + +int imsg_compose_core(struct imsgbuf *, int, u_int32_t, void *, + u_int16_t, pid_t); +struct buf *imsg_create_core(struct imsgbuf *, int, u_int32_t, u_int16_t, + pid_t); + +void +imsg_init(struct imsgbuf *ibuf, int fd) +{ + msgbuf_init(&ibuf->w); + bzero(&ibuf->r, sizeof(ibuf->r)); + ibuf->fd = fd; + ibuf->w.fd = fd; + ibuf->pid = getpid(); +} + +int +imsg_read(struct imsgbuf *ibuf) +{ + ssize_t n; + + if ((n = read(ibuf->fd, ibuf->r.buf + ibuf->r.wpos, + sizeof(ibuf->r.buf) - ibuf->r.wpos)) == -1) { + if (errno != EINTR && errno != EAGAIN) { + log_warn("imsg_read: pipe read error"); + return (-1); + } + return (0); + } + + ibuf->r.wpos += n; + + return (n); +} + +int +imsg_get(struct imsgbuf *ibuf, struct imsg *imsg) +{ + ssize_t datalen = 0; + size_t av, left; + + av = ibuf->r.wpos; + + if (IMSG_HEADER_SIZE > av) + return (0); + + memcpy(&imsg->hdr, ibuf->r.buf, sizeof(imsg->hdr)); + if (imsg->hdr.len < IMSG_HEADER_SIZE || + imsg->hdr.len > MAX_IMSGSIZE) { + log_warnx("imsg_get: imsg hdr len out of bounds"); + return (-1); + } + if (imsg->hdr.len > av) + return (0); + datalen = imsg->hdr.len - IMSG_HEADER_SIZE; + ibuf->r.rptr = ibuf->r.buf + IMSG_HEADER_SIZE; + if ((imsg->data = malloc(datalen)) == NULL) { + log_warn("imsg_get"); + return (-1); + } + memcpy(imsg->data, ibuf->r.rptr, datalen); + + if (imsg->hdr.len < av) { + left = av - imsg->hdr.len; + memcpy(&ibuf->r.buf, ibuf->r.buf + imsg->hdr.len, left); + ibuf->r.wpos = left; + } else + ibuf->r.wpos = 0; + + return (datalen + IMSG_HEADER_SIZE); +} + +int +imsg_compose(struct imsgbuf *ibuf, int type, u_int32_t peerid, void *data, + u_int16_t dlen) +{ + return (imsg_compose_core(ibuf, type, peerid, data, dlen, ibuf->pid)); +} + +int +imsg_compose_pid(struct imsgbuf *ibuf, int type, pid_t pid, void *data, + u_int16_t datalen) +{ + return (imsg_compose_core(ibuf, type, 0, data, datalen, pid)); +} + +int +imsg_compose_core(struct imsgbuf *ibuf, int type, u_int32_t peerid, void *data, + u_int16_t datalen, pid_t pid) +{ + struct buf *wbuf; + struct imsg_hdr hdr; + int n; + + hdr.len = datalen + IMSG_HEADER_SIZE; + hdr.type = type; + hdr.peerid = peerid; + hdr.pid = pid; + wbuf = buf_open(hdr.len); + if (wbuf == NULL) { + log_warn("imsg_compose: buf_open"); + return (-1); + } + if (buf_add(wbuf, &hdr, sizeof(hdr)) == -1) { + log_warnx("imsg_compose: buf_add error"); + buf_free(wbuf); + return (-1); + } + if (datalen) + if (buf_add(wbuf, data, datalen) == -1) { + log_warnx("imsg_compose: buf_add error"); + buf_free(wbuf); + return (-1); + } + + if ((n = buf_close(&ibuf->w, wbuf)) < 0) { + log_warnx("imsg_compose: buf_add error"); + buf_free(wbuf); + return (-1); + } + return (n); +} + +struct buf * +imsg_create(struct imsgbuf *ibuf, int type, u_int32_t peerid, u_int16_t dlen) +{ + return (imsg_create_core(ibuf, type, peerid, dlen, ibuf->pid)); +} + +struct buf * +imsg_create_pid(struct imsgbuf *ibuf, int type, pid_t pid, u_int16_t datalen) +{ + return (imsg_create_core(ibuf, type, 0, datalen, pid)); +} + +struct buf * +imsg_create_core(struct imsgbuf *ibuf, int type, u_int32_t peerid, + u_int16_t datalen, pid_t pid) +{ + struct buf *wbuf; + struct imsg_hdr hdr; + + hdr.len = datalen + IMSG_HEADER_SIZE; + hdr.type = type; + hdr.peerid = peerid; + hdr.pid = pid; + wbuf = buf_open(hdr.len); + if (wbuf == NULL) { + log_warn("imsg_create: buf_open"); + return (NULL); + } + if (buf_add(wbuf, &hdr, sizeof(hdr)) == -1) { + log_warnx("imsg_create: buf_add error"); + buf_free(wbuf); + return (NULL); + } + return (wbuf); +} + +int +imsg_add(struct buf *msg, void *data, u_int16_t datalen) +{ + if (datalen) + if (buf_add(msg, data, datalen) == -1) { + log_warnx("imsg_add: buf_add error"); + buf_free(msg); + return (-1); + } + return (datalen); +} + +int +imsg_close(struct imsgbuf *ibuf, struct buf *msg) +{ + int n; + + if ((n = buf_close(&ibuf->w, msg)) < 0) { + log_warnx("imsg_close: buf_add error"); + buf_free(msg); + return (-1); + } + return (n); +} + +void +imsg_free(struct imsg *imsg) +{ + free(imsg->data); +} diff --git a/src/usr.sbin/ntpd/log.c b/src/usr.sbin/ntpd/log.c new file mode 100644 index 00000000..6f0fd868 --- /dev/null +++ b/src/usr.sbin/ntpd/log.c @@ -0,0 +1,154 @@ +/* $OpenBSD: log.c,v 1.1 2004/05/31 13:46:16 henning Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ntpd.h" + +int debug; + +void logit(int, const char *, ...); + +void +log_init(int n_debug) +{ + extern char *__progname; + + debug = n_debug; + + if (!debug) + openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON); +} + +void +logit(int pri, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(pri, fmt, ap); + va_end(ap); +} + +void +vlog(int pri, const char *fmt, va_list ap) +{ + char *nfmt; + + if (debug) { + /* best effort in out of mem situations */ + if (asprintf(&nfmt, "%s\n", fmt) == -1) { + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } else { + vfprintf(stderr, nfmt, ap); + free(nfmt); + } + fflush(stderr); + } else + vsyslog(pri, fmt, ap); +} + + +void +log_warn(const char *emsg, ...) +{ + char *nfmt; + va_list ap; + + /* best effort to even work in out of memory situations */ + if (emsg == NULL) + logit(LOG_CRIT, "%s", strerror(errno)); + else { + va_start(ap, emsg); + + if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) { + /* we tried it... */ + vlog(LOG_CRIT, emsg, ap); + logit(LOG_CRIT, "%s", strerror(errno)); + } else { + vlog(LOG_CRIT, nfmt, ap); + free(nfmt); + } + va_end(ap); + } +} + +void +log_warnx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_CRIT, emsg, ap); + va_end(ap); +} + +void +log_info(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_INFO, emsg, ap); + va_end(ap); +} + +void +log_debug(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); +} + +void +fatal(const char *emsg) +{ + if (emsg == NULL) + logit(LOG_CRIT, "fatal: %s", strerror(errno)); + else + if (errno) + logit(LOG_CRIT, "fatal: %s: %s", + emsg, strerror(errno)); + else + logit(LOG_CRIT, "fatal: %s", emsg); + + exit(1); +} + +void +fatalx(const char *emsg) +{ + errno = 0; + fatal(emsg); +} diff --git a/src/usr.sbin/ntpd/ntp.c b/src/usr.sbin/ntpd/ntp.c new file mode 100644 index 00000000..cdf5af11 --- /dev/null +++ b/src/usr.sbin/ntpd/ntp.c @@ -0,0 +1,385 @@ +/* $OpenBSD: ntp.c,v 1.1 2004/05/31 13:46:16 henning Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * Copyright (c) 2004 Alexander Guy + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ntpd.h" +#include "ntp.h" + +#define PFD_LISTEN 0 +#define PFD_PIPE_MAIN 1 +#define PFD_MAX 2 + +volatile sig_atomic_t ntp_quit = 0; +struct imsgbuf ibuf_main; +struct l_fixedpt ref_ts; + +void ntp_sighdlr(int); +int setup_listener(struct servent *); +int ntp_dispatch_imsg(void); +int ntp_dispatch(int fd); +int parse_ntp_msg(char *, ssize_t, struct ntp_msg *); +int ntp_reply(int, struct sockaddr *, struct ntp_msg *, int); +int ntp_send(int, struct sockaddr *, struct ntp_msg *, ssize_t, int); +void get_ts(struct l_fixedpt *); + +void +ntp_sighdlr(int sig) +{ + switch (sig) { + case SIGINT: + case SIGTERM: + ntp_quit = 1; + break; + } +} + +pid_t +ntp_main(int pipe_prnt[2]) +{ + int nfds; + int sock = -1; + pid_t pid; + struct pollfd pfd[PFD_MAX]; + struct passwd *pw; + struct servent *se; + + switch (pid = fork()) { + case -1: + fatal("cannot fork"); + case 0: + break; + default: + return (pid); + } + + if ((se = getservbyname("ntp", "udp")) == NULL) + fatal("getservbyname"); + + if ((pw = getpwnam(NTPD_USER)) == NULL) + fatal(NULL); + + if (chroot(pw->pw_dir) == -1) + fatal("chroot"); + if (chdir("/") == -1) + fatal("chdir(\"/\")"); + + setproctitle("ntp engine"); + + sock = setup_listener(se); + + if (setgroups(1, &pw->pw_gid) || + setegid(pw->pw_gid) || setgid(pw->pw_gid) || + seteuid(pw->pw_uid) || setuid(pw->pw_uid)) + fatal("can't drop privileges"); + + endpwent(); + endservent(); + + signal(SIGTERM, ntp_sighdlr); + signal(SIGINT, ntp_sighdlr); + signal(SIGPIPE, SIG_IGN); + + close(pipe_prnt[0]); + imsg_init(&ibuf_main, pipe_prnt[1]); + + log_info("ntp engine ready"); + + while (ntp_quit == 0) { + get_ts(&ref_ts); /* XXX */ + bzero(&pfd, sizeof(pfd)); + pfd[PFD_LISTEN].fd = sock; + pfd[PFD_LISTEN].events = POLLIN; + pfd[PFD_PIPE_MAIN].fd = ibuf_main.fd; + pfd[PFD_PIPE_MAIN].events = POLLIN; + if (ibuf_main.w.queued > 0) + pfd[PFD_PIPE_MAIN].events |= POLLOUT; + + if ((nfds = poll(pfd, 2, INFTIM)) == -1) + if (errno != EINTR) { + log_warn("poll error"); + ntp_quit = 1; + } + + if (nfds > 0 && (pfd[PFD_PIPE_MAIN].revents & POLLOUT)) + if (msgbuf_write(&ibuf_main.w) < 0) { + log_warn("pipe write error (to parent)"); + ntp_quit = 1; + } + + if (nfds > 0 && pfd[PFD_PIPE_MAIN].revents & POLLIN) { + nfds--; + if (ntp_dispatch_imsg() == -1) + ntp_quit = 1; + } + + if (nfds > 0 && pfd[PFD_LISTEN].revents & POLLIN) { + nfds--; + if (ntp_dispatch(sock) == -1) + ntp_quit = 1; + } + } + + msgbuf_write(&ibuf_main.w); + msgbuf_clear(&ibuf_main.w); + + log_info("ntp engine exiting"); + _exit(0); +} + +int +setup_listener(struct servent *se) +{ + struct sockaddr_in sa_in; + int fd; + + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + fatal("socket"); + + bzero(&sa_in, sizeof(sa_in)); + + sa_in.sin_family = AF_INET; + sa_in.sin_addr.s_addr = htonl(INADDR_ANY); + sa_in.sin_port = se->s_port; + + if (bind(fd, (struct sockaddr *)&sa_in, sizeof(sa_in)) == -1) + fatal("bind"); + + return (fd); +} + +int +ntp_dispatch_imsg(void) +{ + struct imsg imsg; + int n; + + if ((n = imsg_read(&ibuf_main)) == -1) + return (-1); + + if (n == 0) { /* connection closed */ + log_warnx("ntp_dispatch_imsg in ntp engine: pipe closed"); + return (-1); + } + + for (;;) { + if ((n = imsg_get(&ibuf_main, &imsg)) == -1) + return (-1); + + if (n == 0) + break; + + switch (imsg.hdr.type) { + default: + break; + } + imsg_free(&imsg); + } + return (0); +} + +int +ntp_dispatch(int fd) +{ + struct sockaddr fsa; + socklen_t fsa_len; + char buf[NTP_MSGSIZE]; + ssize_t size; + struct ntp_msg msg; + + fsa_len = sizeof(fsa); + if ((size = recvfrom(fd, &buf, sizeof(buf), 0, &fsa, &fsa_len)) == -1) + fatal("recvfrom"); + + parse_ntp_msg(buf, size, &msg); + ntp_reply(fd, &fsa, &msg, 0); + + return (0); +} + +int +parse_ntp_msg(char *p, ssize_t len, struct ntp_msg *msg) +{ + int auth, i; + + if (len == NTP_MSGSIZE) + auth = 1; + else if (len == NTP_MSGSIZE_NOAUTH) + auth = 0; + else { + log_warnx("malformed packet received"); + return (-1); + } + + memcpy(&msg->status, p, sizeof(msg->status)); + p += sizeof(msg->status); + memcpy(&msg->stratum, p, sizeof(msg->stratum)); + p += sizeof(msg->stratum); + memcpy(&msg->ppoll, p, sizeof(msg->ppoll)); + p += sizeof(msg->ppoll); + memcpy(&msg->precision, p, sizeof(msg->precision)); + p += sizeof(msg->precision); + memcpy(&msg->distance.int_part, p, sizeof(msg->distance.int_part)); + p += sizeof(msg->distance.int_part); + memcpy(&msg->distance.fraction, p, sizeof(msg->distance.fraction)); + p += sizeof(msg->distance.fraction); + memcpy(&msg->dispersion.int_part, p, sizeof(msg->dispersion.int_part)); + p += sizeof(msg->dispersion.int_part); + memcpy(&msg->dispersion.fraction, p, sizeof(msg->dispersion.fraction)); + p += sizeof(msg->dispersion.fraction); + memcpy(&msg->refid, p, sizeof(msg->refid)); + p += sizeof(msg->refid); + memcpy(&msg->reftime.int_part, p, sizeof(msg->reftime.int_part)); + p += sizeof(msg->reftime.int_part); + memcpy(&msg->reftime.fraction, p, sizeof(msg->reftime.fraction)); + p += sizeof(msg->reftime.fraction); + memcpy(&msg->orgtime.int_part, p, sizeof(msg->orgtime.int_part)); + p += sizeof(msg->orgtime.int_part); + memcpy(&msg->orgtime.fraction, p, sizeof(msg->orgtime.fraction)); + p += sizeof(msg->orgtime.fraction); + memcpy(&msg->rectime.int_part, p, sizeof(msg->rectime.int_part)); + p += sizeof(msg->rectime.int_part); + memcpy(&msg->rectime.fraction, p, sizeof(msg->rectime.fraction)); + p += sizeof(msg->rectime.fraction); + memcpy(&msg->xmttime.int_part, p, sizeof(msg->xmttime.int_part)); + p += sizeof(msg->xmttime.int_part); + memcpy(&msg->xmttime.fraction, p, sizeof(msg->xmttime.fraction)); + p += sizeof(msg->xmttime.fraction); + + if (auth) { + memcpy(&msg->keyid, p, sizeof(msg->keyid)); + p += sizeof(msg->refid); + for (i = 0; i < NTP_DIGESTSIZE; i++) { + memcpy(&msg->digest[i], p, sizeof(msg->digest[i])); + p += sizeof(msg->digest[i]); + } + + /* XXX check auth */ + } + + return (0); +} + +int +ntp_reply(int fd, struct sockaddr *sa, struct ntp_msg *query, int auth) +{ + ssize_t len; + struct l_fixedpt t; + struct ntp_msg reply; + + if (auth) + len = NTP_MSGSIZE; + else + len = NTP_MSGSIZE_NOAUTH; + + bzero(&reply, sizeof(reply)); + reply.status = 0 | (query->status & VERSIONMASK); + if ((query->status & MODEMASK) == MODE_CLIENT) + reply.status |= MODE_SERVER; + else + reply.status |= MODE_SYM_PAS; + + reply.stratum = 2; + reply.ppoll = query->ppoll; + reply.precision = 0; /* XXX */ + reply.refid = htonl(t.fraction); /* XXX */ + reply.reftime.int_part = htonl(ref_ts.int_part); + reply.reftime.fraction = htonl(ref_ts.fraction); + get_ts(&t); + reply.rectime.int_part = htonl(t.int_part); + reply.rectime.fraction = htonl(t.fraction); + reply.xmttime.int_part = htonl(t.int_part); + reply.xmttime.fraction = htonl(t.fraction); + reply.orgtime.int_part = query->xmttime.int_part; + reply.orgtime.fraction = query->xmttime.fraction; + + return (ntp_send(fd, sa, &reply, len, auth)); +} + +int +ntp_send(int fd, struct sockaddr *sa, struct ntp_msg *reply, ssize_t len, + int auth) +{ + char buf[NTP_MSGSIZE]; + char *p; + + p = buf; + memcpy(p, &reply->status, sizeof(reply->status)); + p += sizeof(reply->status); + memcpy(p, &reply->stratum, sizeof(reply->stratum)); + p += sizeof(reply->stratum); + memcpy(p, &reply->ppoll, sizeof(reply->ppoll)); + p += sizeof(reply->ppoll); + memcpy(p, &reply->precision, sizeof(reply->precision)); + p += sizeof(reply->precision); + memcpy(p, &reply->distance.int_part, sizeof(reply->distance.int_part)); + p += sizeof(reply->distance.int_part); + memcpy(p, &reply->distance.fraction, sizeof(reply->distance.fraction)); + p += sizeof(reply->distance.fraction); + memcpy(p, &reply->dispersion.int_part, + sizeof(reply->dispersion.int_part)); + p += sizeof(reply->dispersion.int_part); + memcpy(p, &reply->dispersion.fraction, + sizeof(reply->dispersion.fraction)); + p += sizeof(reply->dispersion.fraction); + memcpy(p, &reply->refid, sizeof(reply->refid)); + p += sizeof(reply->refid); + memcpy(p, &reply->reftime.int_part, sizeof(reply->reftime.int_part)); + p += sizeof(reply->reftime.int_part); + memcpy(p, &reply->reftime.fraction, sizeof(reply->reftime.fraction)); + p += sizeof(reply->reftime.fraction); + memcpy(p, &reply->orgtime.int_part, sizeof(reply->orgtime.int_part)); + p += sizeof(reply->orgtime.int_part); + memcpy(p, &reply->orgtime.fraction, sizeof(reply->orgtime.fraction)); + p += sizeof(reply->orgtime.fraction); + memcpy(p, &reply->rectime.int_part, sizeof(reply->rectime.int_part)); + p += sizeof(reply->rectime.int_part); + memcpy(p, &reply->rectime.fraction, sizeof(reply->rectime.fraction)); + p += sizeof(reply->rectime.fraction); + memcpy(p, &reply->xmttime.int_part, sizeof(reply->xmttime.int_part)); + p += sizeof(reply->xmttime.int_part); + memcpy(p, &reply->xmttime.fraction, sizeof(reply->xmttime.fraction)); + p += sizeof(reply->xmttime.fraction); + + if (auth) { + /* XXX */ + } + + if (sendto(fd, &buf, len, 0, sa, sa->sa_len) != len) + fatal("sendto"); + + return (0); +} + +void +get_ts(struct l_fixedpt *t) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + t->int_part = tv.tv_sec + JAN_1970; + t->fraction = ((float)tv.tv_usec)/1000000 * UINT_MAX; +} \ No newline at end of file diff --git a/src/usr.sbin/ntpd/ntp.h b/src/usr.sbin/ntpd/ntp.h new file mode 100644 index 00000000..cdc9c9e8 --- /dev/null +++ b/src/usr.sbin/ntpd/ntp.h @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2004 Henning Brauer + * Copyright (c) 2004 Alexander Guy + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* Style borrowed from NTP ref/tcpdump and updated for SNTPv4 (RFC2030). */ + +/* + * RFC Section 3 + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Integer Part | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Fraction Part | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Integer Part | Fraction Part | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +struct l_fixedpt { + u_int32_t int_part; + u_int32_t fraction; +}; + +struct s_fixedpt { + u_int16_t int_part; + u_int16_t fraction; +}; + +/* RFC Section 4 + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |LI | VN | Mode| Stratum | Poll | Precision | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Synchronizing Distance | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Synchronizing Dispersion | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reference Clock Identifier | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * | Reference Timestamp (64 bits) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * | Originate Timestamp (64 bits) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * | Receive Timestamp (64 bits) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * | Transmit Timestamp (64 bits) | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Key Identifier (optional) (32) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * | | + * | Message Digest (optional) (128) | + * | | + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + */ + +#define NTP_DIGESTSIZE 16 +#define NTP_MSGSIZE_NOAUTH 48 +#define NTP_MSGSIZE (NTP_MSGSIZE_NOAUTH + 4 + NTP_DIGESTSIZE) + +struct ntp_msg { + u_int8_t status; /* status of local clock and leap info */ + u_int8_t stratum; /* Stratum level */ + u_int8_t ppoll; /* poll value */ + int8_t precision; + struct s_fixedpt distance; + struct s_fixedpt dispersion; + u_int32_t refid; + struct l_fixedpt reftime; + struct l_fixedpt orgtime; + struct l_fixedpt rectime; + struct l_fixedpt xmttime; + u_int32_t keyid; + u_int8_t digest[NTP_DIGESTSIZE]; +}; + +/* + * Leap Second Codes (high order two bits) + */ +#define LI_NOWARNING (0 << 6) /* no warning */ +#define LI_PLUSSEC (1 << 6) /* add a second (61 seconds) */ +#define LI_MINUSSEC (2 << 6) /* minus a second (59 seconds) */ +#define LI_ALARM (3 << 6) /* alarm condition */ + +/* + * Status Masks + */ +#define MODEMASK (7 << 0) +#define VERSIONMASK (7 << 3) +#define LIMASK (2 << 6) + +/* + * Mode values + */ +#define MODE_RES0 0 /* reserved */ +#define MODE_SYM_ACT 1 /* symmetric active */ +#define MODE_SYM_PAS 2 /* symmetric passive */ +#define MODE_CLIENT 3 /* client */ +#define MODE_SERVER 4 /* server */ +#define MODE_BROADCAST 5 /* broadcast */ +#define MODE_RES1 6 /* reserved for NTP control message */ +#define MODE_RES2 7 /* reserved for private use */ + +#define JAN_1970 2208988800UL /* 1970 - 1900 in seconds */ diff --git a/src/usr.sbin/ntpd/ntpd.c b/src/usr.sbin/ntpd/ntpd.c new file mode 100644 index 00000000..ae82abd3 --- /dev/null +++ b/src/usr.sbin/ntpd/ntpd.c @@ -0,0 +1,245 @@ +/* $OpenBSD: ntpd.c,v 1.1 2004/05/31 13:46:16 henning Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ntpd.h" + +void sighdlr(int); +void usage(void); +int main(int, char *[]); +int check_child(pid_t, const char *); +int reconfigure(char *); +int dispatch_imsg(void); + +int rfd = -1; +volatile sig_atomic_t quit = 0; +volatile sig_atomic_t reconfig = 0; +volatile sig_atomic_t sigchld = 0; +struct imsgbuf ibuf; + +void +sighdlr(int sig) +{ + switch (sig) { + case SIGTERM: + case SIGINT: + quit = 1; + break; + case SIGCHLD: + sigchld = 1; + break; + case SIGHUP: + reconfig = 1; + break; + } +} + +void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s [-d] [-f file]", __progname); + exit(1); +} + +#define POLL_MAX 8 +#define PFD_PIPE 0 + +int +main(int argc, char *argv[]) +{ + struct pollfd pfd[POLL_MAX]; + pid_t chld_pid = 0, pid; + char *conffile; + int debug = 0; + int ch, nfds; + int pipe_chld[2]; + + conffile = CONFFILE; + + log_init(1); /* log to stderr until daemonized */ + + while ((ch = getopt(argc, argv, "df:")) != -1) { + switch (ch) { + case 'd': + debug = 1; + break; + case 'f': + conffile = optarg; + break; + default: + usage(); + /* NOTREACHED */ + } + } + + if (geteuid()) + errx(1, "need root privileges"); + + if (getpwnam(NTPD_USER) == NULL) + errx(1, "unknown user %s", NTPD_USER); + endpwent(); + + log_init(debug); + + if (!debug) + daemon(1, 0); + + log_info("startup"); + + if (pipe(pipe_chld) == -1) + fatal("pipe"); + + /* fork children */ + chld_pid = ntp_main(pipe_chld); + + setproctitle("[priv]"); + + signal(SIGTERM, sighdlr); + signal(SIGINT, sighdlr); + signal(SIGCHLD, sighdlr); + signal(SIGHUP, sighdlr); + + close(pipe_chld[1]); + + imsg_init(&ibuf, pipe_chld[0]); + + while (quit == 0) { + pfd[PFD_PIPE].fd = ibuf.fd; + pfd[PFD_PIPE].events = POLLIN; + if (ibuf.w.queued) + pfd[PFD_PIPE].events |= POLLOUT; + + if ((nfds = poll(pfd, 1, INFTIM)) == -1) + if (errno != EINTR) { + log_warn("poll error"); + quit = 1; + } + + if (nfds > 0 && (pfd[PFD_PIPE].revents & POLLOUT)) + if (msgbuf_write(&ibuf.w) < 0) { + log_warn("pipe write error (to child"); + quit = 1; + } + + if (nfds > 0 && pfd[PFD_PIPE].revents & POLLIN) { + nfds--; + if (dispatch_imsg() == -1) + quit = 1; + } + + if (reconfig) { + log_info("rereading config"); + reconfigure(conffile); + reconfig = 0; + } + + if (sigchld) { + if (check_child(chld_pid, "child")) + quit = 1; + sigchld = 0; + } + + } + + signal(SIGCHLD, SIG_IGN); + + if (chld_pid) + kill(chld_pid, SIGTERM); + + do { + if ((pid = wait(NULL)) == -1 && + errno != EINTR && errno != ECHILD) + fatal("wait"); + } while (pid != -1 || (pid == -1 && errno == EINTR)); + + log_info("Terminating"); + return (0); +} + +int +check_child(pid_t pid, const char *pname) +{ + int status; + + if (waitpid(pid, &status, WNOHANG) > 0) { + if (WIFEXITED(status)) { + log_warnx("Lost child: %s exited", pname); + return (1); + } + if (WIFSIGNALED(status)) { + log_warnx("Lost child: %s terminated; signal %d", + pname, WTERMSIG(status)); + return (1); + } + } + + return (0); +} + +int +reconfigure(char *conffile) +{ + return (-1); +} + +int +dispatch_imsg(void) +{ + struct imsg imsg; + int n; + + if ((n = imsg_read(&ibuf)) == -1) + return (-1); + + if (n == 0) { /* connection closed */ + log_warnx("dispatch_imsg in main: pipe closed"); + return (-1); + } + + for (;;) { + if ((n = imsg_get(&ibuf, &imsg)) == -1) + return (-1); + + if (n == 0) + break; + + switch (imsg.hdr.type) { + default: + break; + } + imsg_free(&imsg); + } + return (0); +} diff --git a/src/usr.sbin/ntpd/ntpd.h b/src/usr.sbin/ntpd/ntpd.h new file mode 100644 index 00000000..02875b20 --- /dev/null +++ b/src/usr.sbin/ntpd/ntpd.h @@ -0,0 +1,112 @@ +/* $OpenBSD: ntpd.h,v 1.1 2004/05/31 13:46:16 henning Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#define NTPD_USER "_ntp" +#define CONFFILE "/etc/ntpd.conf" + +#define READ_BUF_SIZE 65535 + +struct buf { + TAILQ_ENTRY(buf) entries; + u_char *buf; + ssize_t size; + ssize_t wpos; + ssize_t rpos; +}; + +struct msgbuf { + u_int32_t queued; + int fd; + TAILQ_HEAD(bufs, buf) bufs; +}; + +struct buf_read { + u_char buf[READ_BUF_SIZE]; + u_char *rptr; + ssize_t wpos; +}; + +/* ipc messages */ + +#define IMSG_HEADER_SIZE sizeof(struct imsg_hdr) +#define MAX_IMSGSIZE 8192 + +struct imsgbuf { + int fd; + pid_t pid; + struct buf_read r; + struct msgbuf w; +}; + +enum imsg_type { + IMSG_NONE +}; + +struct imsg_hdr { + enum imsg_type type; + u_int16_t len; + u_int32_t peerid; + pid_t pid; +}; + +struct imsg { + struct imsg_hdr hdr; + void *data; +}; + +/* prototypes */ +/* log.c */ +void log_init(int); +void vlog(int, const char *, va_list); +void log_warn(const char *, ...); +void log_warnx(const char *, ...); +void log_info(const char *, ...); +void log_debug(const char *, ...); +void fatal(const char *); +void fatalx(const char *); + +/* buffer.c */ +struct buf *buf_open(ssize_t); +int buf_add(struct buf *, void *, ssize_t); +int buf_close(struct msgbuf *, struct buf *); +void buf_free(struct buf *); +void msgbuf_init(struct msgbuf *); +void msgbuf_clear(struct msgbuf *); +int msgbuf_write(struct msgbuf *); + +/* imsg.c */ +void imsg_init(struct imsgbuf *, int); +int imsg_read(struct imsgbuf *); +int imsg_get(struct imsgbuf *, struct imsg *); +int imsg_compose(struct imsgbuf *, int, u_int32_t, void *, u_int16_t); +int imsg_compose_pid(struct imsgbuf *, int, pid_t, void *, u_int16_t); +struct buf *imsg_create(struct imsgbuf *, int, u_int32_t, u_int16_t); +struct buf *imsg_create_pid(struct imsgbuf *, int, pid_t, u_int16_t); +int imsg_add(struct buf *, void *, u_int16_t); +int imsg_close(struct imsgbuf *, struct buf *); +void imsg_free(struct imsg *); + +/* ntp.c */ +pid_t ntp_main(int[2]);