Beispiel #1
0
/* 
 * _hostrange_output
 *
 * Output metric data in hostrange format.  The algorithm involves
 * using the metric_value as a hash key.  Each hash item will then
 * store the hosts with the same metric_value/key.
 */
static void
_hostrange_output(List l)
{
#if CEREBRO_DEBUG
  const char *func = __FUNCTION__;
#endif /* CEREBRO_DEBUG */
  struct node_metric_data *data = NULL;
  ListIterator litr = NULL;
  unsigned int count;
  hash_t h;

  assert(l);

  count = List_count(l);

#if CEREBRO_DEBUG
  if (!count)
    err_exit("%s: invalid count", func);
#endif /* CEREBRO_DEBUG */

  h = Hash_create(count, 
                  (hash_key_f)hash_key_string,
                  (hash_cmp_f)strcmp,
                  (hash_del_f)_hostrange_data_destroy);

  litr = List_iterator_create(l);

  while ((data = list_next(litr)))
    {
      char buf[CEREBRO_STAT_BUFLEN];
      struct hostrange_data *hd;

      _metric_value_str(data->metric_value_type,
                        data->metric_value_len,
                        data->metric_value, 
                        buf, 
                        CEREBRO_STAT_BUFLEN);

      if (!(hd = Hash_find(h, buf)))
        {
          hd = Malloc(sizeof(struct hostrange_data));
          hd->hl = Hostlist_create(NULL);
          hd->key = Strdup(buf);

          Hash_insert(h, hd->key, hd);
        }

      Hostlist_push(hd->hl, data->nodename);
    }

  Hash_for_each(h, _hostrange_output_data, NULL);

  /* No need to destroy list iterator, caller will destroy List */
  Hash_destroy(h);
}
Beispiel #2
0
/* Функция корректного выхода. */
void correct_exit(){
    for(int i = 0; i < server_pool.threads_count; i++){
        pthread_cancel(server_pool.tids[i]);
    }
    zmq_close(server_pool.workers);
    zmq_close(server_pool.clients);
    zmq_ctx_destroy(server_pool.context);
    free(server_pool.tids);
    Cache_destroy(&server_pool.cache);
    Hash_destroy(&server_pool.hash);
    if(server_pool.be_verbose) syslog(LOG_INFO, "Server exit.");
    closelog();
    exit(EXIT_SUCCESS);
}
extern void
Config_section_destroy(Config_section_T *section)
{
    if(section == NULL || *section == NULL)
        return;

    if((*section)->name) {
        free((*section)->name);
        (*section)->name = NULL;
    }

    Hash_destroy(&((*section)->vars));

    free(*section);
    *section = NULL;
}
Beispiel #4
0
/*
 * _setup_event_node_timeout_data
 *
 * Setup event timeout calculation data structures. 
 *
 * Return 0 on success, -1 on error
 */
