/*
client.c - MessageWall SMTP client definitions
Copyright (C) 2002 Ian Gulliver
This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <firedns.h>
#include "messagewall.h"
#include "smtp.h"
#include "tls.h"
#include "client.h"
static const char tagstring[] = "$Id: client.c,v 1.37.2.3 2002/09/21 21:17:24 ian Exp $";
int client_connect(int n) {
/*
* clear up variables
*/
if (backends[n].ssl != NULL)
SSL_free(backends[n].ssl);
backends[n].state = BACKEND_STATE_NOTCONNECTED;
backends[n].client = -1;
backends[n].eightbit = 0;
backends[n].pipelining = 0;
backends[n].tls = 0;
backends[n].auth_plain = 0;
backends[n].buffer.l = 0;
backends[n].ssl = NULL;
fprintf(stderr,"{%d} [%d] BACKEND/STATUS: connect to %s started\n",process,n,firedns_ntoa4(&backendaddr.sin_addr));
/*
* start a client connection
*/
/*
* this socket is created after the security content starts up;
* root socket attacks are a non-event
*/
backends[n].fd = socket(PF_INET, SOCK_STREAM, 0); /* ITS4: ignore socket */
if (backends[n].fd == -1)
return 1;
if (socket_nonblock(backends[n].fd) != 0) {
close(backends[n].fd);
return 1;
}
if (connect(backends[n].fd,(struct sockaddr *)&backendaddr,sizeof(backendaddr)) != 0 && errno != EINPROGRESS) {
close(backends[n].fd);
return 1;
}
backends[n].state = BACKEND_STATE_CONNECTING;
return 0;
}
int client_read_event(int n) {
struct firestring_estr_t *line;
static struct firestring_estr_t outline = {0};
int i;
if (outline.a == 0)
firestring_estr_alloc(&outline,SMTP_LINE_MAXLEN);
switch (backends[n].state) {
case BACKEND_STATE_CONNECTING:
backends[n].state = BACKEND_STATE_GREETING;
break;
case BACKEND_STATE_GREETING:
if (tls_backend_eread(n,&backends[n].buffer) != 0) {
close(backends[n].fd);
backends[n].state = BACKEND_STATE_NOTCONNECTED;
return 1;
}
while (1) {
line = client_get_line(n);
if (line == NULL)
return 0;
if (line->l < 3 || line->s[0] != '2') {
close(backends[n].fd);
backends[n].state = BACKEND_STATE_NOTCONNECTED;
return 1;
}
if (line->l >= 4 && line->s[3] == '-') {
/*
* continued line
*/
client_fold_back(n,line->l);
continue;
}
/*
* got a pleasant greeting
*/
client_fold_back(n,line->l);
firestring_estr_sprintf(&outline,"EHLO %s\r\n",domain);
tls_backend_write(n,outline.s,outline.l);
backends[n].state = BACKEND_STATE_EHLO_RESPONSE;
return 0;
}
case BACKEND_STATE_EHLO_RESPONSE:
if (tls_backend_eread(n,&backends[n].buffer) != 0) {
close(backends[n].fd);
backends[n].state = BACKEND_STATE_NOTCONNECTED;
return 1;
}
while (1) {
line = client_get_line(n);
if (line == NULL)
return 0;
if (line->l >= 12 && firestring_strncasecmp(&line->s[4],"8BITMIME",8) == 0) {
/*
* we've got 8bit mime support
*/
backends[n].eightbit = 1;
}
if (line->l >= 14 && firestring_strncasecmp(&line->s[4],"PIPELINING",10) == 0) {
/*
* we've got pipelining support
*/
backends[n].pipelining = 1;
}
if (line->l >= 12 && firestring_strncasecmp(&line->s[4],"STARTTLS",8) == 0) {
/*
* we've got TLS support
*/
backends[n].tls = 1;
}
if (line->l >= 14 && firestring_strncasecmp(&line->s[4],"AUTH ",5) == 0 && firestring_estr_stristr(line,"PLAIN",9) != -1) {
/*
* we've got AUTH PLAIN support
*/
backends[n].auth_plain = 1;
}
if (line->l >= 4 && line->s[3] == '-') {
/*
* continued line
*/
client_fold_back(n,line->l);
continue;
}
if (line->l < 3 || line->s[0] != '2') {
fprintf(stderr,"{%d} [%d] BACKEND/WARNING: missing ESMTP support; please upgrade your MTA\n",process,n);
client_fold_back(n,line->l);
firestring_estr_sprintf(&outline,"HELO %s\r\n",domain);
tls_backend_write(n,outline.s,outline.l);
backends[n].state = BACKEND_STATE_HELO_RESPONSE;
return 0;
}
if (backends[n].pipelining == 0)
fprintf(stderr,"{%d} [%d] BACKEND/WARNING: missing PIPELINING support; backend conversations will be *much* slower\n",process,n);
if (sevenbit == 0 && backends[n].eightbit == 0) {
fprintf(stderr,"{%d} [%d] BACKEND/FATAL: missing 8BITMIME suport; get a modern MTA or set 7bit in the config file to override\n",process,n);
exit(100);
}
client_fold_back(n,line->l);
if (backend_cert != NULL && backends[n].tls == 1 && backends[n].ssl == NULL) {
backends[n].state = BACKEND_STATE_STARTTLS_RESPONSE;
tls_backend_write(n,"STARTTLS\r\n",10);
} else {
backends[n].state = BACKEND_STATE_IDLE;
fprintf(stderr,"{%d} [%d] BACKEND/STATUS: connection established\n",process,n);
}
return 0;
}
case BACKEND_STATE_HELO_RESPONSE:
if (tls_backend_eread(n,&backends[n].buffer) != 0) {
close(backends[n].fd);
backends[n].state = BACKEND_STATE_NOTCONNECTED;
return 1;
}
while (1) {
line = client_get_line(n);
if (line == NULL)
return 0;
if (line->l < 3 || line->s[0] != '2') {
firestring_estr_0(line);
fprintf(stderr,"{%d} [%d] BACKEND/FATAL: error in HELO response: %s\n",process,n,line->s);
close(backends[n].fd);
backends[n].state = BACKEND_STATE_NOTCONNECTED;
return 1;
}
if (line->l >= 4 && line->s[3] == '-') {
/*
* continued line
*/
client_fold_back(n,line->l);
continue;
}
client_fold_back(n,line->l);
backends[n].state = BACKEND_STATE_IDLE;
fprintf(stderr,"{%d} [%d] BACKEND/WARNING: missing PIPELINING support; backend conversations will be *much* slower\n",process,n);
if (sevenbit == 0) {
fprintf(stderr,"{%d} [%d] BACKEND/FATAL: missing 8BITMIME suport; get a modern MTA or set 7bit in the config file to override\n",process,n);
exit(100);
}
fprintf(stderr,"{%d} [%d] BACKEND/STATUS: connection established\n",process,n);
return 0;
}
case BACKEND_STATE_STARTTLS_RESPONSE:
if (tls_backend_eread(n,&backends[n].buffer) != 0) {
close(backends[n].fd);
backends[n].state = BACKEND_STATE_NOTCONNECTED;
return 1;
}
while (1) {
line = client_get_line(n);
if (line == NULL)
return 0;
if (line->l >= 4 && line->s[3] == '-') {
/*
* continued line
*/
client_fold_back(n,line->l);
continue;
}
client_fold_back(n,line->l);
if (line->l < 3 || line->s[0] != '2') {
/*
* TLS support failed
*/
backends[n].state = BACKEND_STATE_IDLE;
return 0;
}
backends[n].state = BACKEND_STATE_SSL_HANDSHAKE;
i = tls_client_start(n);
if (i == 1) {
fprintf(stderr,"{%d} [%d] TLS/ERROR: negotiation failed, falling back\n",process,n);
backends[n].state = BACKEND_STATE_IDLE;
return 0;
}
if (i == 2)
goto ssl_handshake;
return 0;
}
case BACKEND_STATE_SSL_HANDSHAKE:
if (tls_client_handshake(n) == 0) {
ssl_handshake:
firestring_estr_sprintf(&outline,"EHLO %s\r\n",domain);
tls_backend_write(n,outline.s,outline.l);
backends[n].state = BACKEND_STATE_EHLO_RESPONSE;
}
return 0;
case BACKEND_STATE_IDLE:
/*
* we're idle, just ignore the server
* if it disconnects, break out and parent will reconnect
*/
if (tls_backend_eread(n,&backends[n].buffer) != 0) {
close(backends[n].fd);
backends[n].state = BACKEND_STATE_NOTCONNECTED;
return 1;
}
while (1) {
line = client_get_line(n);
if (line == NULL)
return 0;
firestring_estr_0(line);
fprintf(stderr,"{%d} [%d] BACKEND/STATUS: non-reply line from server: '%s'\n",process,n,line->s);
client_fold_back(n,line->l);
}
break;
case BACKEND_STATE_CLEARPIPE:
/*
* we're getting pipe responses
*/
if (tls_backend_eread(n,&backends[n].buffer) != 0) {
close(backends[n].fd);
backends[n].state = BACKEND_STATE_NOTCONNECTED;
return 1;
}
while (1) {
line = client_get_line(n);
if (line == NULL)
return 0;
if (line->l < 3 || (line->s[0] != '2' && line->s[0] != '3')) {
clients[backends[n].client].num_to = 0;
if (clients[backends[n].client].error == 0) {
firestring_estr_0(line);
fprintf(stderr,"{%d} [%d] BACKEND/REJECT: refused message: '%s'\n",process,n,line->s);
if (line->l < 3 || line->s[0] != '4')
clients[backends[n].client].error = 5;
else
clients[backends[n].client].error = 4;
}
}
if (line->l >= 4 && line->s[3] != '-') {
/*
* not a continued line
*/
if (backends[n].pipelining == 1) {
backends[n].piped--;
if (backends[n].piped == 0) {
client_fold_back(n,line->l);
if (clients[backends[n].client].error != 0) {
/*
* pass error along to client, take everything to idle
*/
if (clients[backends[n].client].error == 4)
tls_client_write(backends[n].client,SMTP_REFUSED_TEMP,sizeof(SMTP_REFUSED_TEMP) - 1);
else
tls_client_write(backends[n].client,SMTP_REFUSED,sizeof(SMTP_REFUSED) - 1);
clients[backends[n].client].send_progress = 0;
client_reset(n);
return 0;
} else {
/*
* take it into write state and fill the outgoing tcp buffer
*/
backends[n].state = BACKEND_STATE_SENDING;
backends[n].sent = 0;
client_send(n);
return 0;
}
}
} else {
if (clients[backends[n].client].error != 0) {
if (clients[backends[n].client].error == 4)
tls_client_write(backends[n].client,SMTP_REFUSED_TEMP,sizeof(SMTP_REFUSED_TEMP) - 1);
else
tls_client_write(backends[n].client,SMTP_REFUSED,sizeof(SMTP_REFUSED) - 1);
clients[backends[n].client].send_progress = 0;
client_reset(n);
return 0;
}
backends[n].piped++;
if (backends[n].piped - 1 <= clients[backends[n].client].num_to) {
/*
* 2 - client.num_to + 1
* send another destination
*/
firestring_estr_sprintf(&outline,"RCPT TO:<%e>\r\n",&clients[backends[n].client].to[backends[n].piped - 2]);
tls_backend_write(n,outline.s,outline.l);
} else if (backends[n].piped - 2 == clients[backends[n].client].num_to) {
/*
* client.num_to + 2
* send DATA
*/
tls_backend_write(n,"DATA\r\n",6);
} else {
/*
* we're done
*/
backends[n].state = BACKEND_STATE_SENDING;
backends[n].sent = 0;
clients[backends[n].client].num_to = 0;
backends[n].piped = 0;
client_fold_back(n,line->l);
client_send(n);
return 0;
}
}
}
client_fold_back(n,line->l);
}
break;
case BACKEND_STATE_WAITDELIVER:
if (tls_backend_eread(n,&backends[n].buffer) != 0) {
close(backends[n].fd);
backends[n].state = BACKEND_STATE_NOTCONNECTED;
return 1;
}
while (1) {
line = client_get_line(n);
if (line == NULL)
return 0;
/*
* Patch from Russell Vincent <rv2@sanger.ac.uk>
* Applied by Douglas K. Rand <rand@meridian-enviro.com>
* Works around problem with MS Exchange servers not liking
* the \r\n showing up in a seperate packet.
* See http://messagewall.org/cgi-bin/ezmlm-browse.cgi?command=showthread&list=messagewall-discuss&month=200302&threadid=nnoiaogojliciagmgpbk
*/
firestring_estr_sprintf(&outline,"%e\r\n", line);
tls_client_write(backends[n].client,outline.s,outline.l);
firestring_estr_0(line);
fprintf(stderr,"{%d} [%d] BACKEND/ACCEPT: accepted message responsibility: '%s'\n",process,n,line->s);
if (line->l >= 4 && line->s[3] != '-') {
/*
* not a continued line
*/
client_fold_back(n,line->l);
clients[backends[n].client].send_progress = 0;
if (line->s[0] == '2')
backends[n].state = BACKEND_STATE_IDLE;
else
client_reset(n);
return 0;
}
client_fold_back(n,line->l);
}
break;
case BACKEND_STATE_RSET:
if (tls_backend_eread(n,&backends[n].buffer) != 0) {
close(backends[n].fd);
backends[n].state = BACKEND_STATE_NOTCONNECTED;
return 1;
}
while (1) {
line = client_get_line(n);
if (line == NULL)
return 0;
if (line->l >= 4 && line->s[3] != '-') {
/*
* not a continued line
*/
client_fold_back(n,line->l);
backends[n].state = BACKEND_STATE_IDLE;
return 0;
}
client_fold_back(n,line->l);
}
break;
}
return 0;
}
struct firestring_estr_t *client_get_line(int n) {
static struct firestring_estr_t ret;
int i;
i = firestring_estr_strstr(&backends[n].buffer,"\r\n",0);
if (i == -1)
return NULL;
ret.a = ret.l = i;
ret.s = backends[n].buffer.s;
return &ret;
}
void client_fold_back(int n, int i) {
backends[n].buffer.l -= i + 2;
memmove(backends[n].buffer.s,&backends[n].buffer.s[i + 2],backends[n].buffer.l);
}
int client_start(int backend, int client) {
int i;
static struct firestring_estr_t outline = {0};
if (outline.a == 0)
firestring_estr_alloc(&outline,SMTP_LINE_MAXLEN);
/*
* set progress indicators
*/
clients[client].send_progress = PROGRESS_WORKING;
backends[backend].state = BACKEND_STATE_CLEARPIPE;
clients[client].error = 0;
backends[backend].client = client;
clients[client].backend = backend;
fprintf(stderr,"{%d} (%d) BACKEND/STATUS: message to [%d]\n",process,client,backend);
firestring_estr_sprintf(&outline,"MAIL FROM:<%e>%s\r\n",&clients[client].from,sevenbit == 1 ? "" : " BODY=8BITMIME");
tls_backend_write(backend,outline.s,outline.l);
if (backends[backend].pipelining == 1) {
/*
* pipeline everything in
*/
for (i = 0; i < clients[client].num_to; i++) {
firestring_estr_sprintf(&outline,"RCPT TO:<%e>\r\n",&clients[client].to[i]);
tls_backend_write(backend,outline.s,outline.l);
}
tls_backend_write(backend,"DATA\r\n",6);
backends[backend].piped = i + 2;
clients[client].num_to = 0;
} else
/*
* send 'em one by one -- SLOW
*/
backends[backend].piped = 1;
return 0;
}
int client_send(int backend) {
int i, s;
/*
* send as much as we can
*/
s = backends[backend].sent;
i = tls_backend_write(backend,
&backends[backend].message.s[s],
backends[backend].message.l - s);
if (i == -1)
return 0;
if (i == 0) {
/*
* server went away on us
*/
fprintf(stderr,"{%d} [%d] BACKEND/FATAL: unable to write to backend\n",process,backend);
tls_client_write(backends[backend].client,SMTP_BACKEND_BROKEN,sizeof(SMTP_BACKEND_BROKEN) - 1);
clients[backends[backend].client].send_progress = 0;
client_connect(backend);
return 0;
}
backends[backend].sent += i;
if (backends[backend].sent == backends[backend].message.l) {
/*
* message done
* put socket back into blocking for a sec to send .
* because we can't tolerate failure here
*/
backends[backend].state = BACKEND_STATE_WAITDELIVER;
}
time(&clients[backends[backend].client].lasttalk);
return 0;
}
int client_reset(int backend) {
backends[backend].state = BACKEND_STATE_RSET;
tls_backend_write(backend,"RSET\r\n",6);
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1