/*      $NetBSD: main.c,v 1.19 2024/08/18 20:47:20 christos Exp $       */

#include <config.h>

#include <event2/util.h>
#include <event2/event.h>

#include "ntp_workimpl.h"
#ifdef WORK_THREAD
# include <event2/thread.h>
#endif

#ifdef HAVE_SYSEXITS_H
# include <sysexits.h>
#endif

#include "main.h"
#include "ntp_libopts.h"
#include "kod_management.h"
#include "networking.h"
#include "utilities.h"
#include "log.h"
#include "libntp.h"

extern const char *progname;

int shutting_down;
int time_derived;
int time_adjusted;
int n_pending_dns = 0;
int n_pending_ntp = 0;
int ai_fam_pref = AF_UNSPEC;
int ntpver = 4;
double steplimit = -1;
SOCKET sock4 = -1;              /* Socket for IPv4 */
SOCKET sock6 = -1;              /* Socket for IPv6 */
/*
** BCAST *must* listen on port 123 (by default), so we can only
** use the UCST sockets (above) if they too are using port 123
*/
SOCKET bsock4 = -1;             /* Broadcast Socket for IPv4 */
SOCKET bsock6 = -1;             /* Broadcast Socket for IPv6 */
struct event_base *base;
struct event *ev_sock4;
struct event *ev_sock6;
struct event *ev_worker_timeout;
struct event *ev_xmt_timer;

struct dns_ctx {
       const char *    name;
       int             flags;
#define CTX_BCST        0x0001
#define CTX_UCST        0x0002
#define CTX_xCST        0x0003
#define CTX_CONC        0x0004
#define CTX_unused      0xfffd
       int             key_id;
       struct timeval  timeout;
       struct key *    key;
};

typedef struct sent_pkt_tag sent_pkt;
struct sent_pkt_tag {
       sent_pkt *              link;
       struct dns_ctx *        dctx;
       sockaddr_u              addr;
       time_t                  stime;
       int                     done;
       struct pkt              x_pkt;
};

typedef struct xmt_ctx_tag xmt_ctx;
struct xmt_ctx_tag {
       xmt_ctx *               link;
       SOCKET                  sock;
       time_t                  sched;
       sent_pkt *              spkt;
};

struct timeval  gap;
xmt_ctx *       xmt_q;
struct key *    keys = NULL;
int             response_timeout;
struct timeval  response_tv;
struct timeval  start_tv;
/* check the timeout at least once per second */
struct timeval  wakeup_tv = { 0, 888888 };

sent_pkt *      fam_listheads[2];
#define v4_pkts_list    (fam_listheads[0])
#define v6_pkts_list    (fam_listheads[1])

static union {
       struct pkt pkt;
       char   buf[LEN_PKT_NOMAC + NTP_MAXEXTEN + MAX_MAC_LEN];
} rbuf;

#define r_pkt  rbuf.pkt

#ifdef HAVE_DROPROOT
int droproot;                   /* intres imports these */
int root_dropped;
#endif
u_long current_time;            /* libntp/authkeys.c */

void open_sockets(void);
void handle_lookup(const char *name, int flags);
void sntp_addremove_fd(int fd, int is_pipe, int remove_it);
void worker_timeout(evutil_socket_t, short, void *);
void worker_resp_cb(evutil_socket_t, short, void *);
void sntp_name_resolved(int, int, void *, const char *, const char *,
                       const struct addrinfo *,
                       const struct addrinfo *);
void queue_xmt(SOCKET sock, struct dns_ctx *dctx, sent_pkt *spkt,
              u_int xmt_delay);
void xmt_timer_cb(evutil_socket_t, short, void *ptr);
void xmt(xmt_ctx *xctx);
int  check_kod(const struct addrinfo *ai);
void timeout_query(sent_pkt *);
void timeout_queries(void);
void sock_cb(evutil_socket_t, short, void *);
void check_exit_conditions(void);
void sntp_libevent_log_cb(int, const char *);
void set_li_vn_mode(struct pkt *spkt, char leap, char version, char mode);
int  set_time(double offset);
void dec_pending_ntp(const char *, sockaddr_u *);
int  libevent_version_ok(void);
int  gettimeofday_cached(struct event_base *b, struct timeval *tv);


