/* Copyright (c) 2016-2018, The Tor Project, Inc. */
/* See LICENSE for licensing information */

/**
 * \file test_hs_config.c
 * \brief Test hidden service configuration functionality.
 */

#define CONFIG_PRIVATE
#define HS_SERVICE_PRIVATE

#include "test/test.h"
#include "test/test_helpers.h"
#include "test/log_test_helpers.h"

#include "app/config/config.h"
#include "feature/hs/hs_common.h"
#include "feature/hs/hs_config.h"
#include "feature/hs/hs_service.h"
#include "feature/rend/rendservice.h"

static int
helper_config_service(const char *conf, int validate_only)
{
  int ret = 0;
  or_options_t *options = NULL;
  tt_assert(conf);
  options = helper_parse_options(conf);
  tt_assert(options);
  ret = hs_config_service_all(options, validate_only);
 done:
  or_options_free(options);
  return ret;
}

static void
test_invalid_service(void *arg)
{
  int ret;

  (void) arg;

  /* Try with a missing port configuration. */
  {
    const char *conf =
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
      "HiddenServiceVersion 1\n"; /* Wrong not supported version. */
    setup_full_capture_of_logs(LOG_WARN);
    ret = helper_config_service(conf, 1);
    tt_int_op(ret, OP_EQ, -1);
    expect_log_msg_containing("HiddenServiceVersion must be between 2 and 3");
    teardown_capture_of_logs();
  }

  /* Bad value of HiddenServiceAllowUnknownPorts. */
  {
    const char *conf =
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
      "HiddenServiceVersion 2\n"
      "HiddenServiceAllowUnknownPorts 2\n"; /* Should be 0 or 1. */
    setup_full_capture_of_logs(LOG_WARN);
    ret = helper_config_service(conf, 1);
    tt_int_op(ret, OP_EQ, -1);
    expect_log_msg_containing("HiddenServiceAllowUnknownPorts must be "
                              "between 0 and 1, not 2");
    teardown_capture_of_logs();
  }

  /* Bad value of HiddenServiceDirGroupReadable */
  {
    const char *conf =
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
      "HiddenServiceVersion 2\n"
      "HiddenServiceDirGroupReadable 2\n"; /* Should be 0 or 1. */
    setup_full_capture_of_logs(LOG_WARN);
    ret = helper_config_service(conf, 1);
    tt_int_op(ret, OP_EQ, -1);
    expect_log_msg_containing("HiddenServiceDirGroupReadable must be "
                              "between 0 and 1, not 2");
    teardown_capture_of_logs();
  }

  /* Bad value of HiddenServiceMaxStreamsCloseCircuit */
  {
    const char *conf =
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
      "HiddenServiceVersion 2\n"
      "HiddenServiceMaxStreamsCloseCircuit 2\n"; /* Should be 0 or 1. */
    setup_full_capture_of_logs(LOG_WARN);
    ret = helper_config_service(conf, 1);
    tt_int_op(ret, OP_EQ, -1);
    expect_log_msg_containing("HiddenServiceMaxStreamsCloseCircuit must "
                              "be between 0 and 1, not 2");
    teardown_capture_of_logs();
  }

  /* Too much max streams. */
  {
    const char *conf =
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
      "HiddenServiceVersion 2\n"
      "HiddenServicePort 80\n"
      "HiddenServiceMaxStreams 65536\n"; /* One too many. */
    setup_full_capture_of_logs(LOG_WARN);
    ret = helper_config_service(conf, 1);
    tt_int_op(ret, OP_EQ, -1);
    expect_log_msg_containing("HiddenServiceMaxStreams must be between "
                              "0 and 65535, not 65536");
    teardown_capture_of_logs();
  }

  /* Duplicate directory directive. */
  {
    const char *conf =
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
      "HiddenServiceVersion 2\n"
      "HiddenServicePort 80\n"
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
      "HiddenServiceVersion 2\n"
      "HiddenServicePort 81\n";
    setup_full_capture_of_logs(LOG_WARN);
    ret = helper_config_service(conf, 1);
    tt_int_op(ret, OP_EQ, -1);
    expect_log_msg_containing("Another hidden service is already "
                              "configured for directory");
    teardown_capture_of_logs();
  }

  /* Bad port. */
  {
    const char *conf =
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
      "HiddenServiceVersion 2\n"
      "HiddenServicePort 65536\n";
    setup_full_capture_of_logs(LOG_WARN);
    ret = helper_config_service(conf, 1);
    tt_int_op(ret, OP_EQ, -1);
    expect_log_msg_containing("Missing or invalid port");
    teardown_capture_of_logs();
  }

  /* Bad target addr:port separation. */
  {
    const char *conf =
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
      "HiddenServiceVersion 2\n"
      "HiddenServicePort 80 127.0.0.1 8000\n";
    setup_full_capture_of_logs(LOG_WARN);
    ret = helper_config_service(conf, 1);
    tt_int_op(ret, OP_EQ, -1);
    expect_log_msg_containing("HiddenServicePort parse error: "
                              "invalid port mapping");
    teardown_capture_of_logs();
  }

  /* Out of order directives. */
  {
    const char *conf =
      "HiddenServiceVersion 2\n"
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
      "HiddenServicePort 80\n";
    setup_full_capture_of_logs(LOG_WARN);
    ret = helper_config_service(conf, 1);
    tt_int_op(ret, OP_EQ, -1);
    expect_log_msg_containing("HiddenServiceVersion with no preceding "
                              "HiddenServiceDir directive");
    teardown_capture_of_logs();
  }

 done:
  ;
}

