/* Copyright (c) 2012, Jacob Appelbaum.
 * Copyright (c) 2012, The Tor Project, Inc. */
/* See LICENSE for licensing information */

/**
 * \file tordate.c
 * \brief The main program to assist in setting the system clock.
 **/

/*
 * tordate is a tool for setting the system clock by hand or by communication
 * with the network. It does not set the RTC. It is designed to be as secure as
 * TLS (RFC 2246) and additionally as secure as the Tor protocol itself. This
 * tool is designed to be run by hand or as a system daemon. It may be run by
 * Tor at a later date but unless tordate has the proper caps, it will not be
 * able to set the system time without running as root or another privileged
 * user.
 */

#include "orconfig.h"
#include "compat.h"
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <getopt.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_TIME_H
#include <time.h>
#endif
#include <string.h>

#include <arpa/inet.h>

#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/evp.h>

#ifdef HAVE_PRCTL
#include <sys/prctl.h>
#endif
#ifdef HAVE_SYS_CAPABILITY_H
#include <sys/capability.h>
#endif

#include <sys/types.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>

#include "tordate.h"
#define tordate_version "0.1"
#define UNPRIV_USER "nobody"

// Hack to make tor code into a shared lib
const char tor_git_revision[] =
#ifndef _MSC_VER
#include "../../or/micro-revision.i"
#endif
  "";


/** Return the proper commandline switches when the user needs information. */
static void
usage(void)
{
  fprintf(stderr, "tordate usage:\n"
          " [-h|--help]\n"
          " [-v|--verbose]\n");
}

/*
XXX: TODO

Functions to implement:

  Randomly select server from dirservers
  Verification of remote certificate for Tor nodes
  Pin SSL certs for racket mode

*/

/** Set the system clock to the value stored in <b>now</b>. */
int set_absolute_time(const struct timeval *now)
{
  return settimeofday(now, NULL);
}

int set_adj_time(const struct timeval *delta, struct timeval *olddelta)
{
  return adjtime(delta, olddelta);
}

// Drop all caps except CAP_SYS_TIME
int drop_caps(void)
{
  int r = 0;
  cap_t caps;

  cap_value_t needed_caps[1] = {CAP_SYS_TIME};
  caps = cap_init();
  if (caps == NULL)
  {
    fprintf(stderr, "cap_init() failed\n");
    exit(23);
  }
  r = cap_set_flag(caps, CAP_EFFECTIVE, 1, needed_caps, CAP_SET);
  if (r != 0)
  {
    fprintf(stderr, "cap_set_flag() failed\n");
    exit(23);
  }
  r = cap_set_flag(caps, CAP_PERMITTED, 1, needed_caps, CAP_SET);
  if (r != 0)
  {
    fprintf(stderr, "cap_set_flag() failed\n");
    exit(23);
  }
  r = cap_set_proc(caps);
  if (r != 0)
  {
    fprintf(stderr, "cap_set_proc() failed\n");
    exit(23);
  }
  r = cap_free(caps);
  if (r != 0)
  {
    fprintf(stderr, "cap_free() failed\n");
    exit(23);
  }
  return r;
}

int switch_uid(struct passwd *pw)
{
  int r = 0;
  r = setgid(pw->pw_gid);
  r = initgroups(UNPRIV_USER, pw->pw_gid);
  r = setuid(pw->pw_uid);
  return r;
}

int chroot_tmp(void)
{
  int r = 0;
  r = chroot("/tmp/");
  r = chdir("/");
  return r;
}

/* This is inspired by conversations with stealth */
int drop_privs(void)
{
  struct passwd *pw;
  int r = 0;
  pw = getpwnam(UNPRIV_USER);
  if (pw == NULL)
  {
    fprintf(stderr, "getpwnam() failed\n");
    exit(23);
  }
  // check these to ensure they're always 0, less is bad
  r = prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
  if (r != 0)
  {
    fprintf(stderr, "prctl() failed\n");
    exit(23);
  }

  r = chroot_tmp();
  if (r != 0)
  {
    fprintf(stderr, "chroot_tmp() failed\n");
    exit(23);
  }

  r = switch_uid(pw);
  if (r != 0)
  {
    fprintf(stderr, "switch_uid() failed\n");
    exit(23);
  }

  r = drop_caps();
  if (r != 0)
  {
    fprintf(stderr, "drop_caps() failed\n");
    exit(23);
  }

  return r;
}

