Beispiel #1
0
static void
dbt_close(dbt_t *dbt)
{
	if (!dbt->dbt_open)
	{
		return;
	}

	if ((dbt->dbt_driver->dd_flags & DBT_LOCK))
	{
		if(pthread_mutex_destroy(&dbt->dbt_mutex))
		{
			log_sys_error("dbt_close: ptrhead_mutex_destroy");
		}
	}

	DBT_DB_CLOSE(dbt);

	if (dbt->dbt_scheme)
	{
		var_delete(dbt->dbt_scheme);
	}

	dbt->dbt_open = 0;

	return;
}
   /*
     Takes a char pointer "key", variable "value", and "user_data" (not used),
     and frees the memory for "key" and "value"
   */
static gboolean
var_free_func( gpointer key,
               gpointer value,
               gpointer user_data ) {
  free( key );
  var_delete( (variable*)value, (fb_allocator*)user_data );
  return TRUE;
}
Beispiel #3
0
void
cf_clear(void)
{
	if (cf_config)
	{
		var_delete(cf_config);
	}

	return;
}
Beispiel #4
0
var_t *
vlist_record_from_table(var_t *scheme, var_t *table)
{
	var_t *record = NULL, *vs, *vt;
	ll_t *ll;
	ll_entry_t *pos;
	void *data;
	var_type_t type;
	char *name;

	record = vlist_create(scheme->v_name, VF_KEEPNAME);
	if (record == NULL)
	{
		log_warning("vlist_record: vlist_create failed");
		return NULL;
	}

	ll = scheme->v_data;
	pos = LL_START(ll);

	while ((vs = ll_next(ll, &pos)))
	{
		name = vs->v_name;
		vt = vtable_lookup(table, name);

		if (vt == NULL && vs->v_flags & VF_KEY)
		{
			log_error("vlist_record_from_table: \"%s\" is missing "
				"in vtable and declared as key", name);
			goto error;
		}


		data = vt == NULL ? NULL : vt->v_data;
		type = vt == NULL ? VT_NULL : vt->v_type;

		if (vlist_append_new(record, type, name, data,
			VF_COPY | vs->v_flags) == -1)
		{
			log_warning("vlist_record_from_table: vlist_append_new"
				" failed");
			goto error;
		}
	}

	return record;

error:
	if(record)
	{
		var_delete(record);
	}
	return NULL;
}
Beispiel #5
0
static int
hitlist_sql_safe_update(hitlist_t *hl, var_t *record, char *value_field,
    VAR_INT_T value_diff, char *expire_field, VAR_INT_T expire_diff)
{
	var_t *record_copy = NULL;
	var_t *field;
	int status = -1;

	record_copy = VAR_COPY(record);
	if (record_copy == NULL)
	{
		log_error("hitlist_sql_safe_update: var_copy failed");
		goto exit;
	}

	field = vlist_record_lookup(record_copy, value_field);
	if (field == NULL)
	{
		log_error("hitlist_sql_safe_update: vlist_get for %s failed", value_field);
		goto exit;
	}
	* (VAR_INT_T *) field->v_data = value_diff;
	field->v_flags |= VF_SQL_SAFE_UPDATE;

	field = vlist_record_lookup(record_copy, expire_field);
	if (field == NULL)
	{
		log_error("hitlist_sql_safe_update: vlist_get for %s failed", expire_field);
		goto exit;
	}
	* (VAR_INT_T *) field->v_data = expire_diff;
	field->v_flags |= VF_SQL_SAFE_UPDATE;

	status = dbt_db_set(&hl->hl_dbt, record_copy);
	if (status)
	{
		log_error("hitlist_sql_safe_update: dbt_db_set failed");
	}

exit:
	if (record_copy)
	{
		var_delete(record_copy);
	}

	return status;
}
Beispiel #6
0
var_t *
vlist_record(var_t *scheme, ...)
{
	va_list ap;
	var_t *record = NULL, *v;
	int flags;
	void *data;
	ll_t *ll;
	ll_entry_t *pos;

	va_start(ap, scheme);

	record = vlist_create(scheme->v_name, VF_KEEPNAME);
	if (record == NULL)
	{
		log_warning("vlist_record: vlist_create failed");
		return NULL;
	}

	ll = scheme->v_data;
	pos = LL_START(ll);

	while ((v = ll_next(ll, &pos)))
	{
		flags = v->v_flags;
		flags |= VF_KEEPDATA | VF_KEEPNAME;
		flags &= ~(VF_COPYNAME | VF_COPYDATA);

		data = va_arg(ap, void *);

		if (vlist_append_new(record, v->v_type, v->v_name, data, flags)
		    == -1)
		{
			log_warning("vlist_record: vlist_append_new failed");

			var_delete(record);
			return NULL;
		}
	}

	va_end(ap);

	return record;
}
Beispiel #7
0
var_t *
vlist_scheme(char *scheme, ...)
{
	va_list ap;
	var_t *record = NULL;
	var_type_t type;
	int flags;
	char *name;
	
	record = vlist_create(scheme, VF_COPYNAME);
	if (record == NULL)
	{
		log_warning("vlist_scheme: vlist_create failed");
		return NULL;
	}

	va_start(ap, scheme);

	for (;;)
	{
		name = va_arg(ap, char *);
		if (name == NULL)
		{
			break;
		}

		type = va_arg(ap, var_type_t);
		flags = va_arg(ap, int);

		if (vlist_append_new(record, type, name, NULL, flags) == -1)
		{
			log_warning("vlist_scheme: vlist_append_new failed");

			var_delete(record);
			return NULL;
		}
	}

	va_end(ap);

	return record;
}
Beispiel #8
0
static var_t *
exp_eval_list(exp_t *exp, var_t *mailspec)
{
	ll_t *exp_list = exp->ex_data;
	ll_entry_t *pos;
	exp_t *exp_item;
	var_t *var_item, *var_list = NULL;

	var_list = vlist_create(NULL, VF_EXP_FREE);
	if (var_list == NULL)
	{
		log_sys_error("exp_eval_list: malloc");
		goto error;
	}

	pos = LL_START(exp_list);
	while ((exp_item = ll_next(exp_list, &pos)))
	{
		var_item = exp_eval(exp_item, mailspec);

		if (vlist_append(var_list, var_item))
		{
			log_sys_error("exp_eval_list: malloc");
			goto error;
		}
	}

	return var_list;


error:

	if (var_list)
	{
		var_delete(var_list);
	}

	return NULL;
}
Beispiel #9
0
void
exp_free(var_t *v)
{
	if (v == NULL)
	{
		return;
	}

	if ((v->v_flags & VF_EXP_FREE) == 0)
	{
		return;
	}

	if (v->v_type == VT_LIST && v->v_data != NULL)
	{
		ll_delete(v->v_data, (void *) exp_free);
		v->v_data = NULL;
	}

	var_delete(v);

	return;
}
Beispiel #10
0
int
vlist_append_new(var_t *list, var_type_t type, char *name, void *data,
    int flags)
{
	var_t *v;

	v = var_create(type, name, data, flags);
	if (v == NULL)
	{
		log_warning("vlist_append_new: var_create failed");
		return -1;
	}

	if (vlist_append(list, v) == 0)
	{
		return 0;
	}

	log_error("vlist_append_new: vlist_append failed");
	var_delete(v);

	return -1;
}
Beispiel #11
0
void
exp_delete(exp_t *exp)
{
	switch (exp->ex_type)
	{
	case EX_PARENTHESES:
		break;

	case EX_CONSTANT:
		var_delete(exp->ex_data);
		break;

	case EX_FUNCTION:
		exp_function_delete(exp);
		break;

	case EX_LIST:
		ll_delete(exp->ex_data, NULL);
		break;

	case EX_SYMBOL:
	case EX_VARIABLE:
	case EX_OPERATION:
	case EX_TERNARY_COND:
	case EX_MACRO:
		free(exp->ex_data);
		break;

	default:
		log_die(EX_SOFTWARE, "exp_delete: bad type");
	}
		
	free(exp);

	return;
}
Beispiel #12
0
static var_t *
exp_eval_function_simple(char *name, acl_function_t *af, ll_t *args)
{
	ll_t garbage;
	ll_entry_t *pos;
	void **argv = NULL;
	var_t *v = NULL;
	int argc;
	int size;
	int i;
	var_t *arg;

	/*
	 * Initialize garbage
	 */
	ll_init(&garbage);

	/*
	 * Check argc
	 */
	argc = args->ll_size;
	if (argc != af->af_argc)
	{
		log_error("exp_eval_function_simple: function \"%s\" requires "
		    "%d arguments", name, af->af_argc);
		return NULL;
	}

	size = (argc + 1) * sizeof (void *);

	argv = (void **) malloc(size);
	if (argv == NULL)
	{
		log_sys_error("exp_eval_function_simple: malloc");
		return NULL;
	}

	memset(argv, 0, size);

	/*
	 * Prepare argv
	 */
	pos = LL_START(args);
	for (i = 0; (arg = ll_next(args, &pos)); ++i)
	{
		if (af->af_types[i] == arg->v_type)
		{
			argv[i] = arg->v_data;
			continue;
		}

		/*
		 * Type casting required. Don't care about the remains of arg
		 * (freed with args!).
		 */
		arg = var_cast_copy(af->af_types[i], arg);
		if (arg == NULL)
		{
			log_error("exp_eval_function_simple: var_cast_copy "
			    "failed");

			goto error;
		}

		/*
		 * Need to free copy later
		 */
		if (LL_INSERT(&garbage, arg) == -1)
		{
			log_error("exp_eval_function_simlpe: LL_INSERT "
			    "failed");

			var_delete(arg);
			goto error;
		}

		argv[i] = arg->v_data;
	}

	v = af->af_callback.fc_simple(argc, argv);


error:
	ll_clear(&garbage, (ll_delete_t) var_delete);

	if (argv)
	{
		free(argv);
	}

	return v;
}
Beispiel #13
0
static var_t *
exp_eval_regex(int op, var_t *left, var_t *right)
{
	char *p;
	int e;
	char error[1024];
	int flags = REG_EXTENDED | REG_NOSUB;
	int match;
	regex_t r;

	var_t *pattern_copy = NULL;
	var_t *str_copy = NULL;

	char *pattern;
	char *str;

	if (left == NULL || right == NULL)
	{
		return EXP_EMPTY;
	}

	if (left->v_data == NULL || right->v_data == NULL)
	{
		log_debug("exp_eval_regex: empty value");
		return EXP_EMPTY;
	}

	// Make sure left is a string
	if (left->v_type != VT_STRING)
	{
		str_copy = var_cast_copy(VT_STRING, left);
		if (str_copy == NULL)
		{
			log_error("exp_eval_regex: var_cast_copy failed");
			goto error;
		}
		str = str_copy->v_data;
	}
	else
	{
		str = left->v_data;
	}

	// Make sure right is a string
	if (right->v_type != VT_STRING)
	{
		pattern_copy = var_cast_copy(VT_STRING, right);
		if (pattern_copy == NULL)
		{
			log_error("exp_eval_regex: var_cast_copy failed");
			goto error;
		}
		pattern = pattern_copy->v_data;
	}
	else
	{
		pattern = right->v_data;
	}

	// Test if pattern contains upper case chars
	flags = REG_EXTENDED | REG_NOSUB;
	for (p = pattern; *p; ++p)
	{
		if (isupper((int) *p))
		{
			break;
		}
	}
	// If pattern is all lower perform case insensitiv matching
	if(*p == 0)
	{
		flags |= REG_ICASE;
	}

	e = regcomp(&r, pattern, flags);
	if (e)
	{
		regerror(e, &r, error, sizeof error);
		log_error("exp_eval_regex: regcomp: %s", error);
		goto error;
	}

	// Regexec returns 0 if pattern matched.
	match = regexec(&r, str, 0, NULL, 0);

	// free memory
	regfree(&r);
	if (str_copy)
	{
		var_delete(str_copy);
	}
	if (pattern_copy)
	{
		var_delete(pattern_copy);
	}

	if (op == NR)
	{
		match = !match;
	}

	if (match)
	{
		return EXP_FALSE;
	}

	return EXP_TRUE;

error:
	if (str_copy)
	{
		var_delete(str_copy);
	}
	if (pattern_copy)
	{
		var_delete(pattern_copy);
	}

	return NULL;
}
Beispiel #14
0
static int
hitlist_lookup(milter_stage_t stage, char *name, var_t *attrs)
{
	hitlist_t *hl;
	var_t *lookup = NULL;
	var_t *record = NULL;
	VAR_INT_T *value;
	VAR_INT_T *expire;
	var_t *addition;
	int success = -1;

	// Used for SQL_SAFE_UPDATE
	VAR_INT_T value_diff = 0;
	VAR_INT_T expire_diff = 0;
	int update_record = 0;

	hl = sht_lookup(hitlists, name);
	if (hl == NULL)
	{
		log_error("Unknown hitlist: %s", name);
		goto exit;
	}

	// If the DB is not open yet, there's a race on hl->hl_connected.
	if (pthread_mutex_lock(&hl->hl_mutex))
	{
		log_sys_error("hitlist_db_open: pthread_mutex_lock");
		goto exit;
	}

	// Open Database
	if (!hl->hl_connected)
	{
		if (hitlist_db_open(hl, attrs))
		{
			log_error("hitlist_lookup: hitlist_db_open failed");
			goto exit;
		}
	}

	// Create lookup record
	lookup = hitlist_record(hl, attrs, 1);
	if (lookup == NULL)
	{
		// Happens if a key is not set or NULL
		log_error("hitlist_lookup: hitlist_record failed");
		vtable_set_null(attrs, name, VF_COPYNAME);

		// Failing would lead to acl termination
		success = 0;
		goto exit;
	}

	if (dbt_db_get(&hl->hl_dbt, lookup, &record))
	{
		log_error("hitlist_lookup: dbt_db_get failed");
		goto exit;
	}

	if (record == NULL)
	{
		// Add new record
		if (hl->hl_create)
		{
			log_debug("hitlist_lookup: %s add record", name);
			record = lookup;
			lookup = NULL;
		}
		// No record found, set symbol to null
		else
		{
			vtable_set_null(attrs, name, VF_COPYNAME);
			log_debug("hitlist_lookup: %s no record", name);
			success = 0;
			goto exit;
		}
	}
	else
	{
		update_record = 1;
		log_debug("hitlist_lookup: %s record found", name);
	}

	value = vlist_record_get(record, hl->hl_value_field);
	expire = vlist_record_get(record, hl->hl_expire_field);
	if (value == NULL || expire == NULL)
	{
		log_error("hitlist_lookup: vlist_record_get failed");
		goto exit;
	}

	// Count
       	if (hl->hl_count)
       	{
		++(*value);
		value_diff = 1;
       	}

	// Sum
        else if (hl->hl_sum)
        {
		addition = vtable_lookup(attrs, hl->hl_sum);
		if (addition == NULL)
		{
			log_error("hitlist: sum field %s is undefined", hl->hl_sum);
			goto exit;
		}

		if (addition->v_type != VT_INT)
		{
			log_error("hitlist: sum field %s must be integer", hl->hl_sum);
			goto exit;
		}

		*value += *(VAR_INT_T *) addition->v_data;
		value_diff = *(VAR_INT_T *) addition->v_data;
        }

        if (hl->hl_update)
        {

		// Record never expires
		if (hl->hl_timeout == 0)
		{
			*expire = INT_MAX;
			expire_diff = 0;
		}
		// Record is extended every time a match is found
		else if (hl->hl_extend)
		{
			*expire = time(NULL) + hl->hl_timeout;
			expire_diff = hl->hl_timeout;
		}
		// Record expires after a fixed timeout
		else if (hl->hl_timeout && *expire == 0)
		{
			*expire = time(NULL) + hl->hl_timeout;
			expire_diff = hl->hl_timeout;
		}

		// SQL_SAFE_UPDATE
		if(update_record && hl->hl_dbt.dbt_driver->dd_use_sql)
		{
			if (hitlist_sql_safe_update(hl, record, hl->hl_value_field, value_diff,
				hl->hl_expire_field, expire_diff))
			{
				log_error("hitlist_lookup: hitlist_sql_safe_update failed");
				goto exit;
			}
		}
		else
		{
			if (dbt_db_set(&hl->hl_dbt, record))
			{
				log_error("hitlist_lookup: dbt_db_set failed");
				goto exit;
			}
		}
        }

	// Add symbol
	if (vtable_set_new(attrs, VT_INT, name, value, VF_COPY))
	{
		log_error("hitlist_lookup: vtable_set_new failed");

		goto exit;
	}

	success = 0;

exit:
	if (lookup != NULL)
	{
		var_delete(lookup);
	}

	if (record != NULL)
	{
		var_delete(record);
	}

	if (pthread_mutex_unlock(&hl->hl_mutex))
	{
		log_sys_error("hitlist_db_open: pthread_mutex_unlock");
	}

	return success;
}
Beispiel #15
0
static var_t *
hitlist_record(hitlist_t *hl, var_t *attrs, int load_data)
{
	ll_entry_t *pos;
	var_t *key;
	char *keystr;
	var_t *v;
	var_t *schema = NULL;
	VAR_INT_T zero = 0;
	char *name;

	name = hl->hl_table? hl->hl_table: hl->hl_name;

	schema = vlist_create(name, VF_KEEPNAME);
	if (schema == NULL)
	{
		log_error("hitlist_schema: vlist_create failed");
		goto error;
	}

	pos = LL_START(hl->hl_keys);
	while ((key = ll_next(hl->hl_keys, &pos)))
	{
		// Impossible
		if (key->v_data == NULL)
		{
			log_error("hitlist_schema: key is NULL");
			goto error;
		}

		// Bad configured
		if (key->v_type != VT_STRING)
		{
			log_error("hitlist_scheme: bad configuration %s:"
				" hitlist keys must be strings.", hl->hl_name);
			goto error;
		}

		keystr = key->v_data;

		// Variables
		if (keystr[0] == '$')
		{
			v = acl_variable_get(attrs, keystr);
		}

		// Regular symbol
		else
		{
			v = acl_symbol_get(attrs, keystr);
		}

		if (v == NULL)
		{
			log_error("hitlist_scheme: %s: lookup %s failed",
				hl->hl_name, keystr);
			goto error;
		}

		if (load_data && v->v_data == NULL)
		{
			log_error("hitlist_scheme: %s: key %s is NULL",
				hl->hl_name, keystr);
			goto error;
		}

		if (vlist_append_new(schema, v->v_type, keystr,
			load_data? v->v_data: NULL, VF_COPY | VF_KEY))
		{
			log_error("hitlist_schema: %s: vlist_append_new"
				" failed", hl->hl_name);
			goto error;
		}
	}

	// Add value
	if (vlist_append_new(schema, VT_INT, hl->hl_value_field,
	    load_data? &zero: NULL, VF_COPY))
	{
		log_error("hitlist_schema: %s: vlist_append_new failed for %s",
			hl->hl_name, hl->hl_value_field);
		goto error;
	}

	if (vlist_append_new(schema, VT_INT, hl->hl_expire_field,
	    load_data? &zero: NULL, VF_COPY))
	{
		log_error("hitlist_schema: %s: vlist_append_new failed for %s",
			hl->hl_name, hl->hl_expire_field);
		goto error;
	}

	return schema;

error:
	if (schema != NULL)
	{
		var_delete(schema);
	}

	return NULL;
}
Beispiel #16
0
int
spamd_query(milter_stage_t stage, char *name, var_t *attrs)
{
	int sock = 0;
	var_t *symbols = NULL;
	int n;
	char recv_header[BUFLEN];
	char buffer[BUFLEN];
	char *p, *q;
	char *message = NULL;
	VAR_INT_T spam;
	VAR_FLOAT_T score;
	VAR_INT_T *message_size;
	long header_size, size;

	/*
         * Build received header
         */
	header_size = spamd_header(attrs, recv_header, sizeof recv_header);
	if (header_size == -1)
	{
		log_error("spamd_query: spamd_header failed");
		goto error;
	}

	log_debug("spamd_query: received header:\n%s", recv_header);

	/*
         * Get message size
         */
	message_size = vtable_get(attrs, "message_size");
	if (message_size == NULL)
	{
		log_error("spamd_query: vtable_get failed");
		goto error;
	}

	size = header_size + *message_size;

	/*
	 * Allocate message buffer
	 */
	message = (char *) malloc(size + 1);
	if (message == NULL)
	{
		log_sys_error("spamd_query: malloc");
		goto error;
	}

	/*
	 * Dump message
	 */
	memcpy(message, recv_header, header_size);
	if (milter_dump_message(message + header_size, size - header_size + 1,
	    attrs) == -1)
	{
		log_error("spamd_query: milter_dump_message failed");
		goto error;
	}

	snprintf(buffer, sizeof(buffer), "SYMBOLS SPAMC/1.2\r\n"
	    "Content-length: %ld\r\n\r\n", size);

	sock = sock_connect_rr(&spamd_srr);
	if (sock == -1)
	{
		log_error("spamd_query: sock_connect failed");
		goto error;
	}
	 
	 /*
	  * Write spamassassin request
	  */
	if (write(sock, buffer, strlen(buffer)) == -1) {
		log_sys_error("spamd_query: write");
		goto error;
	}

	/*
	 * Write message
	 */
	if (write(sock, message, size) == -1) {
		log_sys_error("spamd_query: write");
		goto error;
	}

	/*
	 * Read response
	 */
	n = read(sock, buffer, sizeof buffer - 1);
	if (n == -1) {
		log_sys_error("spamd_query: read");
		goto error;
	}
	buffer[n] = 0;

	/*
	 * No answer. Seen when spamd ran out of mem.
         */
	if (n == 0)
	{
		log_error("spamd_query: no data received");
		goto error;
	}

	/*
	 * Parse response
	 */
	p = buffer;
	if (strncmp(p, SPAMD_SPAMD, SPAMD_SPAMDLEN)) {
		spamd_printable_buffer(p, SPAMD_SPAMDLEN + 1);
		log_error("spamd_query: protocol error: expected='%s' "
			"received='%s'", SPAMD_SPAMD, p);
		goto error;
	}

	p += SPAMD_SPAMDLEN;
	if (strncmp(p, SPAMD_VERSION, SPAMD_VERSIONLEN)) {
		spamd_printable_buffer(p, SPAMD_VERSIONLEN + 1);
		log_notice("spamd_query: protocol version mismtach: "
			"expected='%s' received='%s'", SPAMD_VERSION, p);
	}

	p += SPAMD_VERSIONLEN;
	p = strstr(p, SPAMD_EX_OK);
	if(p == NULL) {
		log_error("spamd_query: spamd returned non EX_OK");
		goto error;
	}

	/*
	 * Spamd returns 2 lines. Read 2nd line if neccessary.
	 */
	p += SPAMD_EX_OKLEN;
	if(strlen(p) <= 2) {  /* '\r\n' */

		n = read(sock, buffer, sizeof(buffer));
		if (n == -1) {
			log_sys_error("spamd_query: read");
			goto error;
		}

		buffer[n] = 0;
		p = buffer;
	}
	else {
		p += 2;  /* '\r\n' */
	}

	/*
	 * Parse results
	 */
	if(strncmp(p, SPAMD_SPAM, SPAMD_SPAMLEN)) {
		spamd_printable_buffer(p, SPAMD_SPAMLEN + 1);
		log_error("spamd_query: protocol error: expected='%s' "
			"received='%s'", SPAMD_SPAM, p);
		goto error;
	}

	p += SPAMD_SPAMLEN;
	if(!strncmp(p, SPAMD_TRUE, SPAMD_TRUELEN)) {
		spam = 1;
		p += SPAMD_TRUELEN;
	}
	else if(!strncmp(p, SPAMD_FALSE, SPAMD_FALSELEN)) {
		spam = 0;
		p += SPAMD_FALSELEN;
	}
	else
	{
		spamd_printable_buffer(p, (SPAMD_TRUELEN > SPAMD_FALSELEN ?
			SPAMD_TRUELEN: SPAMD_FALSELEN) + 1);
		log_error("spamd_query: protocol error: expected: '%s|%s' "
			"received='%s'", SPAMD_TRUE, SPAMD_FALSE, p);
		goto error;
	}

	/*
	 * Cut score.
	 */
	q = strchr(p, ' ');
	if (q == NULL) {
		log_error("spamd_query: protocol error: couldn't find the "
			"next space character");
		goto error;
	}
	*q++ = 0;

	score = (VAR_FLOAT_T) strtod(p, NULL);

	/*
	 * Set SYMBOLS
	 */
	p = strchr(q, '\r');
	if (p == NULL) {
		log_error("spamd_query: protocol error: couldn't find the "
			"start of the spamd symbols");
		goto error;
	}
	p += 4; /* \r\n\r\n */

	q = strchr(p, '\r');
	if (q == NULL) {
		log_error("spamd_query: protocol error: couldn't find the "
			"end of the spamd symbols");
		goto error;
	}
	*q = 0;

	log_message(LOG_ERR, attrs, "spamd: spam=%d score=%.1f symbols=%s",
	    spam, score, p);

	symbols = var_create(VT_LIST, "spamd_symbols", NULL,
		VF_KEEPNAME | VF_CREATE);
	if (symbols == NULL) {
		log_error("spamd_query: var_create failed");
		goto error;
	}

	do {
		q = strchr(p, ',');
		if (q) {
			*q = 0;
		}

		if (vlist_append_new(symbols, VT_STRING, NULL, p,
			VF_COPYDATA) == -1) {
			log_error("spamd_query: vlist_append failed");
			goto error;
		}

		p = q + 1;
	} while (q);

	if (vtable_setv(attrs, VT_INT, "spamd_spam", &spam,
		VF_KEEPNAME | VF_COPYDATA, VT_FLOAT, "spamd_score", &score,
		VF_KEEPNAME | VF_COPYDATA, VT_NULL)) {
		log_error("spamd_query: vtable_setv failed");
		goto error;
	}

	if (vtable_set(attrs, symbols)) {
		log_error("spamd_query: vtable_set failed");
		goto error;
	}

	close(sock);

	free(message);

	return 0;

error:
	if (message)
	{
		free(message);
	}

	if (sock > 0) {
		close(sock);
	}

	if (symbols) {
		var_delete(symbols);
	}

	return -1;
}