static void
test_valid_service(void *arg)
{
  int ret;

  (void) arg;

  /* Mix of v2 and v3. Still valid. */
  {
    const char *conf =
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
      "HiddenServiceVersion 2\n"
      "HiddenServicePort 80\n"
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs2\n"
      "HiddenServiceVersion 3\n"
      "HiddenServicePort 81\n"
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs3\n"
      "HiddenServiceVersion 2\n"
      "HiddenServicePort 82\n";
    ret = helper_config_service(conf, 1);
    tt_int_op(ret, OP_EQ, 0);
  }

 done:
  ;
}

static void
test_invalid_service_v2(void *arg)
{
  int validate_only = 1, ret;

  (void) arg;

  /* Try with a missing port configuration. */
  {
    const char *conf =
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
      "HiddenServiceVersion 2\n";
    setup_full_capture_of_logs(LOG_WARN);
    ret = helper_config_service(conf, validate_only);
    tt_int_op(ret, OP_EQ, -1);
    expect_log_msg_containing("with no ports configured.");
    teardown_capture_of_logs();
  }

  /* Too many introduction points. */
  {
    const char *conf =
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
      "HiddenServiceVersion 2\n"
      "HiddenServicePort 80\n"
      "HiddenServiceNumIntroductionPoints 11\n"; /* One too many. */
    setup_full_capture_of_logs(LOG_WARN);
    ret = helper_config_service(conf, validate_only);
    tt_int_op(ret, OP_EQ, -1);
    expect_log_msg_containing("HiddenServiceNumIntroductionPoints should "
                              "be between 0 and 10, not 11");
    teardown_capture_of_logs();
  }

  /* Too little introduction points. */
  {
    const char *conf =
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
      "HiddenServiceVersion 2\n"
      "HiddenServicePort 80\n"
      "HiddenServiceNumIntroductionPoints -1\n";
    setup_full_capture_of_logs(LOG_WARN);
    ret = helper_config_service(conf, validate_only);
    tt_int_op(ret, OP_EQ, -1);
    expect_log_msg_containing("HiddenServiceNumIntroductionPoints should "
                              "be between 0 and 10, not -1");
    teardown_capture_of_logs();
  }

  /* Bad authorized client type. */
  {
    const char *conf =
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
      "HiddenServiceVersion 2\n"
      "HiddenServicePort 80\n"
      "HiddenServiceAuthorizeClient blah alice,bob\n"; /* blah is no good. */
    setup_full_capture_of_logs(LOG_WARN);
    ret = helper_config_service(conf, validate_only);
    tt_int_op(ret, OP_EQ, -1);
    expect_log_msg_containing("HiddenServiceAuthorizeClient contains "
                              "unrecognized auth-type");
    teardown_capture_of_logs();
  }

 done:
  ;
}