/*
* The actual main function.
*/
int
sntp_main (
       int argc,
       char **argv,
       const char *sntpVersion
       )
{
       int                     i;
       int                     exitcode;
       int                     optct;
       struct event_config *   evcfg;

       /* Initialize logging system - sets up progname */
       sntp_init_logging(argv[0]);

       if (!libevent_version_ok())
               exit(EX_SOFTWARE);

       init_lib();
       init_auth();

       optct = ntpOptionProcess(&sntpOptions, argc, argv);
       argc -= optct;
       argv += optct;


       debug = OPT_VALUE_SET_DEBUG_LEVEL;

       TRACE(2, ("init_lib() done, %s%s\n",
                 (ipv4_works)
                     ? "ipv4_works "
                     : "",
                 (ipv6_works)
                     ? "ipv6_works "
                     : ""));
       ntpver = OPT_VALUE_NTPVERSION;
       steplimit = OPT_VALUE_STEPLIMIT / 1e3;
       gap.tv_usec = max(0, OPT_VALUE_GAP * 1000);
       gap.tv_usec = min(gap.tv_usec, 999999);

       if (HAVE_OPT(LOGFILE))
               open_logfile(OPT_ARG(LOGFILE));

       msyslog(LOG_INFO, "%s", sntpVersion);

       if (0 == argc && !HAVE_OPT(BROADCAST) && !HAVE_OPT(CONCURRENT)) {
               printf("%s: Must supply at least one of -b hostname, -c hostname, or hostname.\n",
                      progname);
               exit(EX_USAGE);
       }


       /*
       ** Eventually, we probably want:
       ** - separate bcst and ucst timeouts (why?)
       ** - multiple --timeout values in the commandline
       */

       response_timeout = OPT_VALUE_TIMEOUT;
       response_tv.tv_sec = response_timeout;
       response_tv.tv_usec = 0;

       /* IPv6 available? */
       if (isc_net_probeipv6() != ISC_R_SUCCESS) {
               ai_fam_pref = AF_INET;
               TRACE(1, ("No ipv6 support available, forcing ipv4\n"));
       } else {
               /* Check for options -4 and -6 */
               if (HAVE_OPT(IPV4))
                       ai_fam_pref = AF_INET;
               else if (HAVE_OPT(IPV6))
                       ai_fam_pref = AF_INET6;
       }

       /* TODO: Parse config file if declared */

       /*
       ** Init the KOD system.
       ** For embedded systems with no writable filesystem,
       ** -K /dev/null can be used to disable KoD storage.
       */
       kod_init_kod_db(OPT_ARG(KOD), FALSE);

       /* HMS: Check and see what happens if KEYFILE doesn't exist */
       auth_init(OPT_ARG(KEYFILE), &keys);

       /*
       ** Considering employing a variable that prevents functions of doing
       ** anything until everything is initialized properly
       **
       ** HMS: What exactly does the above mean?
       */
       event_set_log_callback(&sntp_libevent_log_cb);
       if (debug > 0)
               event_enable_debug_mode();
#ifdef WORK_THREAD
       evthread_use_pthreads();
       /* we use libevent from main thread only, locks should be academic */
       if (debug > 0)
               evthread_enable_lock_debuging();
#endif
       evcfg = event_config_new();
       if (NULL == evcfg) {
               printf("%s: event_config_new() failed!\n", progname);
               return -1;
       }
#ifndef HAVE_SOCKETPAIR
       event_config_require_features(evcfg, EV_FEATURE_FDS);
#endif
       /* all libevent calls are from main thread */
       /* event_config_set_flag(evcfg, EVENT_BASE_FLAG_NOLOCK); */
       base = event_base_new_with_config(evcfg);
       event_config_free(evcfg);
       if (NULL == base) {
               printf("%s: event_base_new() failed!\n", progname);
               return -1;
       }

       /* wire into intres resolver */
       worker_per_query = TRUE;
       addremove_io_fd = &sntp_addremove_fd;

       open_sockets();

       if (HAVE_OPT(BROADCAST)) {
               int             cn = STACKCT_OPT(  BROADCAST );
               const char **   cp = STACKLST_OPT( BROADCAST );

               while (cn-- > 0) {
                       handle_lookup(*cp, CTX_BCST);
                       cp++;
               }
       }

       if (HAVE_OPT(CONCURRENT)) {
               int             cn = STACKCT_OPT( CONCURRENT );
               const char **   cp = STACKLST_OPT( CONCURRENT );

               while (cn-- > 0) {
                       handle_lookup(*cp, CTX_UCST | CTX_CONC);
                       cp++;
               }
       }

       for (i = 0; i < argc; ++i)
               handle_lookup(argv[i], CTX_UCST);

       gettimeofday_cached(base, &start_tv);
       event_base_dispatch(base);
       event_base_free(base);

       if (!time_adjusted &&
           (ENABLED_OPT(STEP) || ENABLED_OPT(SLEW)))
               exitcode = 1;
       else
               exitcode = 0;

       return exitcode;
}


/*
** open sockets and make them non-blocking
*/
void
open_sockets(
       void
       )
{
       sockaddr_u      name;

       if (-1 == sock4) {
               sock4 = socket(PF_INET, SOCK_DGRAM, 0);
               if (-1 == sock4) {
                       /* error getting a socket */
                       msyslog(LOG_ERR, "open_sockets: socket(PF_INET) failed: %m");
                       exit(1);
               }
               /* Make it non-blocking */
               make_socket_nonblocking(sock4);

               /* Let's try using a wildcard... */
               ZERO(name);
               AF(&name) = AF_INET;
               SET_ADDR4N(&name, INADDR_ANY);
               SET_PORT(&name, (HAVE_OPT(USERESERVEDPORT) ? 123 : 0));

               if (-1 == bind(sock4, &name.sa,
                              SOCKLEN(&name))) {
                       msyslog(LOG_ERR, "open_sockets: bind(sock4) failed: %m");
                       exit(1);
               }

               /* Register an NTP callback for recv/timeout */
               ev_sock4 = event_new(base, sock4,
                                    EV_TIMEOUT | EV_READ | EV_PERSIST,
                                    &sock_cb, NULL);
               if (NULL == ev_sock4) {
                       msyslog(LOG_ERR,
                               "open_sockets: event_new(base, sock4) failed!");
               } else {
                       event_add(ev_sock4, &wakeup_tv);
               }
       }

       /* We may not always have IPv6... */
       if (-1 == sock6 && ipv6_works) {
               sock6 = socket(PF_INET6, SOCK_DGRAM, 0);
               if (-1 == sock6 && ipv6_works) {
                       /* error getting a socket */
                       msyslog(LOG_ERR, "open_sockets: socket(PF_INET6) failed: %m");
                       exit(1);
               }
               /* Make it non-blocking */
               make_socket_nonblocking(sock6);

               /* Let's try using a wildcard... */
               ZERO(name);
               AF(&name) = AF_INET6;
               SET_ADDR6N(&name, in6addr_any);
               SET_PORT(&name, (HAVE_OPT(USERESERVEDPORT) ? 123 : 0));

               if (-1 == bind(sock6, &name.sa,
                              SOCKLEN(&name))) {
                       msyslog(LOG_ERR, "open_sockets: bind(sock6) failed: %m");
                       exit(1);
               }
               /* Register an NTP callback for recv/timeout */
               ev_sock6 = event_new(base, sock6,
                                    EV_TIMEOUT | EV_READ | EV_PERSIST,
                                    &sock_cb, NULL);
               if (NULL == ev_sock6) {
                       msyslog(LOG_ERR,
                               "open_sockets: event_new(base, sock6) failed!");
               } else {
                       event_add(ev_sock6, &wakeup_tv);
               }
       }

       return;
}