static int
_setup_event_node_timeout_data(void)
{
  struct timeval tv;
  int num;

  assert(conf.event_server);
  assert(listener_data);

  event_node_timeout_data = List_create(NULL);
  event_node_timeout_data_index = Hash_create(EVENT_NODE_TIMEOUT_SIZE_DEFAULT, 
                                              (hash_key_f)hash_key_string,
                                              (hash_cmp_f)strcmp, 
                                              (hash_del_f)NULL);
  event_node_timeout_data_index_size = EVENT_NODE_TIMEOUT_SIZE_DEFAULT;
  
  Gettimeofday(&tv, NULL);

  num = Hash_for_each(listener_data,
                      _event_node_timeout_data_callback,
                      &(tv.tv_sec));
  if (num != listener_data_numnodes)
    {
      fprintf(stderr, "* invalid create count: num=%d numnodes=%d\n",
              num, listener_data_numnodes);
      goto cleanup;
    }

  return 0;

 cleanup:
  if (event_node_timeout_data)
    {
      List_destroy(event_node_timeout_data);
      event_node_timeout_data = NULL;
    }
  if (event_node_timeout_data_index)
    {
      Hash_destroy(event_node_timeout_data_index);
      event_node_timeout_data_index =  NULL;
    }
  return -1;
}
Beispiel #5
0
void
cerebrod_rehash(hash_t *old_hash,
                int *hash_size,
                int hash_size_increment,
                int hash_num,
                pthread_mutex_t *hash_mutex)
{
    hash_t new_hash;

    assert(old_hash && hash_size && hash_size_increment && hash_num);

#if CEREBRO_DEBUG
    /* Should be called with lock already set */
    if (hash_mutex)
    {
        int rv = Pthread_mutex_trylock(hash_mutex);
        if (rv != EBUSY)
            CEREBROD_EXIT(("mutex not locked: rv=%d", rv));
    }
#endif /* CEREBRO_DEBUG */

    *hash_size += hash_size_increment;

    new_hash = Hash_create(*hash_size,
                           (hash_key_f)hash_key_string,
                           (hash_cmp_f)strcmp,
                           (hash_del_f)_Free);

    if (Hash_for_each(*old_hash, _hash_reinsert, &new_hash) != hash_num)
        CEREBROD_EXIT(("invalid reinsert: hash_num=%d", hash_num));

    if (Hash_remove_if(*old_hash, _hash_removeall, NULL) != hash_num)
        CEREBROD_EXIT(("invalid removeall: hash_num=%d", hash_num));

    Hash_destroy(*old_hash);

    *old_hash = new_hash;
}
Beispiel #6
0
/*
 * Under almost any circumstance, don't return a -1 error, cerebro can
 * go on without loading monitor modules. The listener_data_init_lock
 * should already be set.
 */