static void
test_valid_service_v2(void *arg)
{
  int ret;

  (void) arg;

  /* Valid complex configuration. Basic client authorization. */
  {
    const char *conf =
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
      "HiddenServiceVersion 2\n"
      "HiddenServicePort 80\n"
      "HiddenServicePort 22 localhost:22\n"
#ifdef HAVE_SYS_UN_H
      "HiddenServicePort 42 unix:/path/to/socket\n"
#endif
      "HiddenServiceAuthorizeClient basic alice,bob,eve\n"
      "HiddenServiceAllowUnknownPorts 1\n"
      "HiddenServiceMaxStreams 42\n"
      "HiddenServiceMaxStreamsCloseCircuit 0\n"
      "HiddenServiceDirGroupReadable 1\n"
      "HiddenServiceNumIntroductionPoints 7\n";
    ret = helper_config_service(conf, 1);
    tt_int_op(ret, OP_EQ, 0);
  }

  /* Valid complex configuration. Stealth client authorization. */
  {
    const char *conf =
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs2\n"
      "HiddenServiceVersion 2\n"
      "HiddenServicePort 65535\n"
      "HiddenServicePort 22 1.1.1.1:22\n"
#ifdef HAVE_SYS_UN_H
      "HiddenServicePort 9000 unix:/path/to/socket\n"
#endif
      "HiddenServiceAuthorizeClient stealth charlie,romeo\n"
      "HiddenServiceAllowUnknownPorts 0\n"
      "HiddenServiceMaxStreams 42\n"
      "HiddenServiceMaxStreamsCloseCircuit 0\n"
      "HiddenServiceDirGroupReadable 1\n"
      "HiddenServiceNumIntroductionPoints 8\n";
    ret = helper_config_service(conf, 1);
    tt_int_op(ret, OP_EQ, 0);
  }

 done:
  ;
}

static void
test_invalid_service_v3(void *arg)
{
  int validate_only = 1, ret;

  (void) arg;

  /* Try with a missing port configuration. */
  {
    const char *conf =
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
      "HiddenServiceVersion 3\n";
    setup_full_capture_of_logs(LOG_WARN);
    ret = helper_config_service(conf, validate_only);
    tt_int_op(ret, OP_EQ, -1);
    expect_log_msg_containing("with no ports configured.");
    teardown_capture_of_logs();
  }

  /* Too many introduction points. */
  {
    const char *conf =
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
      "HiddenServiceVersion 3\n"
      "HiddenServicePort 80\n"
      "HiddenServiceNumIntroductionPoints 21\n"; /* One too many. */
    setup_full_capture_of_logs(LOG_WARN);
    ret = helper_config_service(conf, validate_only);
    tt_int_op(ret, OP_EQ, -1);
    expect_log_msg_containing("HiddenServiceNumIntroductionPoints must "
                              "be between 3 and 20, not 21.");
    teardown_capture_of_logs();
  }

  /* Too little introduction points. */
  {
    const char *conf =
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
      "HiddenServiceVersion 3\n"
      "HiddenServicePort 80\n"
      "HiddenServiceNumIntroductionPoints 1\n";
    setup_full_capture_of_logs(LOG_WARN);
    ret = helper_config_service(conf, validate_only);
    tt_int_op(ret, OP_EQ, -1);
    expect_log_msg_containing("HiddenServiceNumIntroductionPoints must "
                              "be between 3 and 20, not 1.");
    teardown_capture_of_logs();
  }

 done:
  ;
}