/*
** handle_lookup
*/
void
handle_lookup(
       const char *name,
       int flags
       )
{
       struct addrinfo hints;  /* Local copy is OK */
       struct dns_ctx *ctx;
       char *          name_copy;
       size_t          name_sz;
       size_t          octets;

       TRACE(1, ("handle_lookup(%s,%#x)\n", name, flags));

       ZERO(hints);
       hints.ai_family = ai_fam_pref;
       hints.ai_flags = AI_CANONNAME | Z_AI_NUMERICSERV;
       /*
       ** Unless we specify a socktype, we'll get at least two
       ** entries for each address: one for TCP and one for
       ** UDP. That's not what we want.
       */
       hints.ai_socktype = SOCK_DGRAM;
       hints.ai_protocol = IPPROTO_UDP;

       name_sz = 1 + strlen(name);
       octets = sizeof(*ctx) + name_sz;        // Space for a ctx and the name
       ctx = emalloc_zero(octets);             // ctx at ctx[0]
       name_copy = (char *)(ctx + 1);          // Put the name at ctx[1]
       memcpy(name_copy, name, name_sz);       // copy the name to ctx[1]
       ctx->name = name_copy;                  // point to it...
       ctx->flags = flags;
       ctx->timeout = response_tv;
       ctx->key = NULL;

       /* The following should arguably be passed in... */
       if (ENABLED_OPT(AUTHENTICATION)) {
               ctx->key_id = OPT_VALUE_AUTHENTICATION;
               get_key(ctx->key_id, &ctx->key);
               if (NULL == ctx->key) {
                       fprintf(stderr, "%s: Authentication with keyID %d requested, but no matching keyID found in <%s>!\n",
                               progname, ctx->key_id, OPT_ARG(KEYFILE));
                       exit(1);
               }
       } else {
               ctx->key_id = -1;
       }

       ++n_pending_dns;
       getaddrinfo_sometime(name, "123", &hints, 0,
                            &sntp_name_resolved, ctx);
}


/*
** DNS Callback:
** - For each IP:
** - - open a socket
** - - increment n_pending_ntp
** - - send a request if this is a Unicast callback
** - - queue wait for response
** - decrement n_pending_dns
*/
void
sntp_name_resolved(
       int                     rescode,
       int                     gai_errno,
       void *                  context,
       const char *            name,
       const char *            service,
       const struct addrinfo * hints,
       const struct addrinfo * addr
       )
{
       struct dns_ctx *        dctx;
       sent_pkt *              spkt;
       const struct addrinfo * ai;
       SOCKET                  sock;
       u_int                   xmt_delay_v4;
       u_int                   xmt_delay_v6;
       u_int                   xmt_delay;
       size_t                  octets;

       xmt_delay_v4 = 0;
       xmt_delay_v6 = 0;
       dctx = context;
       if (rescode) {
#ifdef EAI_SYSTEM
               if (EAI_SYSTEM == rescode) {
                       errno = gai_errno;
                       mfprintf(stderr, "%s lookup error %m\n",
                                dctx->name);
               } else
#endif
                       fprintf(stderr, "%s lookup error %s\n",
                               dctx->name, gai_strerror(rescode));
       } else {
               TRACE(3, ("%s [%s]\n", dctx->name,
                         (addr->ai_canonname != NULL)
                             ? addr->ai_canonname
                             : ""));

               for (ai = addr; ai != NULL; ai = ai->ai_next) {

                       if (check_kod(ai))
                               continue;

                       switch (ai->ai_family) {

                       case AF_INET:
                               sock = sock4;
                               xmt_delay = xmt_delay_v4;
                               xmt_delay_v4++;
                               break;

                       case AF_INET6:
                               if (!ipv6_works)
                                       continue;

                               sock = sock6;
                               xmt_delay = xmt_delay_v6;
                               xmt_delay_v6++;
                               break;

                       default:
                               msyslog(LOG_ERR, "sntp_name_resolved: unexpected ai_family: %d",
                                       ai->ai_family);
                               exit(1);
                               break;
                       }

                       /*
                       ** We're waiting for a response for either unicast
                       ** or broadcast, so...
                       */
                       ++n_pending_ntp;

                       /* If this is for a unicast IP, queue a request */
                       if (dctx->flags & CTX_UCST) {
                               spkt = emalloc_zero(sizeof(*spkt));
                               spkt->dctx = dctx;
                               octets = min(ai->ai_addrlen, sizeof(spkt->addr));
                               memcpy(&spkt->addr, ai->ai_addr, octets);
                               queue_xmt(sock, dctx, spkt, xmt_delay);
                       }
               }
       }
       /* n_pending_dns really should be >0 here... */
       --n_pending_dns;
       check_exit_conditions();
}


/*
** queue_xmt
*/
void
queue_xmt(
       SOCKET                  sock,
       struct dns_ctx *        dctx,
       sent_pkt *              spkt,
       u_int                   xmt_delay
       )
{
       sockaddr_u *    dest;
       sent_pkt **     pkt_listp;
       sent_pkt *      match;
       xmt_ctx *       xctx;
       struct timeval  start_cb;
       struct timeval  delay;

       dest = &spkt->addr;
       if (IS_IPV6(dest))
               pkt_listp = &v6_pkts_list;
       else
               pkt_listp = &v4_pkts_list;

       /* reject attempts to add address already listed */
       for (match = *pkt_listp; match != NULL; match = match->link) {
               if (ADDR_PORT_EQ(&spkt->addr, &match->addr)) {
                       if (strcasecmp(spkt->dctx->name,
                                      match->dctx->name))
                               printf("%s %s duplicate address from %s ignored.\n",
                                      sptoa(&match->addr),
                                      match->dctx->name,
                                      spkt->dctx->name);
                       else
                               printf("%s %s, duplicate address ignored.\n",
                                      sptoa(&match->addr),
                                      match->dctx->name);
                       dec_pending_ntp(spkt->dctx->name, &spkt->addr);
                       free(spkt);
                       return;
               }
       }

       LINK_SLIST(*pkt_listp, spkt, link);

       xctx = emalloc_zero(sizeof(*xctx));
       xctx->sock = sock;
       xctx->spkt = spkt;
       gettimeofday_cached(base, &start_cb);
       xctx->sched = start_cb.tv_sec + (2 * xmt_delay);

       LINK_SORT_SLIST(xmt_q, xctx, (xctx->sched < L_S_S_CUR()->sched),
                       link, xmt_ctx);
       if (xmt_q == xctx) {
               /*
                * The new entry is the first scheduled.  The timer is
                * either not active or is set for the second xmt
                * context in xmt_q.
                */
               if (NULL == ev_xmt_timer)
                       ev_xmt_timer = event_new(base, INVALID_SOCKET,
                                                EV_TIMEOUT,
                                                &xmt_timer_cb, NULL);
               if (NULL == ev_xmt_timer) {
                       msyslog(LOG_ERR,
                               "queue_xmt: event_new(base, -1, EV_TIMEOUT) failed!");
                       exit(1);
               }
               ZERO(delay);
               if (xctx->sched > start_cb.tv_sec)
                       delay.tv_sec = xctx->sched - start_cb.tv_sec;
               event_add(ev_xmt_timer, &delay);
               TRACE(2, ("queue_xmt: xmt timer for %u usec\n",
                         (u_int)delay.tv_usec));
       }
}


