mirror of https://github.com/rspamd/rspamd.git
Rapid spam filtering system
https://rspamd.com/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
506 lines
17 KiB
506 lines
17 KiB
/*
|
|
Copyright (c) 2013-2015, Alexey Savelyev <info@homeweb.ru>
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
|
|
Based on source code from http://www.ols.es/exim/dlext/ by David Saez <david@ols.es>,
|
|
source code of exim by Philip Hazel <ph10@cam.ac.uk>
|
|
and source code of exiscan by Tom Kistner <tom@duncanthrax.net>
|
|
and source code of Victor Ustugov http://mta.org.ua/
|
|
*/
|
|
|
|
#include "exim.h"
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <stdarg.h>
|
|
#include <errno.h>
|
|
#include "cJSON.h"
|
|
#include "cJSON.c"
|
|
|
|
#define RSPAMD_TIMEOUT 120
|
|
|
|
extern uschar *tod_stamp(int);
|
|
//extern BOOL split_spool_directory; /* TRUE to use multiple subdirs */
|
|
//extern uschar *spool_directory; /* Name of spool directory */
|
|
//extern uschar message_subdir[]; /* Subdirectory for messages */
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
int strpos(char *hay, char *needle, int offset)
|
|
{
|
|
char haystack[strlen(hay)];
|
|
strncpy(haystack, hay+offset, strlen(hay)-offset);
|
|
char *p = strstr(haystack, needle);
|
|
if (p)
|
|
return p - haystack+offset;
|
|
return -1;
|
|
}
|
|
|
|
int rspamd(uschar **yield, int argc, uschar *argv[]) {
|
|
char *arg_socket_addr;
|
|
char *arg_defer_ok;
|
|
int defer_ok;
|
|
int rspamd_command;
|
|
char tcp_addr[15];
|
|
int tcp_port;
|
|
FILE *mbox_file = NULL;
|
|
// unsigned long mbox_size;
|
|
off_t mbox_size;
|
|
uschar *s, *p;
|
|
header_line *my_header, *header_new, *header_last, *tmp_headerlist;
|
|
header_line *last_received = NULL;
|
|
uschar *address;
|
|
uschar *helo;
|
|
uschar *sender_host_name;
|
|
uschar *authenticated_id;
|
|
char mbox_path[512];
|
|
int max_len, len;
|
|
int rspamd_sock = 0;
|
|
struct hostent *he;
|
|
struct in_addr in;
|
|
struct sockaddr_un server;
|
|
#ifndef NO_POLL_H
|
|
int result;
|
|
struct pollfd pollfd;
|
|
#endif
|
|
int offset;
|
|
uschar spamd_buffer[32600];
|
|
uschar spamd_buffer2[32600];
|
|
time_t start;
|
|
size_t read, wrote;
|
|
int i, j, c;
|
|
|
|
arg_socket_addr = argv[0];
|
|
arg_defer_ok = argv[1];
|
|
|
|
if (argc < 2) {
|
|
defer_ok = 0;
|
|
} else if (
|
|
(strcmpic(arg_defer_ok,US"1") == 0)
|
|
|| (strcmpic(arg_defer_ok,US"yes") == 0)
|
|
|| (strcmpic(arg_defer_ok,US"true") == 0)
|
|
|| (strcmpic(arg_defer_ok,US"defer_ok") == 0)
|
|
) {
|
|
defer_ok = 1;
|
|
} else {
|
|
defer_ok = 0;
|
|
}
|
|
//debug_printf(" defer_ok: %d\n", defer_ok);
|
|
|
|
if ((arg_socket_addr == NULL) || (arg_socket_addr[0] == 0)) {
|
|
log_write(0, LOG_MAIN|LOG_PANIC,
|
|
"rspamd dlfunc: Socket address expected");
|
|
*yield = string_sprintf("rspamd dlfunc: Socket address expected");
|
|
goto RETURN_DEFER;
|
|
}
|
|
|
|
// get message body stream
|
|
|
|
if (split_spool_directory == 0) {
|
|
sprintf(mbox_path, "%s/input/%s-D", spool_directory, message_id);
|
|
} else {
|
|
sprintf(mbox_path, "%s/input/%s/%s-D", spool_directory, message_subdir, message_id);
|
|
}
|
|
//debug_printf(" Open spool file: %s\n", mbox_path);
|
|
mbox_file = fopen(mbox_path,"rb");
|
|
|
|
if (!mbox_file) {
|
|
*yield = string_sprintf("rspamd dlfunc: Unable to spool message '%s'", mbox_path);
|
|
return(defer_ok ? OK : ERROR);
|
|
}
|
|
|
|
(void)fseek(mbox_file, 0, SEEK_END);
|
|
mbox_size = ftell(mbox_file);
|
|
//debug_printf(" Total spool file size: %d\n", mbox_size);
|
|
mbox_size -= SPOOL_DATA_START_OFFSET;
|
|
//debug_printf(" Spool file size: %d\n", mbox_size);
|
|
//debug_printf(" fseek %d, %d\n", SPOOL_DATA_START_OFFSET, SEEK_SET);
|
|
(void)fseek(mbox_file, SPOOL_DATA_START_OFFSET, SEEK_SET);
|
|
|
|
start = time(NULL);
|
|
/* socket does not start with '/' -> network socket */
|
|
if (arg_socket_addr[0] != '/') {
|
|
if (sscanf(CS arg_socket_addr, "%s %u", tcp_addr, &tcp_port) != 2 ) {
|
|
log_write(0, LOG_MAIN|LOG_PANIC,
|
|
"rspamd dlfunc: Invalid rspamd address: '%s'", arg_socket_addr);
|
|
*yield = string_sprintf("rspamd dlfunc: Invalid rspamd address: '%s'", arg_socket_addr);
|
|
goto RETURN_DEFER;
|
|
}
|
|
|
|
/* Lookup the host */
|
|
if((he = gethostbyname(CS tcp_addr)) == 0) {
|
|
log_write(0, LOG_MAIN|LOG_PANIC,
|
|
"rspamd dlfunc: failed to lookup host '%s'", tcp_addr);
|
|
*yield = string_sprintf("rspamd dlfunc: failed to lookup host '%s'", tcp_addr);
|
|
goto RETURN_DEFER;
|
|
}
|
|
|
|
in = *(struct in_addr *) he->h_addr_list[0];
|
|
|
|
/* contact a rspamd */
|
|
|
|
if ((rspamd_sock = ip_socket(SOCK_STREAM, AF_INET)) < 0) {
|
|
log_write(0, LOG_MAIN|LOG_PANIC,
|
|
"rspamd dlfunc: TCP socket creation failed: %s", strerror(errno));
|
|
*yield = string_sprintf("rspamd dlfunc: TCP socket creation failed: %s", strerror(errno));
|
|
goto RETURN_DEFER;
|
|
};
|
|
|
|
if (ip_connect(rspamd_sock, AF_INET, (uschar*)inet_ntoa(in), tcp_port, 5, FALSE) < 0) {
|
|
log_write(0, LOG_MAIN|LOG_PANIC,
|
|
"rspamd dlfunc: connection to %s, port %u failed: %s", tcp_addr, tcp_port, strerror(errno));
|
|
*yield = string_sprintf("rspamd dlfunc: connection to %s, port %u failed: %s", tcp_addr, tcp_port, strerror(errno));
|
|
goto RETURN_DEFER;
|
|
}
|
|
|
|
//debug_printf(" Use TCP socket %s:%d\n", tcp_addr, tcp_port);
|
|
} else {
|
|
if ((rspamd_sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
|
|
log_write(0, LOG_MAIN|LOG_PANIC,
|
|
"rspamd dlfunc: Unable to acquire socket (%s)", strerror(errno));
|
|
*yield = string_sprintf("rspamd dlfunc: Unable to acquire socket (%s)", strerror(errno));
|
|
goto RETURN_DEFER;
|
|
}
|
|
|
|
server.sun_family = AF_UNIX;
|
|
Ustrcpy(server.sun_path, arg_socket_addr);
|
|
|
|
if (connect(rspamd_sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
|
|
log_write(0, LOG_MAIN|LOG_PANIC,
|
|
"rspamd dlfunc: Unable to connect to UNIX socket %s (%s)", socket, strerror(errno) );
|
|
*yield = string_sprintf("rspamd dlfunc: Unable to connect to UNIX socket %s (%s)", socket, strerror(errno) );
|
|
goto RETURN_DEFER;
|
|
}
|
|
|
|
//debug_printf(" Use UNIX Domain socket %s\n", arg_socket_addr);
|
|
}
|
|
|
|
// now we are connected to rspamd on rspamd_sock
|
|
|
|
memset(spamd_buffer2, 0, sizeof(spamd_buffer2));
|
|
offset = 0;
|
|
|
|
// headers list
|
|
tmp_headerlist = NULL;
|
|
header_last = NULL;
|
|
for (my_header = header_list; my_header; my_header = my_header->next) {
|
|
if ((my_header->type != '*') && (my_header->type != htype_old)) {
|
|
header_new = store_get(sizeof(header_line));
|
|
header_new->text = my_header->text;
|
|
|
|
header_new->slen = my_header->slen;
|
|
header_new->type = my_header->type;
|
|
header_new->next = NULL;
|
|
//debug_printf(" copy header item: '%s'\n", my_header->text);
|
|
|
|
max_len = sizeof(spamd_buffer2) - offset - 1;
|
|
len = my_header->slen;
|
|
if (len > max_len) len = max_len;
|
|
Ustrncpy(spamd_buffer2 + offset, my_header->text, len);
|
|
offset += len;
|
|
|
|
if (header_last != NULL) header_last->next = header_new;
|
|
header_last = header_new;
|
|
}
|
|
}
|
|
|
|
s = string_sprintf("\n");
|
|
max_len = sizeof(spamd_buffer2) - offset - 1;
|
|
len = Ustrlen(s);
|
|
if (len > max_len) len = max_len;
|
|
Ustrncpy(spamd_buffer2 + offset, s, len);
|
|
offset += len;
|
|
|
|
//debug_printf(" Headers size: %d\n", offset);
|
|
mbox_size += offset;
|
|
//debug_printf(" Total message size: %d\n", mbox_size);
|
|
|
|
// copy request to buffer
|
|
memset(spamd_buffer, 0, sizeof(spamd_buffer));
|
|
string_format(spamd_buffer,
|
|
sizeof(spamd_buffer),
|
|
"POST /checkv2 HTTP/1.0\r\nContent-length: " OFF_T_FMT "\r\nPass: all\r\nQueue-Id: %s\r\nFrom: %s\r\nRecipient-Number: %d\r\n",
|
|
mbox_size, message_id, sender_address, recipients_count);
|
|
for (i = 0; i < recipients_count; i ++)
|
|
string_format(spamd_buffer+Ustrlen(spamd_buffer), sizeof(spamd_buffer)-Ustrlen(spamd_buffer), "Rcpt: %s\r\n", recipients_list[i].address);
|
|
if ((helo = expand_string(US"$sender_helo_name")) != NULL && *helo != '\0')
|
|
string_format(spamd_buffer+Ustrlen(spamd_buffer), sizeof(spamd_buffer)-Ustrlen(spamd_buffer), "Helo: %s\r\n", helo);
|
|
|
|
if ((sender_host_name = expand_string(US"$sender_host_name")) != NULL && *sender_host_name != '\0')
|
|
string_format(spamd_buffer+Ustrlen(spamd_buffer), sizeof(spamd_buffer)-Ustrlen(spamd_buffer), "Hostname: %s\r\n", sender_host_name);
|
|
//else
|
|
//string_format(spamd_buffer+Ustrlen(spamd_buffer), sizeof(spamd_buffer)-Ustrlen(spamd_buffer), "Hostname: unknown\r\n");
|
|
|
|
if (sender_host_address != NULL)
|
|
|
|
string_format(spamd_buffer+Ustrlen(spamd_buffer), sizeof(spamd_buffer)-Ustrlen(spamd_buffer), "IP: %s\r\n", sender_host_address);
|
|
string_format(spamd_buffer+Ustrlen(spamd_buffer), sizeof(spamd_buffer)-Ustrlen(spamd_buffer), "Pass: all\r\n");
|
|
|
|
//authenticated_id
|
|
if ((authenticated_id = expand_string(US"$authenticated_id")) != NULL && *authenticated_id != '\0')
|
|
string_format(spamd_buffer+Ustrlen(spamd_buffer), sizeof(spamd_buffer)-Ustrlen(spamd_buffer), "User: %s\r\n", authenticated_id);
|
|
|
|
string_format(spamd_buffer+Ustrlen(spamd_buffer), sizeof(spamd_buffer)-Ustrlen(spamd_buffer), "\r\n");
|
|
|
|
//debug_printf(" Send to socket: %s", spamd_buffer);
|
|
// send our request
|
|
if (send(rspamd_sock, spamd_buffer, Ustrlen(spamd_buffer), 0) < 0) {
|
|
log_write(0, LOG_MAIN|LOG_PANIC,
|
|
"rspamd dlfunc: rspamd send failed: %s", strerror(errno));
|
|
goto RETURN_DEFER;
|
|
}
|
|
|
|
/*
|
|
* now send the data buffer and spool file
|
|
*/
|
|
|
|
Ustrcpy(big_buffer, "sending data block");
|
|
//debug_printf("sending data block\n");
|
|
|
|
//debug_printf(" Send to socket: %s", spamd_buffer2);
|
|
wrote = send(rspamd_sock, spamd_buffer2, strlen(spamd_buffer2), 0);
|
|
if (wrote == -1) goto WRITE_FAILED;
|
|
//debug_printf(" wrote to socket %d bytes\n", wrote);
|
|
|
|
/*
|
|
* Note: poll() is not supported in OSX 10.2.
|
|
*/
|
|
|
|
#ifndef NO_POLL_H
|
|
pollfd.fd = rspamd_sock;
|
|
pollfd.events = POLLOUT;
|
|
#endif
|
|
// (void)fcntl(rspamd_sock, F_SETFL, O_NONBLOCK);
|
|
do {
|
|
read = fread(spamd_buffer,1,sizeof(spamd_buffer),mbox_file);
|
|
if (read < sizeof(spamd_buffer)) spamd_buffer[read] = 0;
|
|
//debug_printf(" Read from spool file: %s", spamd_buffer);
|
|
if (read > 0) {
|
|
offset = 0;
|
|
again:
|
|
|
|
#ifndef NO_POLL_H
|
|
result = poll(&pollfd, 1, 1000);
|
|
if (result == -1 && errno == EINTR)
|
|
continue;
|
|
else if (result < 1) {
|
|
if (result == -1)
|
|
log_write(0, LOG_MAIN|LOG_PANIC,
|
|
"rspamd dlfunc: %s on rspamd socket", strerror(errno));
|
|
else {
|
|
if (time(NULL) - start < RSPAMD_TIMEOUT)
|
|
goto again;
|
|
log_write(0, LOG_MAIN|LOG_PANIC,
|
|
"rspamd dlfunc: timed out writing rspamd socket");
|
|
*yield = string_sprintf("rspamd dlfunc: timed out writing rspamd socket");
|
|
}
|
|
goto RETURN_DEFER;
|
|
}
|
|
#endif
|
|
wrote = send(rspamd_sock, spamd_buffer + offset, read - offset, 0);
|
|
//debug_printf(" Send to socket %d bytes: %s", read - offset, spamd_buffer + offset);
|
|
//debug_printf(" wrote to socket %d bytes\n", wrote);
|
|
if (wrote == -1) goto WRITE_FAILED;
|
|
if (offset + wrote != read) {
|
|
offset += wrote;
|
|
goto again;
|
|
}
|
|
}
|
|
} while (!feof(mbox_file) && !ferror(mbox_file));
|
|
|
|
if (ferror(mbox_file)) {
|
|
log_write(0, LOG_MAIN|LOG_PANIC,
|
|
"rspamd dlfunc: error reading spool file: %s", strerror(errno));
|
|
*yield = string_sprintf("rspamd dlfunc: error reading spool file: %s", strerror(errno));
|
|
goto RETURN_DEFER;
|
|
}
|
|
|
|
/*
|
|
read rspamd response using what's left of the timeout.
|
|
*/
|
|
|
|
//debug_printf("read rspamd response using what's left of the timeout (%d sec)\n", RSPAMD_TIMEOUT - time(NULL) + start);
|
|
|
|
memset(spamd_buffer, 0, sizeof(spamd_buffer));
|
|
offset = 0;
|
|
while ((i = ip_recv(rspamd_sock,
|
|
spamd_buffer + offset,
|
|
sizeof(spamd_buffer) - offset - 1,
|
|
RSPAMD_TIMEOUT - time(NULL) + start)) > 0
|
|
) {
|
|
//debug_printf(" read %d bytes from socket\n", i);
|
|
offset += i;
|
|
}
|
|
//debug_printf(" total read %d bytes from socket\n", offset);
|
|
|
|
/* error handling */
|
|
if((i <= 0) && (errno != 0)) {
|
|
log_write(0, LOG_MAIN|LOG_PANIC,
|
|
"rspamd dlfunc: error reading from rspamd socket: %s", strerror(errno));
|
|
*yield = string_sprintf("rspamd dlfunc: error reading from rspamd socket: %s", strerror(errno));
|
|
goto RETURN_DEFER;
|
|
}
|
|
|
|
//debug_printf("read from socket: %s\n", spamd_buffer);
|
|
|
|
if (rspamd_sock > 0) {
|
|
(void)close(rspamd_sock);
|
|
rspamd_sock = 0;
|
|
}
|
|
if (mbox_file != NULL) {
|
|
(void)fclose(mbox_file);
|
|
mbox_file = NULL;
|
|
}
|
|
|
|
//Parse http response code
|
|
if (strstr(spamd_buffer, "HTTP/1.1 200 OK") == NULL && strstr(spamd_buffer, "HTTP/1.0 200 OK") == NULL) {
|
|
*yield = string_sprintf("rspamd dlfunc: HTTP return code != 200: %s", spamd_buffer);
|
|
goto RETURN_DEFER;
|
|
}
|
|
|
|
//Parse http response
|
|
int pos = strpos(spamd_buffer, "\r\n\r\n", 0);
|
|
if (pos == -1) {
|
|
*yield = string_sprintf("rspamd dlfunc: HTTP response error: %s", spamd_buffer);
|
|
goto RETURN_DEFER;
|
|
}
|
|
char *json_answer = string_sprintf("%s", spamd_buffer +pos+4);
|
|
|
|
//Parse json
|
|
cJSON *json = NULL;
|
|
json = cJSON_Parse(json_answer);
|
|
|
|
if (!json) {
|
|
*yield = string_sprintf("rspamd dlfunc: Json parse error, json: %s", spamd_buffer);
|
|
goto RETURN_DEFER;
|
|
}
|
|
|
|
//Score
|
|
cJSON *score = cJSON_GetObjectItem(json, "score");
|
|
if(!cJSON_IsNumber(score)) {
|
|
*yield = string_sprintf("rspamd dlfunc: Json parse error, no found 'score'");
|
|
goto RETURN_DEFER;
|
|
}
|
|
//required_score
|
|
cJSON *required_score = cJSON_GetObjectItem(json, "required_score");
|
|
if(!cJSON_IsNumber(required_score)) {
|
|
*yield = string_sprintf("rspamd dlfunc: Json parse error, no found 'required_score'");
|
|
goto RETURN_DEFER;
|
|
}
|
|
//Action
|
|
cJSON *action = cJSON_GetObjectItem(json, "action");
|
|
if(!cJSON_IsString(action)) {
|
|
*yield = string_sprintf("rspamd dlfunc: Json parse error, no found 'action'");
|
|
goto RETURN_DEFER;
|
|
}
|
|
*yield = string_sprintf("[%.2f / %.2f]", score->valuedouble, required_score->valuedouble);
|
|
|
|
//Parse scan time
|
|
cJSON *time_real = cJSON_GetObjectItem(json, "time_real");
|
|
cJSON *time_virtual = cJSON_GetObjectItem(json, "time_virtual");
|
|
if(cJSON_IsNumber(time_real) && cJSON_IsNumber(time_virtual)) *yield = string_sprintf("%s [time: %.6f, %.6f]", *yield, time_real->valuedouble, time_virtual->valuedouble);
|
|
|
|
*yield = string_sprintf("%s\n Action: %s\n", *yield, action->valuestring);
|
|
|
|
cJSON *symbol = NULL;
|
|
cJSON *symbol_name = NULL;
|
|
cJSON *symbol_score = NULL;
|
|
cJSON *symbol_options = NULL;
|
|
cJSON *option = NULL;
|
|
|
|
//parse symbols
|
|
cJSON *symbols = cJSON_GetObjectItem(json, "symbols");
|
|
for(i=0; i<cJSON_GetArraySize(symbols); i++) {
|
|
symbol = cJSON_GetArrayItem(symbols, i);
|
|
symbol_name = cJSON_GetObjectItem(symbol, "name");
|
|
symbol_score = cJSON_GetObjectItem(symbol, "score");
|
|
symbol_options = cJSON_GetObjectItem(symbol, "options");
|
|
|
|
if(cJSON_IsString(symbol_name)) *yield = string_sprintf("%s %s", *yield, symbol_name->valuestring);
|
|
if(cJSON_IsNumber(symbol_score)) *yield = string_sprintf("%s(%.2f)", *yield, symbol_score->valuedouble);
|
|
|
|
//parse options
|
|
c = cJSON_GetArraySize(symbol_options);
|
|
if(c > 0) *yield = string_sprintf("%s[", *yield);
|
|
for(j=0; j<c; j++) {
|
|
option = cJSON_GetArrayItem(symbol_options, j);
|
|
if(cJSON_IsString(option)) {
|
|
*yield = string_sprintf("%s%s", *yield, option->valuestring);
|
|
if(j < c-1) *yield = string_sprintf("%s, ", *yield);
|
|
}
|
|
}
|
|
if(c > 0) *yield = string_sprintf("%s]", *yield);
|
|
|
|
*yield = string_sprintf("%s\n", *yield);
|
|
}
|
|
|
|
//Parse messages
|
|
cJSON *mess = NULL;
|
|
cJSON *messages = cJSON_GetObjectItem(json, "messages");
|
|
c = cJSON_GetArraySize(messages);
|
|
for(i=0; i<c; i++) {
|
|
mess = cJSON_GetArrayItem(messages, i);
|
|
if(cJSON_IsString(mess)) *yield = string_sprintf("%s %s", *yield, mess->valuestring);
|
|
if(i < c-1) *yield = string_sprintf("%s\n", *yield);
|
|
}
|
|
|
|
return OK;
|
|
|
|
/* Come here if any call to read_response, other than a response after the data
|
|
phase, failed. Analyse the error, and if isn't too bad, send a QUIT
|
|
command. Wait for the response with a short timeout, so we don't wind up this
|
|
process before the far end has had time to read the QUIT. */
|
|
|
|
WRITE_FAILED:
|
|
{
|
|
log_write(0, LOG_MAIN|LOG_PANIC,
|
|
"rspamd dlfunc: %s on rspamd socket", strerror(errno));
|
|
*yield = string_sprintf("rspamd dlfunc: %s on rspamd socket", strerror(errno));
|
|
goto RETURN_DEFER;
|
|
}
|
|
|
|
RESPONSE_FAILED:
|
|
{
|
|
int code;
|
|
int save_errno;
|
|
int more_errno;
|
|
uschar message_buffer[256];
|
|
uschar *message;
|
|
|
|
save_errno = errno;
|
|
|
|
message = &message_buffer[0];
|
|
|
|
log_write(0, LOG_MAIN|LOG_PANIC, "rspamd dlfunc: %s", message);
|
|
*yield = string_sprintf("rspamd dlfunc: %s", message);
|
|
|
|
goto RETURN_DEFER;
|
|
}
|
|
|
|
RETURN_DEFER:
|
|
{
|
|
if (rspamd_sock > 0) {
|
|
(void)close(rspamd_sock);
|
|
rspamd_sock = 0;
|
|
}
|
|
if (mbox_file != NULL) {
|
|
(void)fclose(mbox_file);
|
|
mbox_file = NULL;
|
|
}
|
|
|
|
return(defer_ok ? OK : ERROR);
|
|
}
|
|
|
|
return OK;
|
|
}
|