static void
test_valid_service_v3(void *arg)
{
  int ret;

  (void) arg;

  /* Valid complex configuration. */
  {
    const char *conf =
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
      "HiddenServiceVersion 3\n"
      "HiddenServicePort 80\n"
      "HiddenServicePort 22 localhost:22\n"
#ifdef HAVE_SYS_UN_H
      "HiddenServicePort 42 unix:/path/to/socket\n"
#endif
      "HiddenServiceAllowUnknownPorts 1\n"
      "HiddenServiceMaxStreams 42\n"
      "HiddenServiceMaxStreamsCloseCircuit 0\n"
      "HiddenServiceDirGroupReadable 1\n"
      "HiddenServiceNumIntroductionPoints 7\n";
    ret = helper_config_service(conf, 1);
    tt_int_op(ret, OP_EQ, 0);
  }

  /* Valid complex configuration. */
  {
    const char *conf =
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs2\n"
      "HiddenServiceVersion 3\n"
      "HiddenServicePort 65535\n"
      "HiddenServicePort 22 1.1.1.1:22\n"
#ifdef HAVE_SYS_UN_H
      "HiddenServicePort 9000 unix:/path/to/socket\n"
#endif
      "HiddenServiceAllowUnknownPorts 0\n"
      "HiddenServiceMaxStreams 42\n"
      "HiddenServiceMaxStreamsCloseCircuit 0\n"
      "HiddenServiceDirGroupReadable 1\n"
      "HiddenServiceNumIntroductionPoints 20\n";
    ret = helper_config_service(conf, 1);
    tt_int_op(ret, OP_EQ, 0);
  }

  /* Mix of v2 and v3. Still valid. */
  {
    const char *conf =
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs1\n"
      "HiddenServiceVersion 2\n"
      "HiddenServicePort 80\n"
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs2\n"
      "HiddenServiceVersion 3\n"
      "HiddenServicePort 81\n"
      "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs3\n"
      "HiddenServiceVersion 2\n"
      "HiddenServicePort 82\n";
    ret = helper_config_service(conf, 1);
    tt_int_op(ret, OP_EQ, 0);
  }

 done:
  ;
}

static void
test_staging_service_v3(void *arg)
{
  int ret;

  (void) arg;

  /* We don't validate a service object, this is the service test that are in
   * charge of doing so. We just check for the stable state after
   * registration. */

  hs_init();

  /* Time for a valid v3 service that should get staged. */
  const char *conf =
    "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs2\n"
    "HiddenServiceVersion 3\n"
    "HiddenServicePort 65535\n"
    "HiddenServicePort 22 1.1.1.1:22\n"
#ifdef HAVE_SYS_UN_H
    "HiddenServicePort 9000 unix:/path/to/socket\n"
#endif
    "HiddenServiceAllowUnknownPorts 0\n"
    "HiddenServiceMaxStreams 42\n"
    "HiddenServiceMaxStreamsCloseCircuit 0\n"
    "HiddenServiceDirGroupReadable 1\n"
    "HiddenServiceNumIntroductionPoints 20\n";
  ret = helper_config_service(conf, 0);
  tt_int_op(ret, OP_EQ, 0);
  /* Ok, we have a service in our map! Registration went well. */
  tt_int_op(get_hs_service_staging_list_size(), OP_EQ, 1);
  /* Make sure we don't have a magic v2 service out of this. */
  tt_int_op(rend_num_services(), OP_EQ, 0);

 done:
  hs_free_all();
}

struct testcase_t hs_config_tests[] = {
  /* Invalid service not specific to any version. */
  { "invalid_service", test_invalid_service, TT_FORK,
    NULL, NULL },
  { "valid_service", test_valid_service, TT_FORK,
    NULL, NULL },

  /* Test case only for version 2. */
  { "invalid_service_v2", test_invalid_service_v2, TT_FORK,
    NULL, NULL },
  { "valid_service_v2", test_valid_service_v2, TT_FORK,
    NULL, NULL },

  /* Test case only for version 3. */
  { "invalid_service_v3", test_invalid_service_v3, TT_FORK,
    NULL, NULL },
  { "valid_service_v3", test_valid_service_v3, TT_FORK,
    NULL, NULL },

  /* Test service staging. */
  { "staging_service_v3", test_staging_service_v3, TT_FORK,
    NULL, NULL },

  END_OF_TESTCASES
};