/*
** xmt_timer_cb
*/
void
xmt_timer_cb(
       evutil_socket_t fd,
       short           what,
       void *          ctx
       )
{
       struct timeval  start_cb;
       struct timeval  delay;
       xmt_ctx *       x;

       UNUSED_ARG(fd);
       UNUSED_ARG(ctx);
       DEBUG_INSIST(EV_TIMEOUT == what);

       if (NULL == xmt_q || shutting_down)
               return;
       gettimeofday_cached(base, &start_cb);
       if (xmt_q->sched <= start_cb.tv_sec) {
               UNLINK_HEAD_SLIST(x, xmt_q, link);
               TRACE(2, ("xmt_timer_cb: at .%6.6u -> %s\n",
                         (u_int)start_cb.tv_usec, stoa(&x->spkt->addr)));
               xmt(x);
               free(x);
               if (NULL == xmt_q)
                       return;
       }
       if (xmt_q->sched <= start_cb.tv_sec) {
               event_add(ev_xmt_timer, &gap);
               TRACE(2, ("xmt_timer_cb: at .%6.6u gap %6.6u\n",
                         (u_int)start_cb.tv_usec,
                         (u_int)gap.tv_usec));
       } else {
               delay.tv_sec = xmt_q->sched - start_cb.tv_sec;
               delay.tv_usec = 0;
               event_add(ev_xmt_timer, &delay);
               TRACE(2, ("xmt_timer_cb: at .%6.6u next %ld seconds\n",
                         (u_int)start_cb.tv_usec,
                         (long)delay.tv_sec));
       }
}


/*
** xmt()
*/
void
xmt(
       xmt_ctx *       xctx
       )
{
       SOCKET          sock = xctx->sock;
       struct dns_ctx *dctx = xctx->spkt->dctx;
       sent_pkt *      spkt = xctx->spkt;
       sockaddr_u *    dst = &spkt->addr;
       struct timeval  tv_xmt;
       struct pkt      x_pkt;
       size_t          pkt_len;
       int             sent;

       if (0 != gettimeofday(&tv_xmt, NULL)) {
               msyslog(LOG_ERR,
                       "xmt: gettimeofday() failed: %m");
               exit(1);
       }
       tv_xmt.tv_sec += JAN_1970;

       pkt_len = generate_pkt(&x_pkt, &tv_xmt, dctx->key_id,
                              dctx->key);

       sent = sendpkt(sock, dst, &x_pkt, pkt_len);
       if (sent) {
               /* Save the packet we sent... */
               memcpy(&spkt->x_pkt, &x_pkt, min(sizeof(spkt->x_pkt),
                      pkt_len));
               spkt->stime = tv_xmt.tv_sec - JAN_1970;

               TRACE(2, ("xmt: %lx.%6.6u %s %s\n", (u_long)tv_xmt.tv_sec,
                         (u_int)tv_xmt.tv_usec, dctx->name, stoa(dst)));
       } else {
               dec_pending_ntp(dctx->name, dst);
       }

       return;
}


/*
* timeout_queries() -- give up on unrequited NTP queries
*/
void
timeout_queries(void)
{
       struct timeval  start_cb;
       u_int           idx;
       sent_pkt *      head;
       sent_pkt *      spkt;
       sent_pkt *      spkt_next;
       long            age;
       int didsomething = 0;

       TRACE(3, ("timeout_queries: called to check %u items\n",
                 (unsigned)COUNTOF(fam_listheads)));

       gettimeofday_cached(base, &start_cb);
       for (idx = 0; idx < COUNTOF(fam_listheads); idx++) {
               head = fam_listheads[idx];
               for (spkt = head; spkt != NULL; spkt = spkt_next) {
                       char xcst;

                       didsomething = 1;
                       switch (spkt->dctx->flags & CTX_xCST) {
                           case CTX_BCST:
                               xcst = 'B';
                               break;

                           case CTX_UCST:
                               xcst = 'U';
                               break;

                           default:
                               INSIST(!"spkt->dctx->flags neither UCST nor BCST");
                               break;
                       }

                       spkt_next = spkt->link;
                       if (0 == spkt->stime || spkt->done)
                               continue;
                       age = start_cb.tv_sec - spkt->stime;
                       TRACE(3, ("%s %s %cCST age %ld\n",
                                 stoa(&spkt->addr),
                                 spkt->dctx->name, xcst, age));
                       if (age > response_timeout)
                               timeout_query(spkt);
               }
       }
       // Do we care about didsomething?
       TRACE(3, ("timeout_queries: didsomething is %d, age is %ld\n",
                 didsomething, (long) (start_cb.tv_sec - start_tv.tv_sec)));
       if (start_cb.tv_sec - start_tv.tv_sec > response_timeout) {
               TRACE(3, ("timeout_queries: bail!\n"));
               event_base_loopexit(base, NULL);
               shutting_down = TRUE;
       }
}


void dec_pending_ntp(
       const char *    name,
       sockaddr_u *    server
       )
{
       if (n_pending_ntp > 0) {
               --n_pending_ntp;
               check_exit_conditions();
       } else {
               INSIST(0 == n_pending_ntp);
               TRACE(1, ("n_pending_ntp was zero before decrement for %s\n",
                         hostnameaddr(name, server)));
       }
}