int
cerebrod_event_modules_setup(void)
{
  int i, event_module_count, event_index_len, event_index_count = 0;
  struct cerebrod_event_module_list *el = NULL;
#if CEREBRO_DEBUG
  int rv;
#endif /* CEREBRO_DEBUG */

  assert(listener_data);

#if CEREBRO_DEBUG
  /* Should be called with lock already set */
  rv = Pthread_mutex_trylock(&listener_data_init_lock);
  if (rv != EBUSY)
    CEREBROD_EXIT(("mutex not locked: rv=%d", rv));
#endif /* CEREBRO_DEBUG */

  if (!conf.event_server)
    return 0;

  if (!(event_handle = event_modules_load()))
    {
      CEREBROD_DBG(("event_modules_load"));
      goto cleanup;
    }

  if ((event_module_count = event_modules_count(event_handle)) < 0)
    {
      CEREBROD_DBG(("event_modules_count"));
      goto cleanup;
    }

  if (!event_module_count)
    {
      if (conf.debug && conf.event_server_debug)
        {
          Pthread_mutex_lock(&debug_output_mutex);
          fprintf(stderr, "**************************************\n");
          fprintf(stderr, "* No Event Modules Found\n");
          fprintf(stderr, "**************************************\n");
          Pthread_mutex_unlock(&debug_output_mutex);
        }
      goto cleanup;
    }

  /* Each event module may want multiple metrics and/or offer multiple
   * event names.  We'll assume there will never be more than 2 per
   * event module, and that will be enough to avoid all hash
   * collisions.
   */
  event_index_len = event_module_count * 2;

  event_index = Hash_create(event_index_len,
                            (hash_key_f)hash_key_string,
                            (hash_cmp_f)strcmp,
                            (hash_del_f)_cerebrod_event_module_list_destroy);

  event_names = List_create((ListDelF)_Free);

  event_module_timeouts = List_create((ListDelF)_cerebrod_event_module_timeout_data_destroy);
  event_module_timeout_index = Hash_create(event_module_count,
					   (hash_key_f)hash_key_string,
					   (hash_cmp_f)strcmp,
					   (hash_del_f)list_destroy);

  for (i = 0; i < event_module_count; i++)
    {
      struct cerebrod_event_module *event_module;
      char *module_name, *module_metric_names, *module_event_names;
      char *metricPtr, *metricbuf;
      char *eventnamePtr, *eventbuf;
      int timeout;

      module_name = event_module_name(event_handle, i);

      if (conf.event_module_exclude_len)
        {
          int found_exclude = 0;
          int j;

          for (j = 0; j < conf.event_module_exclude_len; j++)
            {
              if (!strcasecmp(conf.event_module_exclude[j], module_name))
                {
                  found_exclude++;
                  break;
                }
            }

          if (found_exclude)
            {
              if (conf.debug && conf.event_server_debug)
                {
                  Pthread_mutex_lock(&debug_output_mutex);
                  fprintf(stderr, "**************************************\n");
                  fprintf(stderr, "* Skip Event Module: %s\n", module_name);
                  fprintf(stderr, "**************************************\n");
                  Pthread_mutex_unlock(&debug_output_mutex);
                }
              CEREBROD_ERR(("Dropping event module: %s", module_name));
              continue;
            }
        }

      if (conf.debug && conf.event_server_debug)
        {
          Pthread_mutex_lock(&debug_output_mutex);
          fprintf(stderr, "**************************************\n");
          fprintf(stderr, "* Setup Event Module: %s\n", module_name);
          fprintf(stderr, "**************************************\n");
          Pthread_mutex_unlock(&debug_output_mutex);
        }

      if (event_module_setup(event_handle, i) < 0)
        {
          CEREBROD_DBG(("event_module_setup failed: %s", module_name));
          continue;
        }

      if (!(module_metric_names = event_module_metric_names(event_handle, i)) < 0)
        {
          CEREBROD_DBG(("event_module_metric_names failed: %s", module_name));
          event_module_cleanup(event_handle, i);
          continue;
        }

      if (!(module_event_names = event_module_event_names(event_handle, i)) < 0)
        {
          CEREBROD_DBG(("event_module_event_names failed: %s", module_name));
          event_module_cleanup(event_handle, i);
          continue;
        }
      
      if ((timeout = event_module_timeout_length(event_handle, i)) < 0)
        {
          CEREBROD_DBG(("event_module_timeout_length failed: %s", module_name));
          event_module_cleanup(event_handle, i);
          continue;
        }

      event_module = Malloc(sizeof(struct cerebrod_event_module_info));
      event_module->metric_names = Strdup(module_metric_names);
      event_module->event_names = Strdup(module_event_names);
      event_module->index = i;
      Pthread_mutex_init(&(event_module->event_lock), NULL);

      /* The monitoring module may support multiple metrics */
          
      metricPtr = strtok_r(event_module->metric_names, ",", &metricbuf);
      while (metricPtr)
        {
          if (!(el = Hash_find(event_index, metricPtr)))
            {
              el = (struct cerebrod_event_module_list *)Malloc(sizeof(struct cerebrod_event_module_list));
              el->event_list = List_create((ListDelF)_cerebrod_event_module_info_destroy);
              Pthread_mutex_init(&(el->event_list_lock), NULL);

              List_append(el->event_list, event_module);
              Hash_insert(event_index, metricPtr, el);
              event_index_count++;
            }
          else
            List_append(el->event_list, event_module);
          
          metricPtr = strtok_r(NULL, ",", &metricbuf);
        }

      /* The monitoring module may support multiple event names */

      eventnamePtr = strtok_r(event_module->event_names, ",", &eventbuf);
      while (eventnamePtr)
        {
          if (!list_find_first(event_names,
                               (ListFindF)_cerebrod_name_strcmp,
                               eventnamePtr))
            {
              List_append(event_names, eventnamePtr);
              if (conf.debug && conf.event_server_debug)
                {
                  Pthread_mutex_lock(&debug_output_mutex);
                  fprintf(stderr, "**************************************\n");
                  fprintf(stderr, "* Event Name: %s\n", eventnamePtr);
                  fprintf(stderr, "**************************************\n");
                  Pthread_mutex_unlock(&debug_output_mutex);
                }
            }
          eventnamePtr = strtok_r(NULL, ",", &eventbuf);
        }

      if (timeout)
        {
          struct cerebrod_event_module_timeout_data *mtd;
          List modules_list;

          if (!(mtd = List_find_first(event_module_timeouts, 
                                      _event_module_timeout_data_find_callback, 
                                      &timeout)))
            {
              char strbuf[64];

              mtd = (struct cerebrod_event_module_timeout_data *)Malloc(sizeof(struct cerebrod_event_module_timeout_data));
              mtd->timeout = timeout;
              snprintf(strbuf, 64, "%d", timeout);
              mtd->timeout_str = Strdup(strbuf);

              List_append(event_module_timeouts, mtd);
              
              if (timeout < event_module_timeout_min)
                event_module_timeout_min = timeout;
            }

          if (!(modules_list = Hash_find(event_module_timeout_index, 
                                         mtd->timeout_str)))
            {
              modules_list = List_create((ListDelF)NULL);
              List_append(modules_list, event_module);
              Hash_insert(event_module_timeout_index,
                          mtd->timeout_str,
                          modules_list);
            }
          else
            List_append(modules_list, event_module);
        }
    }
  
  List_sort(event_module_timeouts, _event_module_timeout_data_compare);

  if (!event_index_count)
    goto cleanup;

  if (_setup_event_node_timeout_data() < 0)
    goto cleanup;

  /* 
   * Since the cerebrod listener is started before any of the event
   * threads (node_timeout, queue_monitor, server), this must be
   * created in here (which is called by the listener) to avoid a
   * possible race of modules creating events before the event_queue
   * is created.
   */
  event_queue = List_create((ListDelF)cerebrod_event_to_send_destroy);

  return 1;
  
 cleanup:
  if (event_handle)
    {
      event_modules_unload(event_handle);
      event_handle = NULL;
    }
  if (event_index)
    {
      Hash_destroy(event_index);
      event_index = NULL;
    }
  if (event_names)
    {
      List_destroy(event_names);
      event_names = NULL;
    }
  return 0;
}
/* 
 * Under almost any circumstance, don't return a -1 error, cerebro can
 * go on without loading monitor modules. The listener_data_init_lock
 * should already be set.
 */
