/*
 * tlsdate_status.c - handles tlsdate-monitor responses
 * Copyright (c) 2013 The Chromium Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "config.h"

#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <event2/event.h>

#include "src/conf.h"
#include "src/util.h"
#include "src/tlsdate.h"

/* Returns < 0 on error, > 0 on eagain, and 0 on success */
int
read_tlsdate_response (int fd, time_t *t)
{
  /* TLS passes time as a 32-bit value. */
  uint32_t server_time = 0;
  ssize_t ret = IGNORE_EINTR (read (fd, &server_time, sizeof (server_time)));
  if (ret == -1 && errno == EAGAIN)
    {
      /* Full response isn't ready yet. */
      return 1;
    }
  if (ret != sizeof (server_time))
    {
      /* End of pipe (0) or truncated: death probable. */
      error ("[event:(%s)] invalid time read from tlsdate (rd:%d,ret:%zd).",
             __func__, server_time, ret);
      return -1;
    }
  /* uint32_t moves to signed long so there is room for silliness. */
  *t = server_time;
  return 0;
}

void
action_tlsdate_timeout (evutil_socket_t fd, short what, void *arg)
{
  struct state *state = arg;
  info ("[event:%s] tlsdate timed out", __func__);
  /* Force kill it and let action_sigchld rerun. */
  if (state->tlsdate_pid)
    kill (state->tlsdate_pid, SIGKILL);
}

void
action_tlsdate_status (evutil_socket_t fd, short what, void *arg)
{
  struct state *state = arg;
  time_t t = 0;
  int ret = read_tlsdate_response (fd, &t);
  verb_debug ("[event:%s] fired", __func__);
  if (ret < 0)
    {
      verb_debug ("[event:%s] forcibly timing out tlsdate", __func__);
      trigger_event (state, E_TLSDATE_TIMEOUT, 0);
      return;
    }
  if (ret)
    {
      /* EAGAIN'd: wait for the rest. */
      trigger_event (state, E_TLSDATE_STATUS, -1);
      return;
    }
  if (is_sane_time (t))
    {
      /* Note that last_time is from an online source */
      state->last_sync_type = SYNC_TYPE_NET;
      state->last_time = t;
      trigger_event (state, E_SAVE, -1);
    }
  else
    {
      error ("[event:%s] invalid time received from tlsdate: %ld",
             __func__, t);
    }
  /* Restore the backoff and tries count on success, insane or not.
   * On failure, the event handler does it.
   */
  state->tries = 0;
  state->backoff = state->opts.wait_between_tries;
  return;
}

/* Returns 0 on success and populates |fds| */
int
new_tlsdate_monitor_pipe (int fds[2])
{
  if (pipe (fds) < 0)
    {
      perror ("pipe failed");
      return -1;
    }
  /* TODO(wad): CLOEXEC, Don't leak these into tlsdate proper. */
  return 0;
}

/* Create a fd pair that the tlsdate runner will communicate over */
int
setup_tlsdate_status (struct state *state)
{
  int fds[2] = { -1, -1 };
  /* One pair of pipes are reused along with the event. */
  if (new_tlsdate_monitor_pipe (fds))
    {
      return -1;
    }
  verb_debug ("[%s] monitor fd pair (%d, %d)", __func__, fds[0], fds[1]);
  /* The fd that the monitor process will write to */
  state->tlsdate_monitor_fd = fds[1];
  /* Make the reader fd non-blocking and not leak into tlsdate. */
  if (fcntl (fds[0], F_SETFL, O_NONBLOCK|O_CLOEXEC) < 0)
    {
      perror ("pipe[0] fcntl(O_NONBLOCK) failed");
      return 1;
    }
  state->events[E_TLSDATE_STATUS] = event_new (state->base, fds[0],
                                    EV_READ,
                                    action_tlsdate_status, state);
  if (!state->events[E_TLSDATE_STATUS])
    {
      error ("Failed to allocate tlsdate status event");
      return 1;
    }
  event_priority_set (state->events[E_TLSDATE_STATUS], PRI_NET);
  state->events[E_TLSDATE_TIMEOUT] = event_new (state->base, -1,
                                     EV_TIMEOUT,
                                     action_tlsdate_timeout, state);
  if (!state->events[E_TLSDATE_TIMEOUT])
    {
      error ("Failed to allocate tlsdate timeout event");
      return 1;
    }
  event_priority_set (state->events[E_TLSDATE_TIMEOUT], PRI_SAVE);
  return 0;
}