void timeout_query(
       sent_pkt *      spkt
       )
{
       sockaddr_u *    server;
       char            xcst;


       switch (spkt->dctx->flags & CTX_xCST) {
           case CTX_BCST:
               xcst = 'B';
               break;

           case CTX_UCST:
               xcst = 'U';
               break;

           default:
               INSIST(!"spkt->dctx->flags neither UCST nor BCST");
               break;
       }
       spkt->done = TRUE;
       server = &spkt->addr;
       msyslog(LOG_INFO, "%s no %cCST response after %d seconds",
               hostnameaddr(spkt->dctx->name, server), xcst,
               response_timeout);
       dec_pending_ntp(spkt->dctx->name, server);
       return;
}


/*
** check_kod
*/
int
check_kod(
       const struct addrinfo * ai
       )
{
       char *hostname;
       struct kod_entry *reason;

       /* Is there a KoD on file for this address? */
       hostname = addrinfo_to_str(ai);
       TRACE(2, ("check_kod: checking <%s>\n", hostname));
       if (search_entry(hostname, &reason)) {
               printf("prior KoD for %s, skipping.\n",
                       hostname);
               free(reason);
               free(hostname);

               return 1;
       }
       free(hostname);

       return 0;
}


/*
** Socket readable/timeout Callback:
** Read in the packet
** Unicast:
** - close socket
** - decrement n_pending_ntp
** - If packet is good, set the time and "exit"
** Broadcast:
** - If packet is good, set the time and "exit"
*/
void
sock_cb(
       evutil_socket_t fd,
       short what,
       void *ptr
       )
{
       sockaddr_u      sender;
       sockaddr_u *    psau;
       sent_pkt **     p_pktlist;
       sent_pkt *      spkt;
       int             rpktl;
       int             rc;

       INSIST(sock4 == fd || sock6 == fd);

       TRACE(3, ("sock_cb: event on sock%s:%s%s%s%s\n",
                 (fd == sock6)
                     ? "6"
                     : "4",
                 (what & EV_TIMEOUT) ? " timeout" : "",
                 (what & EV_READ)    ? " read" : "",
                 (what & EV_WRITE)   ? " write" : "",
                 (what & EV_SIGNAL)  ? " signal" : ""));

       if (!(EV_READ & what)) {
               if (EV_TIMEOUT & what)
                       timeout_queries();

               return;
       }

       /* Read in the packet */
       rpktl = recvdata(fd, &sender, &rbuf, sizeof(rbuf));
       if (rpktl < 0) {
               msyslog(LOG_DEBUG, "recvfrom error %m");
               return;
       }

       if (sock6 == fd)
               p_pktlist = &v6_pkts_list;
       else
               p_pktlist = &v4_pkts_list;

       for (spkt = *p_pktlist; spkt != NULL; spkt = spkt->link) {
               psau = &spkt->addr;
               if (SOCK_EQ(&sender, psau))
                       break;
       }
       if (NULL == spkt) {
               msyslog(LOG_WARNING,
                       "Packet from unexpected source %s dropped",
                       sptoa(&sender));
               return;
       }

       TRACE(1, ("sock_cb: %s %s\n", spkt->dctx->name,
                 sptoa(&sender)));

       rpktl = process_pkt(&r_pkt, &sender, rpktl, MODE_SERVER,
                           &spkt->x_pkt, "sock_cb");

       TRACE(2, ("sock_cb: process_pkt returned %d\n", rpktl));

       /* If this is a Unicast packet, one down ... */
       if (!spkt->done && (CTX_UCST & spkt->dctx->flags)) {
               dec_pending_ntp(spkt->dctx->name, &spkt->addr);
               spkt->done = TRUE;
       }


       /* If the packet is good, set the time and we're all done */
       rc = handle_pkt(rpktl, &r_pkt, &spkt->addr, spkt->dctx->name);
       if (0 != rc)
               TRACE(1, ("sock_cb: handle_pkt() returned %d\n", rc));
       check_exit_conditions();
}


/*
* check_exit_conditions()
*
* If sntp has a reply, ask the event loop to stop after this round of
* callbacks, unless --wait was used.
*/
void
check_exit_conditions(void)
{
       if ((0 == n_pending_ntp && 0 == n_pending_dns) ||
           (time_derived && !HAVE_OPT(WAIT))) {
               event_base_loopexit(base, NULL);
               shutting_down = TRUE;
       } else {
               TRACE(2, ("%d NTP and %d name queries pending\n",
                         n_pending_ntp, n_pending_dns));
       }
}


/*
* sntp_addremove_fd() is invoked by the intres blocking worker code
* to read from a pipe, or to stop same.
*/
void sntp_addremove_fd(
       int     fd,
       int     is_pipe,
       int     remove_it
       )
{
       u_int           idx;
       blocking_child *c;
       struct event *  ev;

#ifdef HAVE_SOCKETPAIR
       if (is_pipe) {
               /* sntp only asks for EV_FEATURE_FDS without HAVE_SOCKETPAIR */
               msyslog(LOG_ERR, "fatal: pipes not supported on systems with socketpair()");
               exit(1);
       }
#endif

       c = NULL;
       for (idx = 0; idx < blocking_children_alloc; idx++) {
               c = blocking_children[idx];
               if (NULL == c)
                       continue;
               if (fd == c->resp_read_pipe)
                       break;
       }
       if (idx == blocking_children_alloc)
               return;

       if (remove_it) {
               ev = c->resp_read_ctx;
               c->resp_read_ctx = NULL;
               event_del(ev);
               event_free(ev);

               return;
       }

       ev = event_new(base, fd, EV_READ | EV_PERSIST,
                      &worker_resp_cb, c);
       if (NULL == ev) {
               msyslog(LOG_ERR,
                       "sntp_addremove_fd: event_new(base, fd) failed!");
               return;
       }
       c->resp_read_ctx = ev;
       event_add(ev, NULL);
}