int
cerebrod_monitor_modules_setup(void)
{
  int i, monitor_module_count, monitor_index_len, monitor_index_count = 0;
  struct cerebrod_monitor_module_list *ml = NULL;
#if CEREBRO_DEBUG
  int rv;
#endif /* CEREBRO_DEBUG */

#if CEREBRO_DEBUG
  /* Should be called with lock already set */
  rv = Pthread_mutex_trylock(&listener_data_init_lock);
  if (rv != EBUSY)
    CEREBROD_EXIT(("mutex not locked: rv=%d", rv));
#endif /* CEREBRO_DEBUG */

  if (!(monitor_handle = monitor_modules_load()))
    {
      CEREBROD_DBG(("monitor_modules_load"));
      goto cleanup;
    }

  if ((monitor_module_count = monitor_modules_count(monitor_handle)) < 0)
    {
      CEREBROD_DBG(("monitor_modules_count"));
      goto cleanup;
    }

  if (!monitor_module_count)
    {
      if (conf.debug && conf.listen_debug)
        {
          Pthread_mutex_lock(&debug_output_mutex);
          fprintf(stderr, "**************************************\n");
          fprintf(stderr, "* No Monitor Modules Found\n");
          fprintf(stderr, "**************************************\n");
          Pthread_mutex_unlock(&debug_output_mutex);
        }
      goto cleanup;
    }
  
  /* Each monitor module may wish to monitor multiple metrics.  We'll
   * assume there will never be more than 2 metrics per monitor module, and
   * that will be enough to avoid all hash collisions.
   */
  monitor_index_len = monitor_module_count * 2;

  monitor_index = Hash_create(monitor_index_len, 
                              (hash_key_f)hash_key_string, 
                              (hash_cmp_f)strcmp, 
                              (hash_del_f)_cerebrod_monitor_module_list_destroy);

  for (i = 0; i < monitor_module_count; i++)
    {
      struct cerebrod_monitor_module_info *monitor_module;
      char *module_name, *metric_names;
      char *metricPtr, *metricbuf;

      module_name = monitor_module_name(monitor_handle, i);

      if (conf.monitor_module_exclude_len)
        {
          int found_exclude = 0;
          int j;

          for (j = 0; j < conf.monitor_module_exclude_len; j++)
            {
              if (!strcasecmp(conf.monitor_module_exclude[j], module_name))
                {
                  found_exclude++;
                  break;
                }
            }

          if (found_exclude)
            {
              if (conf.debug && conf.listen_debug)
                {
                  Pthread_mutex_lock(&debug_output_mutex);
                  fprintf(stderr, "**************************************\n");
                  fprintf(stderr, "* Skip Monitor Module: %s\n", module_name);
                  fprintf(stderr, "**************************************\n");
                  Pthread_mutex_unlock(&debug_output_mutex);
                }
              CEREBROD_ERR(("Dropping monitor module: %s", module_name));
              continue;
            }
        }

      if (conf.debug && conf.listen_debug)
        {
          Pthread_mutex_lock(&debug_output_mutex);
          fprintf(stderr, "**************************************\n");
          fprintf(stderr, "* Setup Monitor Module: %s\n", module_name);
          fprintf(stderr, "**************************************\n");
          Pthread_mutex_unlock(&debug_output_mutex);
        }

      if (monitor_module_setup(monitor_handle, i) < 0)
        {
          CEREBROD_DBG(("monitor_module_setup failed: %s", module_name));
          continue;
        }

      if (!(metric_names = monitor_module_metric_names(monitor_handle, i)) < 0)
        {
          CEREBROD_DBG(("monitor_module_metric_names failed: %s", module_name));
          monitor_module_cleanup(monitor_handle, i);
          continue;
        }

      monitor_module = Malloc(sizeof(struct cerebrod_monitor_module_info));
      monitor_module->metric_names = Strdup(metric_names);
      monitor_module->index = i;
      Pthread_mutex_init(&(monitor_module->monitor_lock), NULL);

      /* The monitoring module may support multiple metrics */
          
      metricPtr = strtok_r(monitor_module->metric_names, ",", &metricbuf);
      while (metricPtr)
        {
          if (!(ml = Hash_find(monitor_index, metricPtr)))
            {
              ml = (struct cerebrod_monitor_module_list *)Malloc(sizeof(struct cerebrod_monitor_module_list));
              ml->monitor_list = List_create((ListDelF)_cerebrod_monitor_module_info_destroy);
              Pthread_mutex_init(&(ml->monitor_list_lock), NULL);

              List_append(ml->monitor_list, monitor_module);
              Hash_insert(monitor_index, metricPtr, ml);
              monitor_index_count++;
            }
          else
            List_append(ml->monitor_list, monitor_module);
          
          metricPtr = strtok_r(NULL, ",", &metricbuf);
        }
    }

  if (!monitor_index_count)
    goto cleanup;

  return 1;

 cleanup:
  if (monitor_handle)
    {
      monitor_modules_unload(monitor_handle);
      monitor_handle = NULL;
    }
  if (monitor_index)
    {
      Hash_destroy(monitor_index);
      monitor_index = NULL;
    }
  return 0;
}
Beispiel #8
0
int
main(void)
{
    Lexer_source_T ls;
    Lexer_T l;
    Config_parser_T cp;
    Config_T c;
    struct Greyd_state gs;
    struct Con con;
    Blacklist_T bl1, bl2, bl3;
    struct sockaddr_storage src;
    int ret;
    char *conf =
        "hostname = \"greyd.org\"\n"
        "banner   = \"greyd IP-based SPAM blocker\"\n"
        "section grey {\n"
        "  enable           = 1,\n"
        "  traplist_name    = \"test traplist\",\n"
        "  traplist_message = \"you have been trapped\",\n"
        "  grey_expiry      = 3600,\n"
        "  stutter          = 15\n"
        "}\n"
        "section firewall {\n"
        "  driver = \"../drivers/fw_dummy.so\"\n"
        "}\n"
        "section database {\n"
        "  driver = \"../drivers/bdb.so\",\n"
        "  path   = \"/tmp/greyd_test_grey.db\"\n"
        "}";

    /* Empty existing database file. */
    ret = unlink("/tmp/greyd_test_grey.db");
    if(ret < 0 && errno != ENOENT) {
        printf("Error unlinking test Berkeley DB: %s\n", strerror(errno));
    }

    TEST_START(49);

    c = Config_create();
    ls = Lexer_source_create_from_str(conf, strlen(conf));
    l = Config_lexer_create(ls);
    cp = Config_parser_create(l);
    Config_parser_start(cp, c);

    /*
     * Set up the greyd state and blacklists.
     */
    memset(&gs, 0, sizeof(gs));
    gs.config = c;
    gs.max_cons = 4;
    gs.max_black = 4;
    gs.blacklists = Hash_create(5, NULL);

    bl1 = Blacklist_create("blacklist_1", "You (%A) are on blacklist 1", BL_STORAGE_TRIE);
    bl2 = Blacklist_create("blacklist_2", "You (%A) are on blacklist 2", BL_STORAGE_TRIE);
    bl3 = Blacklist_create("blacklist_3_with_an_enormously_big_long_long_epic_epicly_long_large_name",
                           "Your address %A\\nis on blacklist 3", BL_STORAGE_TRIE);

    Hash_insert(gs.blacklists, bl1->name, bl1);
    Hash_insert(gs.blacklists, bl2->name, bl2);
    Hash_insert(gs.blacklists, bl3->name, bl3);

    Blacklist_add(bl1, "10.10.10.1/32");
    Blacklist_add(bl1, "10.10.10.2/32");

    Blacklist_add(bl2, "10.10.10.1/32");
    Blacklist_add(bl2, "10.10.10.2/32");
    Blacklist_add(bl2, "2001::fad3:1/128");

    Blacklist_add(bl3, "10.10.10.2/32");
    Blacklist_add(bl3, "10.10.10.3/32");
    Blacklist_add(bl3, "2001::fad3:1/128");

    /*
     * Start testing the connection management.
     */
    memset(&src, 0, sizeof(src));
    ((struct sockaddr_in *) &src)->sin_family = AF_INET;
    inet_pton(AF_INET, "10.10.10.1", &((struct sockaddr_in *) &src)->sin_addr);

    memset(&con, 0, sizeof(con));
    Con_init(&con, 0, &src, &gs);

    TEST_OK(con.state == 0, "init state ok");
    TEST_OK(con.last_state == 0, "last state ok");
    TEST_OK(List_size(con.blacklists) == 2, "blacklist matches ok");
    TEST_OK(!strcmp(con.src_addr, "10.10.10.1"), "src addr ok");
    TEST_OK(con.out_buf != NULL, "out buf ok");
    TEST_OK(con.out_p == con.out_buf, "out buf pointer ok");
    TEST_OK(con.out_size == CON_OUT_BUF_SIZE, "out buf size ok");
    TEST_OK(!strcmp(con.lists, "blacklist_1 blacklist_2"),
            "list summary ok");

    /* The size of the banner. */
    TEST_OK(con.out_remaining == 75, "out buf remaining ok");

    TEST_OK(gs.clients == 1, "clients ok");
    TEST_OK(gs.black_clients == 1, "blacklisted clients ok");

    /*
     * Test the closing of a connection.
     */
    Con_close(&con, &gs);
    TEST_OK(List_size(con.blacklists) == 0, "blacklist empty ok");
    TEST_OK(con.out_buf == NULL, "out buf ok");
    TEST_OK(con.out_p == NULL, "out buf pointer ok");
    TEST_OK(con.out_size == 0, "out buf size ok");
    TEST_OK(con.lists == NULL, "lists ok");

    TEST_OK(gs.clients == 0, "clients ok");
    TEST_OK(gs.black_clients == 0, "blacklisted clients ok");

    /* Test recycling a connection. */
    memset(&src, 0, sizeof(src));
    ((struct sockaddr_in6 *) &src)->sin6_family = AF_INET6;
    inet_pton(AF_INET6, "2001::fad3:1", &((struct sockaddr_in6 *) &src)->sin6_addr);

    Con_init(&con, 0, &src, &gs);

    TEST_OK(con.state == 0, "init state ok");
    TEST_OK(con.last_state == 0, "last state ok");
    TEST_OK(List_size(con.blacklists) == 2, "blacklist matches ok");
    TEST_OK(!strcmp(con.src_addr, "2001::fad3:1"), "src addr ok");
    TEST_OK(con.out_buf != NULL, "out buf ok");
    TEST_OK(con.out_p == con.out_buf, "out buf pointer ok");
    TEST_OK(con.out_size == CON_OUT_BUF_SIZE, "out buf size ok");

    /*
     * As the 3rd blacklist's name is really long, the summarize lists
     * function should truncate with a "...".
     */
    TEST_OK(!strcmp(con.lists, "blacklist_2 ..."),
            "list summary ok");

    /* The size of the banner. */
    TEST_OK(con.out_remaining == 75, "out buf remaining ok");

    TEST_OK(gs.clients == 1, "clients ok");
    TEST_OK(gs.black_clients == 1, "blacklisted clients ok");

    /*
     * Test the rejection message building.
     */
    Con_build_reply(&con, "451");
    TEST_OK(!strcmp(con.out_p,
                    "451-You (2001::fad3:1) are on blacklist 2\n"
                    "451-Your address 2001::fad3:1\n"
                    "451 is on blacklist 3\n"),
            "Blacklisted error response ok");
    TEST_OK(con.out_remaining == 94, "out buf remaining ok");

    /*
     * Test the writing of the buffer without stuttering.
     */
    time_t now = time(NULL);
    int con_pipe[2], to_write, nread;
    char in[CON_OUT_BUF_SIZE];

    pipe(con_pipe);
    con.fd = con_pipe[1];
    con.w = now;
    to_write = con.out_remaining;
    Con_handle_write(&con, &now, &gs);
    nread = read(con_pipe[0], in, to_write);
    in[nread] = '\0';

    TEST_OK(!strcmp(in,
                    "451-You (2001::fad3:1) are on blacklist 2\n"
                    "451-Your address 2001::fad3:1\n"
                    "451 is on blacklist 3\n"),
            "Con write without stuttering ok");

    /*
     * Test the writing with stuttering. Note the reply is longer due
     * to the stutering adding in \r before each \n if there isn't one
     * already.
     */
    Con_build_reply(&con, "451");
    gs.max_cons = 100;
    gs.max_black = 100;
    con.w = now;
    while(con.out_remaining > 0) {
        Con_handle_write(&con, &now, &gs);
        now += con.stutter + 1;
    }

    memset(in, 0, sizeof(in));
    nread = read(con_pipe[0], in, to_write + 3);
    in[nread] = '\0';

    TEST_OK(!strcmp(in,
                    "451-You (2001::fad3:1) are on blacklist 2\r\n"
                    "451-Your address 2001::fad3:1\r\n"
                    "451 is on blacklist 3\r\n"),
            "Con write with stuttering ok");

    /* Test recycling a connection, which is not on a blacklist. */
    Con_close(&con, &gs);
    memset(&src, 0, sizeof(src));
    ((struct sockaddr_in6 *) &src)->sin6_family = AF_INET6;
    inet_pton(AF_INET6, "fa40::fad3:1", &((struct sockaddr_in6 *) &src)->sin6_addr);

    Con_init(&con, 0, &src, &gs);
    TEST_OK(List_size(con.blacklists) == 0, "not on blacklist ok");

    /*
     * Note custom error codes only apply for blacklist connections, so expect
     * a 451 for this greylisted connections.
     */
    Con_build_reply(&con, "551");
    TEST_OK(!strcmp(con.out_p,
                    "451 Temporary failure, please try again later.\r\n"),
            "greylisted error response ok");
    Con_close(&con, &gs);
    List_destroy(&con.blacklists);

    /*
     * Test the connection reading function.
     */
    memset(&src, 0, sizeof(src));
    ((struct sockaddr_in *) &src)->sin_family = AF_INET;
    inet_pton(AF_INET, "10.10.10.1", &((struct sockaddr_in *) &src)->sin_addr);

    pipe(con_pipe);
    memset(&con, 0, sizeof(con));
    Con_init(&con, con_pipe[0], &src, &gs);

    char *out = "EHLO greyd.org\r\n";
    write(con_pipe[1], out, strlen(out));

    Con_handle_read(&con, &now, &gs); /* This should change the state. */
    TEST_OK(con.state == CON_STATE_HELO_IN, "Initial state set ok");
    Con_handle_read(&con, &now, &gs);
    TEST_OK(!strcmp(con.in_buf, "EHLO greyd.org"), "con read ok");
    TEST_OK(!strcmp(con.helo, "greyd.org"), "helo parsed ok");
    TEST_OK(con.state = CON_STATE_HELO_OUT, "state helo out ok");

    char *mail_from = "MAIL FROM: <*****@*****.**>\r\n";
    write(con_pipe[1], mail_from, strlen(mail_from));

    Con_next_state(&con, &now, &gs);
    TEST_OK(con.state = CON_STATE_MAIL_IN, "state mail in ok");
    Con_handle_read(&con, &now, &gs);
    TEST_OK(!strcmp(con.mail, "*****@*****.**"), "MAIL FROM parsed ok");
    TEST_OK(con.state = CON_STATE_MAIL_OUT, "state mail out ok");

    char *rcpt = "RCPT TO: [email protected]\r\n";
    write(con_pipe[1], rcpt, strlen(rcpt));

    Con_next_state(&con, &now, &gs);
    TEST_OK(con.state = CON_STATE_RCPT_IN, "state rcpt in ok");
    Con_handle_read(&con, &now, &gs);
    TEST_OK(!strcmp(con.rcpt, "*****@*****.**"), "RCPT parsed ok");
    TEST_OK(con.state = CON_STATE_RCPT_OUT, "state rcpt out ok");

    char *data = "DATA\r\n";
    write(con_pipe[1], data, strlen(data));

    Con_next_state(&con, &now, &gs);
    TEST_OK(con.state = CON_STATE_RCPT_IN, "state rcpt in ok"); /* this will goto spam. */
    Con_handle_read(&con, &now, &gs);
    TEST_OK(con.state = CON_STATE_DATA_OUT, "state data out ok");

    char *msg = "This is a spam message\r\ndeliver me!\r\n.\r\n";
    write(con_pipe[1], msg, strlen(msg));

    Con_next_state(&con, &now, &gs);
    TEST_OK(con.state = CON_STATE_MESSAGE, "state message ok");
    Con_handle_read(&con, &now, &gs);
    TEST_OK(con.state = CON_STATE_CLOSE, "state close ok");

    /* This should close the connection. */
    Con_next_state(&con, &now, &gs);

    /* Cleanup. */
    List_destroy(&con.blacklists);
    Hash_destroy(&gs.blacklists);
    Blacklist_destroy(&bl1);
    Blacklist_destroy(&bl2);
    Blacklist_destroy(&bl3);
    Config_destroy(&c);
    Config_parser_destroy(&cp);

    TEST_COMPLETE;
}