/* $Id: client.c,v 1.691 2011/06/28 00:13:48 sbajic Exp $ */
/*
DSPAM
COPYRIGHT (C) 2002-2012 DSPAM PROJECT
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
/*
* client.c - client-based functions (for operating in client/daemon mode)c
*
* DESCRIPTION
* Client-based functions are called when --client is specified on the
* commandline or by dspamc (where --client is inferred). The client
* functions connect to a DSPAM server for processing (rather than the
* usual behavior which is to process the message itself). Client
* functions are also used when delivering via LMTP or SMTP.
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#ifdef DAEMON
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifndef _WIN32
#include
#include
#endif
#include
#include
#include
#include "client.h"
#include "dspam.h"
#include "config.h"
#include "util.h"
#include "language.h"
#include "buffer.h"
/*
* client_process(AGENT_CTX *, buffer *)
*
* DESCRIPTION
* connect to a dspam daemon socket and attempt to process a message
* this function is called by the dspam agent when --client is specified
*
* INPUT ARGUMENTS
* ATX agent context
* message message to be processed
*
* RETURN VALUES
* returns 0 on success
*/
int client_process(AGENT_CTX *ATX, buffer *message) {
char buf[1024], err[256];
struct nt_node *node_nt;
struct nt_c c_nt;
int exitcode = 0, msglen;
THREAD_CTX TTX;
int i;
TTX.sockfd = client_connect(ATX, 0);
if (TTX.sockfd <0) {
LOG(LOG_WARNING, ERR_CLIENT_CONNECT);
STATUS(ERR_CLIENT_CONNECT);
return TTX.sockfd;
}
TTX.packet_buffer = buffer_create(NULL);
if (TTX.packet_buffer == NULL)
goto BAIL;
/* LHLO / MAIL FROM - Authenticate on the server */
if (client_authenticate(&TTX, ATX->client_args)<0) {
LOG(LOG_WARNING, ERR_CLIENT_AUTH_FAILED);
STATUS(ERR_CLIENT_AUTH_FAILED);
goto QUIT;
}
/* RCPT TO - Send recipient information */
strcpy(buf, "RCPT TO: ");
node_nt = c_nt_first(ATX->users, &c_nt);
while(node_nt != NULL) {
const char *ptr = (const char *) node_nt->ptr;
snprintf(buf, sizeof(buf), "RCPT TO: <%s>", ptr);
if (send_socket(&TTX, buf)<=0) {
STATUS(ERR_CLIENT_SEND_FAILED);
goto BAIL;
}
if (client_getcode(&TTX, err, sizeof(err))!=LMTP_OK) {
STATUS("%s", err);
goto QUIT;
}
node_nt = c_nt_next(ATX->users, &c_nt);
}
/* DATA - Send message */
if (send_socket(&TTX, "DATA")<=0)
goto BAIL;
if (client_getcode(&TTX, err, sizeof(err))!=LMTP_DATA) {
STATUS("%s", err);
goto QUIT;
}
i = 0;
msglen = strlen(message->data);
while(i 0) {
if (message->data[i] == '\n') {
/* only replace \n and not \r\n */
if (message->data[i - 1] != '\r') {
buf[buflen] = '\r';
buflen++;
}
/* take care of dot stuffing \n */
if (message->data[i + 1] && message->data[i + 1] == '.') {
buf[buflen] = '\n';
buflen++;
buf[buflen] = '.';
buflen++;
buf[buflen] = '.';
buflen++;
i += 2;
continue;
}
}
}
buf[buflen] = message->data[i];
buflen++;
i++;
}
/* send buf */
t = 0;
while (t < buflen) {
r = send(TTX.sockfd, buf+t, buflen - t, 0);
if (r <= 0) {
LOG(LOG_ERR, ERR_CLIENT_SEND_FAILED);
STATUS(ERR_CLIENT_SEND_FAILED);
goto BAIL;
}
t += r;
}
}
if (message->data[msglen-1]!= '\n') {
if (send_socket(&TTX, "")<=0) {
STATUS(ERR_CLIENT_SEND_FAILED);
goto BAIL;
}
}
if (send_socket(&TTX, ".")<=0) {
STATUS(ERR_CLIENT_SEND_FAILED);
goto BAIL;
}
/* Server response */
if (ATX->flags & DAF_STDOUT || ATX->flags & DAF_SUMMARY ||
ATX->operating_mode == DSM_CLASSIFY)
{
char *line = NULL;
int head = !(ATX->flags & DAF_STDOUT);
if (ATX->flags & DAF_SUMMARY)
head = 1;
line = client_getline(&TTX, 300);
while(line != NULL && strcmp(line, ".")) {
chomp(line);
if (!head) {
head = 1;
if (!strncmp(line, "250 ", 4)) {
free(line);
goto QUIT;
}
if (!strcmp(line, "X-Daemon-Classification: SPAM") &&
_ds_match_attribute(agent_config, "Broken", "returnCodes"))
{
exitcode = 99;
}
} else {
/* remove dot stuffing, if needed */
if((line[0] && line[0]=='.') && (line[1] && line[1]=='.')) {
size_t i, len = strlen(line);
for(i=0;iusers->items;i++) {
char *input = client_getline(&TTX, 300);
char *x;
int code = 500;
if (!input) {
goto BAIL;
}
x = strtok(input, " ");
if (x) {
code = atoi(x);
if (code != LMTP_OK) {
if (exitcode > 0)
exitcode = 0;
exitcode--;
} else {
if (_ds_match_attribute(agent_config, "Broken", "returnCodes")) {
x = strtok(NULL, ":");
if (x)
x = strtok(NULL, ":");
if (x && strstr(x, "SPAM") && exitcode == 0)
exitcode = 99;
}
}
}
}
}
send_socket(&TTX, "QUIT");
client_getcode(&TTX, err, sizeof(err));
close(TTX.sockfd);
buffer_destroy(TTX.packet_buffer);
return exitcode;
QUIT:
send_socket(&TTX, "QUIT");
client_getcode(&TTX, err, sizeof(err));
BAIL:
exitcode = EFAILURE;
buffer_destroy(TTX.packet_buffer);
close(TTX.sockfd);
return exitcode;
}
/*
* client_connect(AGENT_CTX ATX, int flags)
*
* DESCRIPTION
* establish a connection to a server
*
* INPUT ARGUMENTS
* ATX agent context
* flags connection flags
*
* FLAGS
* CCF_PROCESS Use ClientHost as destination
* CCF_DELIVERY Use DeliveryHost as destination
*
* RETURN VALUES
* returns 0 on success
*/
int client_connect(AGENT_CTX *ATX, int flags) {
struct sockaddr_in addr;
struct sockaddr_un saun;
int sockfd;
int yes = 1;
int port = 24;
int domain = 0;
int addr_len;
char *host;
if (flags & CCF_DELIVERY) {
host = _ds_read_attribute(agent_config, "DeliveryHost");
if (_ds_read_attribute(agent_config, "DeliveryPort"))
port = atoi(_ds_read_attribute(agent_config, "DeliveryPort"));
if (ATX->recipient && ATX->recipient[0]) {
char *domain = strchr(ATX->recipient, '@');
if (domain) {
char key[128];
char lcdomain[strlen(ATX->recipient)];
lc(lcdomain, domain+1);
snprintf(key, sizeof(key), "DeliveryHost.%s", lcdomain);
if (_ds_read_attribute(agent_config, key))
host = _ds_read_attribute(agent_config, key);
snprintf(key, sizeof(key), "DeliveryPort.%s", lcdomain);
if (_ds_read_attribute(agent_config, key))
port = atoi(_ds_read_attribute(agent_config, key));
}
}
if (host && host[0] == '/')
domain = 1;
} else {
host = _ds_read_attribute(agent_config, "ClientHost");
if (_ds_read_attribute(agent_config, "ClientPort"))
port = atoi(_ds_read_attribute(agent_config, "ClientPort"));
if (host && host[0] == '/')
domain = 1;
}
if (host == NULL) {
LOG(LOG_CRIT, ERR_CLIENT_INVALID_CONFIG);
STATUS(ERR_CLIENT_INVALID_CONFIG);
return EINVAL;
}
/* Connect (domain socket) */
if (domain) {
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
saun.sun_family = AF_UNIX;
strcpy(saun.sun_path, host);
addr_len = sizeof(saun.sun_family) + strlen(saun.sun_path) + 1;
LOGDEBUG(INFO_CLIENT_CONNECTING, host, 0);
if(connect(sockfd, (struct sockaddr *)&saun, addr_len)<0) {
LOG(LOG_ERR, ERR_CLIENT_CONNECT_SOCKET, host, strerror(errno));
STATUS("%s", strerror(errno));
close(sockfd);
return EFAILURE;
}
/* Connect (TCP socket) */
} else {
sockfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(host);
addr.sin_port = htons(port);
addr_len = sizeof(struct sockaddr_in);
LOGDEBUG(INFO_CLIENT_CONNECTING, host, port);
if(connect(sockfd, (struct sockaddr *)&addr, addr_len)<0) {
LOG(LOG_ERR, ERR_CLIENT_CONNECT_HOST, host, port, strerror(errno));
STATUS("%s", strerror(errno));
close(sockfd);
return EFAILURE;
}
}
LOGDEBUG(INFO_CLIENT_CONNECTED);
setsockopt(sockfd,SOL_SOCKET,TCP_NODELAY,&yes,sizeof(int));
return sockfd;
}
/*
* client_authenticate(AGENT_CTX *ATX, const char *mode)
*
* DESCRIPTION
* greet and authenticate on a server
*
* INPUT ARGUMENTS
* ATX agent context
* mode processing mode
*
* NOTES
* the process mode is passed using the DSPAMPROCESSMODE service tag
*
* RETURN VALUES
* returns 0 on success
*/
int client_authenticate(THREAD_CTX *TTX, const char *mode) {
char *ident = _ds_read_attribute(agent_config, "ClientIdent");
char buf[1024], err[128];
char *ptr;
char pmode[1024];
pmode[0] = 0;
if (mode) {
int pos = 0, cpos = 0;
for(;mode[cpos]&&(size_t)pos<(sizeof(pmode)-1);cpos++) {
if (mode[cpos] == '"') {
pmode[pos] = '\\';
pos++;
}
pmode[pos] = mode[cpos];
pos++;
}
pmode[pos] = 0;
}
if (!ident || !strchr(ident, '@')) {
LOG(LOG_ERR, ERR_CLIENT_IDENT);
return EINVAL;
}
ptr = client_expect(TTX, LMTP_GREETING, err, sizeof(err));
if (ptr == NULL) {
LOG(LOG_ERR, ERR_CLIENT_WHILE_AUTH, err);
return EFAILURE;
}
free(ptr);
snprintf(buf, sizeof(buf), "LHLO %s", strchr(ident, '@')+1);
if (send_socket(TTX, buf)<=0)
return EFAILURE;
if (client_getcode(TTX, err, sizeof(err))!=LMTP_OK) {
return EFAILURE;
}
if (mode) {
snprintf(buf, sizeof(buf), "MAIL FROM: <%s> DSPAMPROCESSMODE=\"%s\"", ident, pmode);
} else {
snprintf(buf, sizeof(buf), "MAIL FROM: <%s>", ident);
}
if (send_socket(TTX, buf)<=0) {
return EFAILURE;
}
if (client_getcode(TTX, err, sizeof(err))!=LMTP_OK) {
LOG(LOG_ERR, ERR_CLIENT_AUTHENTICATE);
return EFAILURE;
}
return 0;
}
/*
* client_expect(THREAD_CTX *TTX, int code, char *err, size_t len)
*
* DESCRIPTION
* wait for the appropriate return code, then return
*
* INPUT ARGUMENTS
* ATX agent context
* code return code to wait for
* err error buffer
* len buffer len
*
* RETURN VALUES
* allocated pointer to acknowledgement line, NULL on error
* err buffer is populated on error
*/
char * client_expect(THREAD_CTX *TTX, int code, char *err, size_t len) {
char *inp, *dup, *ptr, *ptrptr;
int recv_code;
inp = client_getline(TTX, 300);
while(inp != NULL) {
recv_code = 0;
dup = strdup(inp);
if (!dup) {
free(inp);
LOG(LOG_CRIT, ERR_MEM_ALLOC);
strlcpy(err, ERR_MEM_ALLOC, len);
return NULL;
}
if (strncmp(dup, "250-", 4)) {
ptr = strtok_r(dup, " ", &ptrptr);
if (ptr)
recv_code = atoi(ptr);
free(dup);
if (recv_code == code) {
err[0] = 0;
return inp;
}
LOG(LOG_WARNING, ERR_CLIENT_RESPONSE_CODE, code, inp);
}
strlcpy(err, inp, len);
free(inp);
inp = client_getline(TTX, 300);
}
return NULL;
}
/*
* client_parsecode(const char *err)
*
* DESCRIPTION
* parse response code from plain text
*
* INPUT ARGUMENTS
* err error message to parse
*
* RETURN VALUES
* integer value of response code
*/
int client_parsecode(char *error) {
char code[4];
code[3] = 0;
strncpy(code, error, 3);
return atoi(code);
}
/*
* client_getcode(THREAD_CTX *TTX, char *err, size_t len)
*
* DESCRIPTION
* retrieve a line of input and return response code
*
* INPUT ARGUMENTS
* TTX thread context containing sockfd
* err error buffer
* len buffer len
*
* RETURN VALUES
* integer value of response code
*/
int client_getcode(THREAD_CTX *TTX, char *err, size_t len) {
char *inp, *ptr, *ptrptr = NULL;
int i;
inp = client_getline(TTX, 300);
if (!inp)
return EFAILURE;
while(inp && !strncmp(inp, "250-", 4)) {
free(inp);
inp = client_getline(TTX, 300);
}
strlcpy(err, inp, len);
ptr = strtok_r(inp, " ", &ptrptr);
if (ptr == NULL)
return EFAILURE;
i = atoi(ptr);
free(inp);
return i;
}
/*
* client_getline(THREAD_CTX *TTX, int timeout)
*
* DESCRIPTION
* read a complete line from a socket
*
* INPUT ARGUMENTS
* TTX thread context containing sockfd
* timeout timeout (in seconds) to wait for input
*
* RETURN VALUES
* allocated pointer to input
*/
char *client_getline(THREAD_CTX *TTX, int timeout) {
struct timeval tv;
long recv_len;
char buf[1024];
char *pop;
fd_set fds;
int i;
pop = pop_buffer(TTX);
while(!pop) {
tv.tv_sec = timeout;
tv.tv_usec = 0;
FD_ZERO(&fds);
FD_SET(TTX->sockfd, &fds);
i = select(TTX->sockfd+1, &fds, NULL, NULL, &tv);
if (i<=0)
return NULL;
recv_len = recv(TTX->sockfd, buf, sizeof(buf)-1, 0);
buf[recv_len] = 0;
if (recv_len == 0)
return NULL;
buffer_cat(TTX->packet_buffer, buf);
pop = pop_buffer(TTX);
}
#ifdef VERBOSE
LOGDEBUG("RECV: %s", pop);
#endif
return pop;
}
/*
* pop_buffer (THREAD_CTX *TTX)
*
* DESCRIPTION
* pop a line off the packet buffer
*
* INPUT ARGUMENTS
* TTX thread context containing the packet buffer
*
* RETURN VALUES
* allocated pointer to line, NULL if complete line isn't available
*/
char *pop_buffer(THREAD_CTX *TTX) {
char *buf, *eol;
long len;
if (!TTX || !TTX->packet_buffer || !TTX->packet_buffer->data)
return NULL;
eol = strchr(TTX->packet_buffer->data, 10);
if (!eol)
return NULL;
len = (eol - TTX->packet_buffer->data) + 1;
buf = calloc(1, len+1);
if (!buf) {
LOG(LOG_CRIT, ERR_MEM_ALLOC);
return NULL;
}
memcpy(buf, TTX->packet_buffer->data, len);
memmove(TTX->packet_buffer->data, eol+1, strlen(eol+1)+1);
TTX->packet_buffer->used -= len;
return buf;
}
/*
* send_socket(THREAD_CTX *TTX, const char *text)
*
* DESCRIPTION
* send a line of text to a socket
*
* INPUT ARGUMENTS
* TTX thread context containing sockfd
* text text to send
*
* RETURN VALUES
* number of bytes sent
*/
int send_socket(THREAD_CTX *TTX, const char *text) {
int i = 0, r, msglen;
#ifdef VERBOSE
LOGDEBUG("SEND: %s", text);
#endif
msglen = strlen(text);
while(isockfd, text+i, msglen-i, 0);
if (r <= 0) {
return r;
}
i += r;
}
r = send(TTX->sockfd, "\r\n", 2, 0);
if (r > 0) {
i += r;
}
return i;
}
/*
* deliver_socket(AGENT_CTX *ATX, const char *msg, int proto)
*
* DESCRIPTION
* delivers message via LMTP or SMTP (instead of TrustedDeliveryAgent)
*
* If LMTP/SMTP delivery was specified in dspam.conf, this function will be
* called by deliver_message(). This function connects to and delivers the
* message using standard LMTP or SMTP. Depending on how DSPAM was originally
* called, either the address supplied with the incoming RCPT TO or the
* address supplied on the commandline with --rcpt-to will be used. If
* neither are present, the username will be used.
*
* INPUT ARGUMENTS
* ATX agent context
* msg message to send
* proto protocol to use
*
* PROTOCOLS
* DDP_LMTP LMTP
* DDP_SMTP SMTP
*
* RETURN VALUES
* returns 0 on success
*/
int deliver_socket(AGENT_CTX *ATX, const char *msg, int proto) {
THREAD_CTX TTX;
char buf[1024], err[256];
char *ident = _ds_read_attribute(agent_config, "DeliveryIdent");
int exitcode = EFAILURE;
int msglen, code;
int buflen;
char *inp;
int i;
int size_extension = 0;
err[0] = 0;
TTX.sockfd = client_connect(ATX, CCF_DELIVERY);
if (TTX.sockfd <0) {
STATUS(ERR_CLIENT_CONNECT);
LOG(LOG_ERR, ERR_CLIENT_CONNECT);
return TTX.sockfd;
}
TTX.packet_buffer = buffer_create(NULL);
if (TTX.packet_buffer == NULL) {
LOG(LOG_CRIT, ERR_MEM_ALLOC);
STATUS(ERR_MEM_ALLOC);
goto BAIL;
}
inp = client_expect(&TTX, LMTP_GREETING, err, sizeof(err));
if (inp == NULL) {
LOG(LOG_ERR, ERR_CLIENT_ON_GREETING, err);
STATUS("%s", err);
goto BAIL;
}
free(inp);
/* LHLO / HELO */
snprintf(buf, sizeof(buf), "%s %s", (proto == DDP_LMTP) ? "LHLO" : "HELO",
(ident) ? ident : "localhost");
if (send_socket(&TTX, buf)<=0) {
LOG(LOG_ERR, ERR_CLIENT_SEND_FAILED);
STATUS(ERR_CLIENT_SEND_FAILED);
goto BAIL;
}
/* Check for SIZE extension */
if (proto == DDP_LMTP) {
char *dup, *ptr, *ptrptr;
inp = client_getline(&TTX, 300);
while(inp != NULL) {
code = 0;
dup = strdup(inp);
if (!dup) {
free(inp);
LOG(LOG_CRIT, ERR_MEM_ALLOC);
LOG(LOG_ERR, ERR_CLIENT_INVALID_RESPONSE, "LHLO", ERR_MEM_ALLOC);
STATUS("LHLO: %s", ERR_MEM_ALLOC);
goto QUIT;
}
if (!strcmp(dup, "250-SIZE") || (!strncmp(dup, "250-SIZE", 8) && strlen(dup)>=8 && isspace(dup[8]))) {
free(inp);
free(dup);
size_extension = 1;
inp = client_expect(&TTX, LMTP_OK, err, sizeof(err));
break;
} else if (strncmp(dup, "250-", 4)) {
ptr = strtok_r(dup, " ", &ptrptr);
if (ptr)
code = atoi(ptr);
if (code == LMTP_OK) {
ptr = strtok_r(NULL, " ", &ptrptr);
if (ptr && !strcmp(ptr, "SIZE"))
size_extension = 1;
}
free(dup);
if (code == LMTP_OK) {
err[0] = 0;
break;
}
LOG(LOG_WARNING, ERR_CLIENT_RESPONSE_CODE, code, inp);
}
strlcpy(err, inp, sizeof(err));
free(inp);
inp = client_getline(&TTX, 300);
}
} else {
inp = client_expect(&TTX, LMTP_OK, err, sizeof(err));
}
if (inp == NULL) {
LOG(LOG_ERR, ERR_CLIENT_INVALID_RESPONSE,
(proto == DDP_LMTP) ? "LHLO" : "HELO", err);
STATUS("%s: %s", (proto == DDP_LMTP) ? "LHLO" : "HELO", err);
goto QUIT;
}
free(inp);
/* MAIL FROM */
if (proto == DDP_LMTP && size_extension == 1) {
snprintf(buf, sizeof(buf), "MAIL FROM:<%s> SIZE=%ld",
ATX->mailfrom, (long) strlen(msg));
} else {
snprintf(buf, sizeof(buf), "MAIL FROM:<%s>", ATX->mailfrom);
}
if (send_socket(&TTX, buf)<=0) {
LOG(LOG_ERR, ERR_CLIENT_SEND_FAILED);
STATUS(ERR_CLIENT_SEND_FAILED);
goto BAIL;
}
code = client_getcode(&TTX, err, sizeof(err));
if (code!=LMTP_OK) {
LOG(LOG_ERR, ERR_CLIENT_RESPONSE, code, "MAIL FROM", err);
if (code >= 500)
exitcode = EINVAL;
chomp(err);
STATUS((code >= 500) ? "Fatal: %s" : "Deferred: %s", err);
goto QUIT;
}
/* RCPT TO */
snprintf(buf, sizeof(buf), "RCPT TO:<%s>", (ATX->recipient) ? ATX->recipient : "");
if (send_socket(&TTX, buf)<=0) {
LOG(LOG_ERR, ERR_CLIENT_SEND_FAILED);
STATUS(ERR_CLIENT_SEND_FAILED);
goto BAIL;
}
code = client_getcode(&TTX, err, sizeof(err));
if (code!=LMTP_OK) {
LOG(LOG_ERR, ERR_CLIENT_RESPONSE, code, "RCPT TO", err);
if (code >= 500)
exitcode = EINVAL;
chomp(err);
STATUS((code >= 500) ? "Fatal: %s" : "Deferred: %s", err);
goto QUIT;
}
/* DATA */
if (send_socket(&TTX, "DATA")<=0) {
LOG(LOG_ERR, ERR_CLIENT_SEND_FAILED);
STATUS(ERR_CLIENT_SEND_FAILED);
goto BAIL;
}
code = client_getcode(&TTX, err, sizeof(err));
if (code!=LMTP_DATA) {
LOG(LOG_ERR, ERR_CLIENT_RESPONSE, code, "DATA", err);
if (code >= 500)
exitcode = EINVAL;
chomp(err);
STATUS((code >= 500) ? "Fatal: %s" : "Deferred: %s", err);
goto QUIT;
}
i = 0;
msglen = strlen(msg);
while(i 0 && msg[i] == '\n' && msg[i - 1] != '\r') {
buf[buflen] = '\r';
buflen++;
}
/* escape dot if first character on line */
if (msg[i] == '.' && (i == 0 || msg[i - 1] == '\n')) {
buf[buflen] = '.';
buflen++;
}
buf[buflen] = msg[i];
buflen++;
i++;
}
/* send buf */
t = 0;
while (t < buflen) {
r = send(TTX.sockfd, buf+t, buflen - t, 0);
if (r <= 0) {
LOG(LOG_ERR, ERR_CLIENT_SEND_FAILED);
STATUS(ERR_CLIENT_SEND_FAILED);
goto BAIL;
}
t += r;
}
}
if (msg[strlen(msg)-1]!= '\n') {
if (send_socket(&TTX, "")<=0) {
LOG(LOG_ERR, ERR_CLIENT_SEND_FAILED);
STATUS(ERR_CLIENT_SEND_FAILED);
goto BAIL;
}
}
if (send_socket(&TTX, "\r\n.")<=0) {
LOG(LOG_ERR, ERR_CLIENT_SEND_FAILED);
STATUS(ERR_CLIENT_SEND_FAILED);
goto BAIL;
}
/* server response */
code = client_getcode(&TTX, err, sizeof(err));
if (code < 200 || code >= 300) {
LOG(LOG_ERR, ERR_CLIENT_RESPONSE, code, "message data", err);
if (code >= 400 && code < 500)
exitcode = EX_TEMPFAIL;
else if (code >= 500)
exitcode = EINVAL;
chomp(err);
STATUS((code >= 500) ? "Fatal: %s" : "Deferred: %s", err);
goto QUIT;
}
send_socket(&TTX, "QUIT");
client_getcode(&TTX, err, sizeof(err));
close(TTX.sockfd);
buffer_destroy(TTX.packet_buffer);
return 0;
QUIT:
send_socket(&TTX, "QUIT");
client_getcode(&TTX, err, sizeof(err));
buffer_destroy(TTX.packet_buffer);
close(TTX.sockfd);
return exitcode;
BAIL:
LOG(LOG_ERR, ERR_CLIENT_DELIVERY_FAILED);
buffer_destroy(TTX.packet_buffer);
close(TTX.sockfd);
return exitcode;
}
#endif /* DAEMON */