/* called by forked intres child to close open descriptors */
#ifdef WORK_FORK
void
kill_asyncio(
       int     startfd
       )
{
       if (INVALID_SOCKET != sock4) {
               closesocket(sock4);
               sock4 = INVALID_SOCKET;
       }
       if (INVALID_SOCKET != sock6) {
               closesocket(sock6);
               sock6 = INVALID_SOCKET;
       }
       if (INVALID_SOCKET != bsock4) {
               closesocket(bsock4);
               bsock4 = INVALID_SOCKET;
       }
       if (INVALID_SOCKET != bsock6) {
               closesocket(bsock6);
               bsock6 = INVALID_SOCKET;
       }
}
#endif


/*
* worker_resp_cb() is invoked when resp_read_pipe is readable.
*/
void
worker_resp_cb(
       evutil_socket_t fd,
       short           what,
       void *          ctx     /* blocking_child * */
       )
{
       blocking_child *        c;

       REQUIRE(EV_READ & what);
       c = ctx;
       INSIST(fd == c->resp_read_pipe);
       process_blocking_resp(c);
}


/*
* intres_timeout_req(s) is invoked in the parent to schedule an idle
* timeout to fire in s seconds, if not reset earlier by a call to
* intres_timeout_req(0), which clears any pending timeout.  When the
* timeout expires, worker_idle_timer_fired() is invoked (again, in the
* parent).
*
* sntp and ntpd each provide implementations adapted to their timers.
*/
void
intres_timeout_req(
       u_int   seconds         /* 0 cancels */
       )
{
       struct timeval  tv_to;

       if (NULL == ev_worker_timeout) {
               ev_worker_timeout = event_new(base, -1,
                                             EV_TIMEOUT | EV_PERSIST,
                                             &worker_timeout, NULL);
               INSIST(NULL != ev_worker_timeout);
       } else {
               event_del(ev_worker_timeout);
       }
       if (0 == seconds)
               return;
       tv_to.tv_sec = seconds;
       tv_to.tv_usec = 0;
       event_add(ev_worker_timeout, &tv_to);
}


void
worker_timeout(
       evutil_socket_t fd,
       short           what,
       void *          ctx
       )
{
       UNUSED_ARG(fd);
       UNUSED_ARG(ctx);

       REQUIRE(EV_TIMEOUT & what);
       worker_idle_timer_fired();
}


void
sntp_libevent_log_cb(
       int             severity,
       const char *    msg
       )
{
       int             level;

       switch (severity) {

       default:
       case _EVENT_LOG_DEBUG:
               level = LOG_DEBUG;
               break;

       case _EVENT_LOG_MSG:
               level = LOG_NOTICE;
               break;

       case _EVENT_LOG_WARN:
               level = LOG_WARNING;
               break;

       case _EVENT_LOG_ERR:
               level = LOG_ERR;
               break;
       }

       msyslog(level, "%s", msg);
}


int
generate_pkt (
       struct pkt *x_pkt,
       const struct timeval *tv_xmt,
       int key_id,
       struct key *pkt_key
       )
{
       l_fp    xmt_fp;
       int     pkt_len;
       int     mac_size;

       pkt_len = LEN_PKT_NOMAC;
       ZERO(*x_pkt);
       TVTOTS(tv_xmt, &xmt_fp);
       HTONL_FP(&xmt_fp, &x_pkt->xmt);
       x_pkt->stratum = STRATUM_TO_PKT(STRATUM_UNSPEC);
       x_pkt->ppoll = 8;
       /* FIXME! Modus broadcast + adr. check -> bdr. pkt */
       set_li_vn_mode(x_pkt, LEAP_NOTINSYNC, ntpver, 3);
       if (debug > 0) {
               printf("generate_pkt: key_id %d, key pointer %p\n", key_id, pkt_key);
       }
       if (pkt_key != NULL) {
               x_pkt->exten[0] = htonl(key_id);
               mac_size = make_mac(x_pkt, pkt_len, pkt_key,
                                   (char *)&x_pkt->exten[1], MAX_MDG_LEN);
               if (mac_size > 0)
                       pkt_len += mac_size + KEY_MAC_LEN;
#ifdef DEBUG
               if (debug > 0) {
                       printf("generate_pkt: mac_size is %d\n", mac_size);
               }
#endif

       }
       return pkt_len;
}