int
main(int argc, char **argv)
{
  int r = 0;
  int c = 0;
  struct timeval start_timeval;
  struct timeval end_timeval;
  struct timeval timeval;
  tordate_options_t tordate_options;
  struct timeval server_time;
  uint32_t server_time_tmp;
  server_time.tv_sec = 0;
  server_time.tv_usec = 0;
  uint32_t rt_time = 0;
  uint32_t ca_racket = 1;
  long ssl_verify_result = 0;

  // From src/or/config.c
  const char *dirservers[] = {
    "moria1 orport=9101 no-v2 "
      "v3ident=D586D18309DED4CD6D57C18FDB97EFA96D330566 "
      "128.31.0.39:9131 9695 DFC3 5FFE B861 329B 9F1A B04C 4639 7020 CE31",
    "tor26 v1 orport=443 v3ident=14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4 "
      "86.59.21.38:80 847B 1F85 0344 D787 6491 A548 92F9 0493 4E4E B85D",
    "dizum orport=443 v3ident=E8A9C45EDE6D711294FADF8E7951F4DE6CA56B58 "
      "194.109.206.212:80 7EA6 EAD6 FD83 083C 538F 4403 8BBF A077 587D D755",
    "Tonga orport=443 bridge no-v2 82.94.251.203:80 "
      "4A0C CD2D DC79 9508 3D73 F5D6 6710 0C8A 5831 F16D",
    "ides orport=9090 no-v2 v3ident=27B6B5996C426270A5C95488AA5BCEB6BCC86956 "
      "216.224.124.114:9030 F397 038A DC51 3361 35E7 B80B D99C A384 4360 292B",
    "gabelmoo orport=443 no-v2 "
      "v3ident=ED03BB616EB2F60BEC80151114BB25CEF515B226 "
      "212.112.245.170:80 F204 4413 DAC2 E02E 3D6B CF47 35A1 9BCA 1DE9 7281",
    "dannenberg orport=443 no-v2 "
      "v3ident=585769C78764D58426B8B52B6651A5A71137189A "
      "193.23.244.244:80 7BE6 83E6 5D48 1413 21C5 ED92 F075 C553 64AC 7123",
    "urras orport=80 no-v2 v3ident=80550987E1D626E3EBA5E5E75A458DE0626D088C "
      "208.83.223.34:443 0AD3 FA88 4D18 F89E EA2D 89C0 1937 9E0E 7FD9 4417",
    "maatuska orport=80 no-v2 "
      "v3ident=49015F787433103580E3B66A1707A00E60F2D15B "
      "213.115.239.118:443 BD6A 8292 55CB 08E6 6FBE 7D37 4836 3586 E46B 3810",
    NULL
  };

  BIO *s_bio, *c_bio;
  SSL_CTX *ctx;
  SSL *ssl;
  X509 *x509;

  SSL_load_error_strings();
  SSL_library_init();

  memset(&tordate_options, 0, sizeof(tordate_options));

  while (1) {
    int option_index = 0;
    static struct option long_options[] =
      {
        {"verbose", 0, 0, 'v'},
        {"help", 0, 0, 'h'},
        {0, 0, 0, 0}
      };

    c = getopt_long(argc, argv, "vh",
                    long_options, &option_index);
    if (c == -1)
      break;

    switch (c) {
      case 'v': tordate_options.verbose = 1; break;
      case 'h': tordate_options.help = 1; usage(); exit(1); break;
      case '?': break;
      default : fprintf(stderr, "Unknown option!\n"); usage(); exit(1);
    }
  }

  if (tordate_options.verbose) {
    fprintf(stderr, "V: tordate version %s\n"
            "V: We were called with the following arguments:\n"
            "V: verbose = %d, help = %d\n",
            tordate_version, tordate_options.verbose, tordate_options.help);
  }

  /* Get the current time from the system clock. */
  gettimeofday(&timeval, NULL);
  if (tordate_options.verbose)
    fprintf(stderr, "V: time is currently %lu sec and %lu usec\n", timeval.tv_sec,
                                                                   timeval.tv_usec);

  ctx = SSL_CTX_new(SSLv23_client_method());
  if (ctx == NULL)
    exit(1);

  if (ca_racket)
  {
    // For google specifically:
    // r = SSL_CTX_load_verify_locations(ctx, "/etc/ssl/certs/Equifax_Secure_CA.pem", NULL);
    r = SSL_CTX_load_verify_locations(ctx, NULL, "/etc/ssl/certs/");
    if (r == 0)
      fprintf(stdout, "V: SSL_CTX_load_verify_locations\n");
  }

  s_bio = BIO_new_ssl_connect(ctx);
  if (s_bio == NULL)
    exit(2);

  BIO_get_ssl(s_bio, &ssl);
  if (ssl == NULL)
    exit(3);

  SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);

  // XXX TODO: Don't hardcode this garbage...
  if (ca_racket)
  {
    // Tor Project HTTPS server
    BIO_set_conn_hostname(s_bio, "check.torproject.org:443");
  } else {
    // Tor Directory Authority OR port
    BIO_set_conn_hostname(s_bio, "rgnx.net:80");
  }

  c_bio = BIO_new_fp(stdout, BIO_NOCLOSE);
  if (c_bio == NULL)
    exit(4);

  // This should run in seccomp
  // eg:     prctl(PR_SET_SECCOMP, 1);
  r = BIO_do_connect(s_bio); // XXX TODO: BIO_should_retry() later?
  if (r <= 0)
    exit(5);

  // XXX TODO: this should happen way way before here...
  r = drop_privs();
  if (r != 0)
  {
    fprintf(stderr, "drop_privs() failed\n");
    exit(23);
  }

  gettimeofday(&start_timeval, NULL);
  r = BIO_do_handshake(s_bio);
  if (r != 1)
    exit(6);
  gettimeofday(&end_timeval, NULL);
  // Verify the peer certificate against the CA certs on the local system
  if (ca_racket) {
    x509 = SSL_get_peer_certificate(ssl);
    if (x509 == NULL)
      exit(7);
    if (tordate_options.verbose)
      fprintf(stdout, "V: SSL_get_peer_certificate returned\n");

    // In theory, we verify that the cert is valid
    ssl_verify_result = SSL_get_verify_result(ssl);
    if ( ssl_verify_result == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
         ssl_verify_result == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN )
    {
      fprintf(stderr, "self signed cert\n");
    } else {
      if (  ssl_verify_result == X509_V_OK )
      {
        if (tordate_options.verbose)
          fprintf(stderr, "V: verification OK: %ld\n", ssl_verify_result);
      } else {
          fprintf(stderr, "E: verification error: %ld\n", ssl_verify_result);
      }
    }

    if (tordate_options.verbose)
      fprintf(stdout, "V: ssl_verify_result returned %ld\n", ssl_verify_result);
  } else {
    // This branch is for hitting the Tor Directory servers
    // XXX: TODO - implement it
    /*
    Let's start by
    assuming that we only want to support the v3 handshake.  So what we
    have to do is then let the TLS handshake complete.  At this point ***,
    you're authenticated with _some_ cert, but you don't know if it's the
    right one.  So you send a versions cell, containing the version "3"
    {00 00 07 00 02 00 03} .  You receive some number of cells; you parse
    each one.  (Should be pretty easy; the format's in tor-spec.txt
    section 3; reading the first 5 bytes will tell you how much more to
    read.)  You need to parse the CERTS cell and the NETINFO cell in
    particular.

    The CERTS cell will have at least two X509 certs in it.  You extract
    the one whose cert type is 1 (Link) and the one whose cert type is 2
    (identity).  You verify that the key of the Link cert matches the key
    used in the SSL object, and that the link cert was correctly signed by
    the key in the identity cert.  Now you know who you're talking to.  At
    this point, you take the digest of the key in the identity cert, and
    make sure it matches the fingerprint you were trying to reach.  If so,
    great!  At this point, you can look at the time from the TLS
    negotiation and the time from the NETINFO cell.

    Then you close the connection.
    */

    // XXX: construct versions cell
    //   eg: connection_or_send_versions in src/or/connection_or.c
    // XXX: write versions cell to s_bio
    // XXX: start timer
    // XXX: read response
    // XXX: parse each cell
    // XXX: find the CERTS cell
    // XXX: find the NETINFO cell
    // XXX: parse the ident key out of the CERTS cell
    // XXX: parse the tls key out of the CERTS cell
    // XXX: verify the ident key
    // XXX: verify the tls key
    // XXX: end timer

    //fprintf(stdout, "V: update_approx_time()\n");
    //update_approx_time(time(NULL));

    //fprintf(stdout, "V: tor_threads_init()\n");
    //tor_threads_init();

    //fprintf(stdout, "V: init_logging()\n");
    //init_logging();

    // fprintf(stdout, "V: tor_init()\n");
    // tor_init(0, 0);

    // fprintf(stdout, "V: tor_cleanup()\n");
    // tor_cleanup();
    printf("bare_or_send_versions\n");
    bare_or_send_versions(s_bio);
    printf("bare_or_send_versions\n");

  }

  rt_time = abs(end_timeval.tv_sec - start_timeval.tv_sec);
  if (tordate_options.verbose)
    fprintf(stdout, "V: server_random fetched in %i sec\n", rt_time);

  // from /usr/include/openssl/ssl3.h
  //  ssl->s3->server_random is an unsigned char of 32 bytes
  memcpy(&server_time_tmp, ssl->s3->server_random, 4);
  server_time.tv_sec = ntohl(server_time_tmp);
  if (tordate_options.verbose)
    fprintf(stdout, "V: server_random with ntohl is: %lu and 0 usec\n", server_time.tv_sec);

  // Set the time absolutely...
  // r = set_absolute_time(&server_time);
  // Ensure that we only increase time...
  //r = set_adj_time(NULL, &server_time);
  r = set_absolute_time(&server_time);

  if (tordate_options.verbose)
  {
    fprintf(stderr, "V: setting time returned: %i\n", r);
    if (r == 0) {
      fprintf(stdout, "tordate: SUCCESS\n");
    } else {
      fprintf(stderr, "tordate: FAILURE\n");
    }
  }
  exit(r);
}

