root/usr.sbin/ntpd/server.c
/*      $OpenBSD: server.c,v 1.44 2016/09/03 11:52:06 reyk Exp $ */

/*
 * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
 * Copyright (c) 2004 Alexander Guy <alexander@openbsd.org>
 *
 * 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.
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <errno.h>
#include <ifaddrs.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "ntpd.h"

int
setup_listeners(struct servent *se, struct ntpd_conf *lconf, u_int *cnt)
{
        struct listen_addr      *la, *nla, *lap;
        struct ifaddrs          *ifa, *ifap;
        struct sockaddr         *sa;
        struct if_data          *ifd;
        u_int8_t                *a6;
        size_t                   sa6len = sizeof(struct in6_addr);
        u_int                    new_cnt = 0;
        int                      tos = IPTOS_LOWDELAY, rdomain = 0;

        TAILQ_FOREACH(lap, &lconf->listen_addrs, entry) {
                switch (lap->sa.ss_family) {
                case AF_UNSPEC:
                        if (getifaddrs(&ifa) == -1)
                                fatal("getifaddrs");

                        for (ifap = ifa; ifap != NULL; ifap = ifap->ifa_next) {
                                sa = ifap->ifa_addr;
                                if (sa == NULL || SA_LEN(sa) == 0)
                                        continue;
                                if (sa->sa_family == AF_LINK) {
                                        ifd = ifap->ifa_data;
                                        rdomain = ifd->ifi_rdomain;
                                }
                                if (sa->sa_family != AF_INET &&
                                    sa->sa_family != AF_INET6)
                                        continue;
                                if (lap->rtable != -1 && rdomain != lap->rtable)
                                        continue;

                                if (sa->sa_family == AF_INET &&
                                    ((struct sockaddr_in *)sa)->sin_addr.s_addr ==
                                    INADDR_ANY)
                                        continue;

                                if (sa->sa_family == AF_INET6) {
                                        a6 = ((struct sockaddr_in6 *)sa)->
                                            sin6_addr.s6_addr;
                                        if (memcmp(a6, &in6addr_any, sa6len) == 0)
                                                continue;
                                }

                                if ((la = calloc(1, sizeof(struct listen_addr))) ==
                                    NULL)
                                        fatal("setup_listeners calloc");

                                memcpy(&la->sa, sa, SA_LEN(sa));
                                la->rtable = rdomain;

                                TAILQ_INSERT_TAIL(&lconf->listen_addrs, la, entry);
                        }

                        freeifaddrs(ifa);
                default:
                        continue;
                }
        }


        for (la = TAILQ_FIRST(&lconf->listen_addrs); la; ) {
                switch (la->sa.ss_family) {
                case AF_INET:
                        if (((struct sockaddr_in *)&la->sa)->sin_port == 0)
                                ((struct sockaddr_in *)&la->sa)->sin_port =
                                    se->s_port;
                        break;
                case AF_INET6:
                        if (((struct sockaddr_in6 *)&la->sa)->sin6_port == 0)
                                ((struct sockaddr_in6 *)&la->sa)->sin6_port =
                                    se->s_port;
                        break;
                case AF_UNSPEC:
                        nla = TAILQ_NEXT(la, entry);
                        TAILQ_REMOVE(&lconf->listen_addrs, la, entry);
                        free(la);
                        la = nla;
                        continue;
                default:
                        fatalx("king bula sez: af borked");
                }

                log_info("listening on %s %s",
                    log_sockaddr((struct sockaddr *)&la->sa),
                    print_rtable(la->rtable));

                if ((la->fd = socket(la->sa.ss_family, SOCK_DGRAM, 0)) == -1)
                        fatal("socket");

                if (la->sa.ss_family == AF_INET && setsockopt(la->fd,
                    IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) == -1)
                        log_warn("setsockopt IPTOS_LOWDELAY");

                if (la->rtable != -1 &&
                    setsockopt(la->fd, SOL_SOCKET, SO_RTABLE, &la->rtable,
                    sizeof(la->rtable)) == -1)
                        fatal("setup_listeners setsockopt SO_RTABLE");

                if (bind(la->fd, (struct sockaddr *)&la->sa,
                    SA_LEN((struct sockaddr *)&la->sa)) == -1) {
                        log_warn("bind on %s failed, skipping",
                            log_sockaddr((struct sockaddr *)&la->sa));
                        close(la->fd);
                        nla = TAILQ_NEXT(la, entry);
                        TAILQ_REMOVE(&lconf->listen_addrs, la, entry);
                        free(la);
                        la = nla;
                        continue;
                }
                new_cnt++;
                la = TAILQ_NEXT(la, entry);
        }

        *cnt = new_cnt;

        return (0);
}

int
server_dispatch(int fd, struct ntpd_conf *lconf)
{
        ssize_t                  size;
        double                   rectime;
        struct sockaddr_storage  fsa;
        socklen_t                fsa_len;
        struct ntp_msg           query, reply;
        char                     buf[NTP_MSGSIZE];

        fsa_len = sizeof(fsa);
        if ((size = recvfrom(fd, &buf, sizeof(buf), 0,
            (struct sockaddr *)&fsa, &fsa_len)) == -1) {
                if (errno == EHOSTUNREACH || errno == EHOSTDOWN ||
                    errno == ENETUNREACH || errno == ENETDOWN) {
                        log_warn("recvfrom %s",
                            log_sockaddr((struct sockaddr *)&fsa));
                        return (0);
                } else
                        fatal("recvfrom");
        }

        rectime = gettime_corrected();

        if (ntp_getmsg((struct sockaddr *)&fsa, buf, size, &query) == -1)
                return (0);

        memset(&reply, 0, sizeof(reply));
        if (lconf->status.synced)
                reply.status = lconf->status.leap;
        else
                reply.status = LI_ALARM;
        reply.status |= (query.status & VERSIONMASK);
        if ((query.status & MODEMASK) == MODE_CLIENT)
                reply.status |= MODE_SERVER;
        else if ((query.status & MODEMASK) == MODE_SYM_ACT)
                reply.status |= MODE_SYM_PAS;
        else /* ignore packets of different type (e.g. bcast) */
                return (0);

        reply.stratum = lconf->status.stratum;
        reply.ppoll = query.ppoll;
        reply.precision = lconf->status.precision;
        reply.rectime = d_to_lfp(rectime);
        reply.reftime = d_to_lfp(lconf->status.reftime);
        reply.xmttime = d_to_lfp(gettime_corrected());
        reply.orgtime = query.xmttime;
        reply.rootdelay = d_to_sfp(lconf->status.rootdelay);
        reply.refid = lconf->status.refid;

        ntp_sendmsg(fd, (struct sockaddr *)&fsa, &reply);
        return (0);
}