int
handle_pkt(
       int             rpktl,
       struct pkt *    rpkt,
       sockaddr_u *    host,
       const char *    hostname
       )
{
       char            disptxt[32];
       const char *    addrtxt;
       struct timeval  tv_dst;
       int             cnt;
       int             sw_case;
       int             digits;
       int             stratum;
       char *          ref;
       char *          ts_str;
       const char *    leaptxt;
       double          offset;
       double          precision;
       double          synch_distance;
       char *          p_SNTP_PRETEND_TIME;
       time_t          pretend_time;
#if SIZEOF_TIME_T == 8
       long long       ll;
#else
       long            l;
#endif

       ts_str = NULL;

       if (rpktl > 0)
               sw_case = 1;
       else
               sw_case = rpktl;

       switch (sw_case) {

       case SERVER_UNUSEABLE:
               return -1;
               break;

       case PACKET_UNUSEABLE:
               break;

       case SERVER_AUTH_FAIL:
               break;

       case KOD_DEMOBILIZE:
               /* Received a DENY or RESTR KOD packet */
               addrtxt = stoa(host);
               ref = (char *)&rpkt->refid;
               add_entry(addrtxt, ref);
               msyslog(LOG_WARNING, "KOD code %c%c%c%c from %s %s",
                       ref[0], ref[1], ref[2], ref[3], addrtxt, hostname);
               break;

       case KOD_RATE:
               /*
               ** Hmm...
               ** We should probably call add_entry() with an
               ** expiration timestamp of several seconds in the future,
               ** and back-off even more if we get more RATE responses.
               */
               break;

       case 1:
               TRACE(3, ("handle_pkt: %d bytes from %s %s\n",
                         rpktl, stoa(host), hostname));

               gettimeofday_cached(base, &tv_dst);

               p_SNTP_PRETEND_TIME = getenv("SNTP_PRETEND_TIME");
               if (p_SNTP_PRETEND_TIME) {
                       pretend_time = 0;
#if SIZEOF_TIME_T == 4
                       if (1 == sscanf(p_SNTP_PRETEND_TIME, "%ld", &l))
                               pretend_time = (time_t)l;
#elif SIZEOF_TIME_T == 8
                       if (1 == sscanf(p_SNTP_PRETEND_TIME, "%lld", &ll))
                               pretend_time = (time_t)ll;
#else
# include "GRONK: unexpected value for SIZEOF_TIME_T"
#endif
                       if (0 != pretend_time)
                               tv_dst.tv_sec = pretend_time;
               }

               offset_calculation(rpkt, rpktl, &tv_dst, &offset,
                                  &precision, &synch_distance);
               time_derived = TRUE;

               for (digits = 0; (precision *= 10.) < 1.; ++digits)
                       /* empty */ ;
               if (digits > 6)
                       digits = 6;

               ts_str = tv_to_str(&tv_dst);
               stratum = rpkt->stratum;
               if (0 == stratum)
                               stratum = 16;

               if (synch_distance > 0.) {
                       cnt = snprintf(disptxt, sizeof(disptxt),
                                      " +/- %f", synch_distance);
                       if ((size_t)cnt >= sizeof(disptxt))
                               snprintf(disptxt, sizeof(disptxt),
                                        "ERROR %d >= %d", cnt,
                                        (int)sizeof(disptxt));
               } else {
                       disptxt[0] = '\0';
               }

               switch (PKT_LEAP(rpkt->li_vn_mode)) {
                   case LEAP_NOWARNING:
                       leaptxt = "no-leap";
                       break;
                   case LEAP_ADDSECOND:
                       leaptxt = "add-leap";
                       break;
                   case LEAP_DELSECOND:
                       leaptxt = "del-leap";
                       break;
                   case LEAP_NOTINSYNC:
                       leaptxt = "unsync";
                       break;
                   default:
                       leaptxt = "LEAP-ERROR";
                       break;
               }

               msyslog(LOG_INFO, "%s %+.*f%s %s s%d %s%s", ts_str,
                       digits, offset, disptxt,
                       hostnameaddr(hostname, host), stratum,
                       leaptxt,
                       (time_adjusted)
                           ? " [excess]"
                           : "");
               free(ts_str);

               if (p_SNTP_PRETEND_TIME)
                       return 0;

               if (!time_adjusted &&
                   (ENABLED_OPT(STEP) || ENABLED_OPT(SLEW)))
                       return set_time(offset);

               return EX_OK;
       }

       return 1;
}


void
offset_calculation(
       struct pkt *rpkt,
       int rpktl,
       struct timeval *tv_dst,
       double *offset,
       double *precision,
       double *synch_distance
       )
{
       l_fp p_rec, p_xmt, p_ref, p_org, tmp, dst;
       u_fp p_rdly, p_rdsp;
       double t21, t34, delta;

       /* Convert timestamps from network to host byte order */
       p_rdly = NTOHS_FP(rpkt->rootdelay);
       p_rdsp = NTOHS_FP(rpkt->rootdisp);
       NTOHL_FP(&rpkt->reftime, &p_ref);
       NTOHL_FP(&rpkt->org, &p_org);
       NTOHL_FP(&rpkt->rec, &p_rec);
       NTOHL_FP(&rpkt->xmt, &p_xmt);

       *precision = LOGTOD(rpkt->precision);

       TRACE(3, ("offset_calculation: LOGTOD(rpkt->precision): %f\n", *precision));

       /* Compute offset etc. */
       tmp = p_rec;
       L_SUB(&tmp, &p_org);
       LFPTOD(&tmp, t21);
       TVTOTS(tv_dst, &dst);
       dst.l_ui += JAN_1970;
       tmp = p_xmt;
       L_SUB(&tmp, &dst);
       LFPTOD(&tmp, t34);
       *offset = (t21 + t34) / 2.;
       delta = t21 - t34;

       // synch_distance is:
       // (peer->delay + peer->rootdelay) / 2 + peer->disp
       // + peer->rootdisp + clock_phi * (current_time - peer->update)
       // + peer->jitter;
       //
       // and peer->delay = fabs(peer->offset - p_offset) * 2;
       // and peer->offset needs history, so we're left with
       // p_offset = (t21 + t34) / 2.;
       // peer->disp = 0; (we have no history to augment this)
       // clock_phi = 15e-6;
       // peer->jitter = LOGTOD(sys_precision); (we have no history to augment this)
       // and ntp_proto.c:set_sys_tick_precision() should get us sys_precision.
       //
       // so our answer seems to be:
       //
       // (fabs(t21 + t34) + peer->rootdelay) / 3.
       // + 0 (peer->disp)
       // + peer->rootdisp
       // + 15e-6 (clock_phi)
       // + LOGTOD(sys_precision)

       INSIST( FPTOD(p_rdly) >= 0. );
#if 1
       *synch_distance = (fabs(t21 + t34) + FPTOD(p_rdly)) / 3.
               + 0.
               + FPTOD(p_rdsp)
               + 15e-6
               + 0.    /* LOGTOD(sys_precision) when we can get it */
               ;
       INSIST( *synch_distance >= 0. );
#else
       *synch_distance = (FPTOD(p_rdly) + FPTOD(p_rdsp))/2.0;
#endif

#ifdef DEBUG
       if (debug > 3) {
               printf("sntp rootdelay: %f\n", FPTOD(p_rdly));
               printf("sntp rootdisp: %f\n", FPTOD(p_rdsp));
               printf("sntp syncdist: %f\n", *synch_distance);

               pkt_output(rpkt, rpktl, stdout);

               printf("sntp offset_calculation: rpkt->reftime:\n");
               l_fp_output(&p_ref, stdout);
               printf("sntp offset_calculation: rpkt->org:\n");
               l_fp_output(&p_org, stdout);
               printf("sntp offset_calculation: rpkt->rec:\n");
               l_fp_output(&p_rec, stdout);
               printf("sntp offset_calculation: rpkt->xmt:\n");
               l_fp_output(&p_xmt, stdout);
       }
#endif

       TRACE(3, ("sntp offset_calculation:\trec - org t21: %.6f\n"
                 "\txmt - dst t34: %.6f\tdelta: %.6f\toffset: %.6f\n",
                 t21, t34, delta, *offset));

       return;
}



/* Compute the 8 bits for li_vn_mode */
void
set_li_vn_mode (
       struct pkt *spkt,
       char leap,
       char version,
       char mode
       )
{
       if (leap > 3) {
               msyslog(LOG_DEBUG, "set_li_vn_mode: leap > 3, using max. 3");
               leap = 3;
       }

       if ((unsigned char)version > 7) {
               msyslog(LOG_DEBUG, "set_li_vn_mode: version < 0 or > 7, using 4");
               version = 4;
       }

       if (mode > 7) {
               msyslog(LOG_DEBUG, "set_li_vn_mode: mode > 7, using client mode 3");
               mode = 3;
       }

       spkt->li_vn_mode  = leap << 6;
       spkt->li_vn_mode |= version << 3;
       spkt->li_vn_mode |= mode;
}


/*
** set_time applies 'offset' to the local clock.
*/
int
set_time(
       double offset
       )
{
       int rc;

       if (time_adjusted)
               return EX_OK;

       /*
       ** If we can step but we cannot slew, then step.
       ** If we can step or slew and and |offset| > steplimit, then step.
       */
       if (ENABLED_OPT(STEP) &&
           (   !ENABLED_OPT(SLEW)
            || (ENABLED_OPT(SLEW) && (fabs(offset) > steplimit))
           )) {
               rc = step_systime(offset);

               /* If there was a problem, can we rely on errno? */
               if (1 == rc)
                       time_adjusted = TRUE;
               return (time_adjusted)
                          ? EX_OK
                          : 1;
               /*
               ** In case of error, what should we use?
               ** EX_UNAVAILABLE?
               ** EX_OSERR?
               ** EX_NOPERM?
               */
       }

       if (ENABLED_OPT(SLEW)) {
               rc = adj_systime(offset);

               /* If there was a problem, can we rely on errno? */
               if (1 == rc)
                       time_adjusted = TRUE;
               return (time_adjusted)
                          ? EX_OK
                          : 1;
               /*
               ** In case of error, what should we use?
               ** EX_UNAVAILABLE?
               ** EX_OSERR?
               ** EX_NOPERM?
               */
       }

       return EX_SOFTWARE;
}


int
libevent_version_ok(void)
{
       ev_uint32_t v_compile_maj;
       ev_uint32_t v_run_maj;

       v_compile_maj = LIBEVENT_VERSION_NUMBER & 0xffff0000;
       v_run_maj = event_get_version_number() & 0xffff0000;
       if (v_compile_maj != v_run_maj) {
               fprintf(stderr,
                       "Incompatible libevent versions: have %s, built with %s\n",
                       event_get_version(),
                       LIBEVENT_VERSION);
               return 0;
       }
       return 1;
}

/*
* gettimeofday_cached()
*
* Clones the event_base_gettimeofday_cached() interface but ensures the
* times are always on the gettimeofday() 1970 scale.  Older libevent 2
* sometimes used gettimeofday(), sometimes the since-system-start
* clock_gettime(CLOCK_MONOTONIC), depending on the platform.
*
* It is not cleanly possible to tell which timescale older libevent is
* using.
*
* The strategy involves 1 hour thresholds chosen to be far longer than
* the duration of a round of libevent callbacks, which share a cached
* start-of-round time.  First compare the last cached time with the
* current gettimeofday() time.  If they are within one hour, libevent
* is using the proper timescale so leave the offset 0.  Otherwise,
* compare libevent's cached time and the current time on the monotonic
* scale.  If they are within an hour, libevent is using the monotonic
* scale so calculate the offset to add to such times to bring them to
* gettimeofday()'s scale.
*/
int
gettimeofday_cached(
       struct event_base *     b,
       struct timeval *        caller_tv
       )
{
#if defined(_EVENT_HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
       static struct event_base *      cached_b;
       static struct timeval           cached;
       static struct timeval           adj_cached;
       static struct timeval           offset;
       static int                      offset_ready;
       struct timeval                  latest;
       struct timeval                  systemt;
       struct timespec                 ts;
       struct timeval                  mono;
       struct timeval                  diff;
       int                             cgt_rc;
       int                             gtod_rc;

       event_base_gettimeofday_cached(b, &latest);
       if (b == cached_b &&
           !memcmp(&latest, &cached, sizeof(latest))) {
               *caller_tv = adj_cached;
               return 0;
       }
       cached = latest;
       cached_b = b;
       if (!offset_ready) {
               cgt_rc = clock_gettime(CLOCK_MONOTONIC, &ts);
               gtod_rc = gettimeofday(&systemt, NULL);
               if (0 != gtod_rc) {
                       msyslog(LOG_ERR,
                               "%s: gettimeofday() error %m",
                               progname);
                       exit(1);
               }
               diff = sub_tval(systemt, latest);
               if (debug > 1)
                       printf("system minus cached %+ld.%06ld\n",
                              (long)diff.tv_sec, (long)diff.tv_usec);
               if (0 != cgt_rc || labs((long)diff.tv_sec) < 3600) {
                       /*
                        * Either use_monotonic == 0, or this libevent
                        * has been repaired.  Leave offset at zero.
                        */
               } else {
                       mono.tv_sec = ts.tv_sec;
                       mono.tv_usec = ts.tv_nsec / 1000;
                       diff = sub_tval(latest, mono);
                       if (debug > 1)
                               printf("cached minus monotonic %+ld.%06ld\n",
                                      (long)diff.tv_sec, (long)diff.tv_usec);
                       if (labs((long)diff.tv_sec) < 3600) {
                               /* older libevent2 using monotonic */
                               offset = sub_tval(systemt, mono);
                               TRACE(1, ("%s: Offsetting libevent CLOCK_MONOTONIC times  by %+ld.%06ld\n",
                                        "gettimeofday_cached",
                                        (long)offset.tv_sec,
                                        (long)offset.tv_usec));
                       }
               }
               offset_ready = TRUE;
       }
       adj_cached = add_tval(cached, offset);
       *caller_tv = adj_cached;

       return 0;
#else
       return event_base_gettimeofday_cached(b, caller_tv);
#endif
}

/* Dummy function to satisfy libntp/work_fork.c */
extern int set_user_group_ids(void);
int set_user_group_ids(void)
{
   return 1;
}