summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common/smtppass.c235
-rw-r--r--common/smtppass.h64
-rw-r--r--common/spio.c259
3 files changed, 377 insertions, 181 deletions
diff --git a/common/smtppass.c b/common/smtppass.c
index c2d2136..b13bdac 100644
--- a/common/smtppass.c
+++ b/common/smtppass.c
@@ -174,6 +174,8 @@ static int smtp_passthru(spctx_t* ctx);
static int connect_out(spctx_t* ctx);
static int read_server_response(spctx_t* ctx);
static int parse_config_file(const char* configfile);
+static char* parse_address(char* line);
+static int is_successful_rsp(char* line);
/* Used externally in some cases */
int sp_parse_option(const char* name, const char* option);
@@ -602,6 +604,18 @@ static void cleanup_context(spctx_t* ctx)
ctx->cachename[0] = 0;
}
+ if(ctx->recipients)
+ {
+ free(ctx->recipients);
+ ctx->recipients = NULL;
+ }
+
+ if(ctx->sender)
+ {
+ free(ctx->sender);
+ ctx->sender = NULL;
+ }
+
ctx->logline[0] = 0;
}
@@ -762,17 +776,20 @@ static int connect_out(spctx_t* ctx)
static int smtp_passthru(spctx_t* ctx)
{
+ char* t;
int r, ret = 0;
unsigned int mask;
int neterror = 0;
int first_rsp = 1; /* The first 220 response from server to be filtered */
- int filter_ehlo = 0; /* Filtering parts of an EHLO extensions response */
int filter_host = 0; /* Next response is 250 hostname, which we change */
ASSERT(spio_valid(&(ctx->client)) &&
spio_valid(&(ctx->server)));
+ #define C_LINE ctx->client.line
+ #define S_LINE ctx->server.line
+
for(;;)
{
mask = spio_select(ctx, &(ctx->client), &(ctx->server), NULL);
@@ -786,15 +803,15 @@ static int smtp_passthru(spctx_t* ctx)
/* Client has data available, read a line and process */
if(mask & 1)
{
- if(spio_read_line(ctx, &(ctx->client), SPIO_DISCARD) == -1)
+ if((r = spio_read_line(ctx, &(ctx->client), SPIO_DISCARD)) == -1)
RETURN(-1);
/* Client disconnected, we're done */
- if(ctx->linelen == 0)
+ if(r == 0)
RETURN(0);
/* We don't let clients send really long lines */
- if(LINE_TOO_LONG(ctx))
+ if(LINE_TOO_LONG(r))
{
if(spio_write_data(ctx, &(ctx->client), SMTP_TOOLONG) == -1)
RETURN(-1);
@@ -803,11 +820,10 @@ static int smtp_passthru(spctx_t* ctx)
}
/* Only valid after EHLO or HELO commands */
- filter_ehlo = 0;
filter_host = 0;
/* Handle the DATA section via our AV checker */
- if(is_first_word(ctx->line, DATA_CMD, KL(DATA_CMD)))
+ if(is_first_word(C_LINE, DATA_CMD, KL(DATA_CMD)))
{
/* Send back the intermediate response to the client */
if(spio_write_data(ctx, &(ctx->client), SMTP_DATAINTERMED) == -1)
@@ -832,29 +848,14 @@ static int smtp_passthru(spctx_t* ctx)
}
/*
- * We filter out features that we can't support in
- * the EHLO response (ESMTP). See below
- */
- else if(is_first_word(ctx->line, EHLO_CMD, KL(EHLO_CMD)))
- {
- sp_messagex(ctx, LOG_DEBUG, "filtering EHLO response");
- filter_ehlo = 1;
- filter_host = 1;
-
- /* New email so cleanup */
- cleanup_context(ctx);
- }
-
- /*
* We need our response to HELO to be modified in order
* to prevent complaints about mail loops
*/
- else if(is_first_word(ctx->line, HELO_CMD, KL(HELO_CMD)))
+ else if(is_first_word(C_LINE, EHLO_CMD, KL(EHLO_CMD)) ||
+ is_first_word(C_LINE, HELO_CMD, KL(HELO_CMD)))
{
+ /* EHLO can have multline responses so we set a flag */
filter_host = 1;
-
- /* A new email so cleanup */
- cleanup_context(ctx);
}
/*
@@ -862,8 +863,8 @@ static int smtp_passthru(spctx_t* ctx)
* filtered out their service extensions earlier in the EHLO response.
* This is just for errant clients.
*/
- else if(is_first_word(ctx->line, STARTTLS_CMD, KL(STARTTLS_CMD)) ||
- is_first_word(ctx->line, BDAT_CMD, KL(BDAT_CMD)))
+ else if(is_first_word(C_LINE, STARTTLS_CMD, KL(STARTTLS_CMD)) ||
+ is_first_word(C_LINE, BDAT_CMD, KL(BDAT_CMD)))
{
sp_messagex(ctx, LOG_DEBUG, "ESMTP feature not supported");
@@ -874,20 +875,8 @@ static int smtp_passthru(spctx_t* ctx)
continue;
}
- /* Append recipients to log line */
- else if((r = check_first_word(ctx->line, FROM_CMD, KL(FROM_CMD), SMTP_DELIMS)) > 0)
- sp_add_log(ctx, "from=", ctx->line + r);
-
- /* Append sender to log line */
- else if((r = check_first_word(ctx->line, TO_CMD, KL(TO_CMD), SMTP_DELIMS)) > 0)
- sp_add_log(ctx, "to=", ctx->line + r);
-
- /* Reset log line */
- else if(is_first_word(ctx->line, RSET_CMD, KL(RSET_CMD)))
- cleanup_context(ctx);
-
/* All other commands just get passed through to server */
- if(spio_write_data(ctx, &(ctx->server), ctx->line) == -1)
+ if(spio_write_data(ctx, &(ctx->server), C_LINE) == -1)
RETURN(-1);
continue;
@@ -896,13 +885,13 @@ static int smtp_passthru(spctx_t* ctx)
/* Server has data available, read a line and forward */
if(mask & 2)
{
- if(spio_read_line(ctx, &(ctx->server), SPIO_DISCARD) == -1)
+ if((r = spio_read_line(ctx, &(ctx->server), SPIO_DISCARD)) == -1)
RETURN(-1);
- if(ctx->linelen == 0)
+ if(r == 0)
RETURN(0);
- if(LINE_TOO_LONG(ctx))
+ if(LINE_TOO_LONG(r))
sp_messagex(ctx, LOG_WARNING, "SMTP response line too long. discarded extra");
/*
@@ -921,7 +910,7 @@ static int smtp_passthru(spctx_t* ctx)
{
first_rsp = 0;
- if(is_first_word(ctx->line, START_RSP, KL(START_RSP)))
+ if(is_first_word(S_LINE, START_RSP, KL(START_RSP)))
{
sp_messagex(ctx, LOG_DEBUG, "intercepting initial response");
@@ -940,40 +929,49 @@ static int smtp_passthru(spctx_t* ctx)
*/
if(filter_host)
{
+ /* Can have multi-line responses, and we want to be
+ * sure to only replace the first one. */
filter_host = 0;
/* Check for a simple '250 xxxx' */
- if(is_first_word(ctx->line, OK_RSP, KL(OK_RSP)))
+ if(is_first_word(S_LINE, OK_RSP, KL(OK_RSP)))
{
sp_messagex(ctx, LOG_DEBUG, "intercepting host response");
if(spio_write_data(ctx, &(ctx->client), SMTP_HELO_RSP) == -1)
RETURN(-1);
+
+ /* A new email so cleanup */
+ cleanup_context(ctx);
+
continue;
}
/* Check for the continued response '250-xxxx' */
- if(check_first_word(ctx->line, OK_RSP, KL(OK_RSP), SMTP_MULTI_DELIMS) > 0)
+ if(check_first_word(S_LINE, OK_RSP, KL(OK_RSP), SMTP_MULTI_DELIMS) > 0)
{
sp_messagex(ctx, LOG_DEBUG, "intercepting host response");
if(spio_write_data(ctx, &(ctx->client), SMTP_EHLO_RSP) == -1)
RETURN(-1);
+ /* New email so cleanup */
+ cleanup_context(ctx);
+
continue;
}
}
- /*
- * Filter out any EHLO responses that we can't or don't want
- * to support. For example pipelining or TLS.
- */
- if(filter_ehlo)
+ if(is_successful_rsp(S_LINE))
{
- if((r = check_first_word(ctx->line, OK_RSP, KL(OK_RSP), SMTP_MULTI_DELIMS)) > 0)
+ /*
+ * Filter out any EHLO responses that we can't or don't want
+ * to support. For example pipelining or TLS.
+ */
+ if(is_first_word(C_LINE, EHLO_CMD, KL(EHLO_CMD)))
{
- char* p = ctx->line + r;
+ char* p = S_LINE + r;
if(is_first_word(p, ESMTP_PIPELINE, KL(ESMTP_PIPELINE)) ||
is_first_word(p, ESMTP_TLS, KL(ESMTP_TLS)) ||
is_first_word(p, ESMTP_CHUNK, KL(ESMTP_CHUNK)) ||
@@ -984,9 +982,45 @@ static int smtp_passthru(spctx_t* ctx)
continue;
}
}
+
+ /* MAIL FROM */
+ if((r = check_first_word(C_LINE, FROM_CMD, KL(FROM_CMD), SMTP_DELIMS)) > 0)
+ {
+ t = parse_address(C_LINE + r);
+ sp_add_log(ctx, "from=", t);
+
+ /* Make note of the sender for later */
+ ctx->sender = (char*)reallocf(ctx->sender, strlen(t) + 1);
+ if(ctx->sender)
+ strcpy(ctx->sender, t);
+ }
+
+ /* RCPT TO */
+ else if((r = check_first_word(C_LINE, TO_CMD, KL(TO_CMD), SMTP_DELIMS)) > 0)
+ {
+ t = parse_address(C_LINE + r);
+ sp_add_log(ctx, "to=", t);
+
+ /* Make note of the recipient for later */
+ r = ctx->recipients ? strlen(ctx->recipients) : 0;
+ ctx->recipients = (char*)reallocf(ctx->recipients, r + strlen(t) + 2);
+ if(ctx->recipients)
+ {
+ /* Recipients are separated by lines */
+ if(r != 0)
+ strcat(ctx->recipients, "\n");
+ strcat(ctx->recipients, t);
+ }
+ }
+
+ /* RSET */
+ else if(is_first_word(C_LINE, RSET_CMD, KL(RSET_CMD)))
+ {
+ cleanup_context(ctx);
+ }
}
- if(spio_write_data(ctx, &(ctx->client), ctx->line) == -1)
+ if(spio_write_data(ctx, &(ctx->client), S_LINE) == -1)
RETURN(-1);
continue;
@@ -1005,6 +1039,44 @@ cleanup:
* SMTP PASSTHRU FUNCTIONS FOR DATA CHECK
*/
+static char* parse_address(char* line)
+{
+ char* t;
+ line = trim_start(line);
+
+ /*
+ * We parse out emails in the form of <blah@blah.com>
+ * as well as accept other addresses.
+ */
+ if(line[0] == '<')
+ {
+ if((t = strchr(line, '>')) != NULL)
+ {
+ *t = 0;
+ line++;
+ return line;
+ }
+ }
+
+ return trim_end(line);
+}
+
+static int is_successful_rsp(char* line)
+{
+ /*
+ * We check for both '250 xxx' type replies
+ * and the continued response '250-xxxx' type
+ */
+
+ line = trim_start(line);
+
+ if(line[0] == '2' && isdigit(line[1]) && isdigit(line[2]) &&
+ (line[3] == ' ' || line[3] == '-'))
+ return 1;
+
+ return 0;
+}
+
void sp_add_log(spctx_t* ctx, char* prefix, char* line)
{
int l = SP_LINE_LENGTH;
@@ -1028,12 +1100,14 @@ void sp_add_log(spctx_t* ctx, char* prefix, char* line)
int sp_read_data(spctx_t* ctx, const char** data)
{
+ int r;
+
ASSERT(ctx);
ASSERT(data);
*data = NULL;
- switch(spio_read_line(ctx, &(ctx->client), SPIO_QUIET))
+ switch(r = spio_read_line(ctx, &(ctx->client), SPIO_QUIET))
{
case 0:
sp_messagex(ctx, LOG_ERR, "unexpected end of data from client");
@@ -1043,13 +1117,13 @@ int sp_read_data(spctx_t* ctx, const char** data)
return -1;
};
- if(ctx->_crlf && strcmp(ctx->line, DATA_END_SIG) == 0)
+ if(ctx->_crlf && strcmp(ctx->client.line, DATA_END_SIG) == 0)
return 0;
/* Check if this line ended with a CRLF */
- ctx->_crlf = (strcmp(CRLF, ctx->line + (ctx->linelen - KL(CRLF))) == 0);
- *data = ctx->line;
- return ctx->linelen;
+ ctx->_crlf = (strcmp(CRLF, ctx->client.line + (r - KL(CRLF))) == 0);
+ *data = ctx->client.line;
+ return r;
}
int sp_write_data(spctx_t* ctx, const char* buf, int len)
@@ -1140,6 +1214,7 @@ int sp_done_data(spctx_t* ctx, const char* header)
FILE* file = 0;
int had_header = 0;
int ret = 0;
+ char line[SP_LINE_LENGTH];
ASSERT(ctx->cachename[0]); /* Must still be around */
ASSERT(!ctx->cachefile); /* File must be closed */
@@ -1160,9 +1235,9 @@ int sp_done_data(spctx_t* ctx, const char* header)
RETURN(-1);
/* If server returns an error then tell the client */
- if(!is_first_word(ctx->line, DATA_RSP, KL(DATA_RSP)))
+ if(!is_first_word(ctx->server.line, DATA_RSP, KL(DATA_RSP)))
{
- if(spio_write_data(ctx, &(ctx->client), ctx->line) == -1)
+ if(spio_write_data(ctx, &(ctx->client), ctx->server.line) == -1)
RETURN(-1);
sp_messagex(ctx, LOG_DEBUG, "server refused data transfer");
@@ -1173,15 +1248,24 @@ int sp_done_data(spctx_t* ctx, const char* header)
sp_messagex(ctx, LOG_DEBUG, "sending from cache file: %s", ctx->cachename);
/* Transfer actual file data */
- while(fgets(ctx->line, SP_LINE_LENGTH, file) != NULL)
+ while(fgets(line, SP_LINE_LENGTH, file) != NULL)
{
+ /*
+ * If the line is <CRLF>.<CRLF> we need to change it so that
+ * it doesn't end the email. We do this by adding a space.
+ * This won't occur much in clamsmtpd, but proxsmtpd might
+ * have filters that accidentally put this in.
+ */
+ if(strcmp(line, "." CRLF) == 0)
+ strncpy(line, ". " CRLF, SP_LINE_LENGTH);
+
if(header && !had_header)
{
/*
* The first blank line we see means the headers are done.
* At this point we add in our virus checked header.
*/
- if(is_blank_line(ctx->line))
+ if(is_blank_line(line))
{
if(spio_write_data_raw(ctx, &(ctx->server), (char*)header, strlen(header)) == -1 ||
spio_write_data_raw(ctx, &(ctx->server), CRLF, KL(CRLF)) == -1)
@@ -1191,7 +1275,7 @@ int sp_done_data(spctx_t* ctx, const char* header)
}
}
- if(spio_write_data_raw(ctx, &(ctx->server), ctx->line, strlen(ctx->line)) == -1)
+ if(spio_write_data_raw(ctx, &(ctx->server), line, strlen(line)) == -1)
RETURN(-1);
}
@@ -1211,7 +1295,7 @@ int sp_done_data(spctx_t* ctx, const char* header)
if(read_server_response(ctx) == -1)
RETURN(-1);
- if(spio_write_data(ctx, &(ctx->client), ctx->line) == -1)
+ if(spio_write_data(ctx, &(ctx->client), ctx->server.line) == -1)
RETURN(-1);
cleanup:
@@ -1260,11 +1344,13 @@ int sp_fail_data(spctx_t* ctx, const char* smtp_status)
static int read_server_response(spctx_t* ctx)
{
+ int r;
+
/* Read response line from the server */
- if(spio_read_line(ctx, &(ctx->server), SPIO_DISCARD) == -1)
+ if((r = spio_read_line(ctx, &(ctx->server), SPIO_DISCARD)) == -1)
return -1;
- if(ctx->linelen == 0)
+ if(r == 0)
{
sp_messagex(ctx, LOG_ERR, "server disconnected unexpectedly");
@@ -1273,12 +1359,27 @@ static int read_server_response(spctx_t* ctx)
return 0;
}
- if(LINE_TOO_LONG(ctx))
+ if(LINE_TOO_LONG(r))
sp_messagex(ctx, LOG_WARNING, "SMTP response line too long. discarded extra");
return 0;
}
+void sp_setup_env(spctx_t* ctx)
+{
+ if(ctx->sender)
+ setenv("SENDER", ctx->sender, 1);
+
+ if(ctx->recipients)
+ setenv("RECIPIENTS", ctx->recipients, 1);
+
+ if(ctx->cachename[0])
+ setenv("EMAIL", ctx->cachename, 1);
+
+ setenv("TMP", g_state.directory, 1);
+}
+
+
/* ----------------------------------------------------------------------------------
* LOGGING
*/
diff --git a/common/smtppass.h b/common/smtppass.h
index 08faa34..c287067 100644
--- a/common/smtppass.h
+++ b/common/smtppass.h
@@ -50,20 +50,30 @@ struct spctx;
* only things that are currently used go here.
*/
+/*
+ * A generous maximum line length. It needs to be longer than
+ * a full path on this system can be, because we pass the file
+ * name to clamd.
+ */
+
+#if 2000 > MAXPATHLEN
+ #define SP_LINE_LENGTH 2000
+#else
+ #define SP_LINE_LENGTH (MAXPATHLEN + 128)
+#endif
+
typedef struct spio
{
int fd; /* The file descriptor wrapped */
const char* name; /* The name for logging */
- #define SPIO_BUFLEN 256
- unsigned char _bf[SPIO_BUFLEN];
+
+ /* Internal use only */
+ char line[SP_LINE_LENGTH];
+ char* _nx;
size_t _ln;
}
spio_t;
-#define SPIO_TRIM 0x00000001
-#define SPIO_DISCARD 0x00000002
-#define SPIO_QUIET 0x00000004
-
#define spio_valid(io) ((io)->fd != -1)
/* Setup the io structure (allocated elsewhere */
@@ -73,13 +83,18 @@ void spio_init(spio_t* io, const char* name);
int spio_connect(struct spctx* ctx, spio_t* io, const struct sockaddr_any* sany, const char* addrname);
void spio_disconnect(struct spctx* ctx, spio_t* io);
-/* Read a line from a socket. Use options above */
-int spio_read_line(struct spctx* ctx, spio_t* io, int opts);
+#define SPIO_TRIM 0x00000001
+#define SPIO_DISCARD 0x00000002
+#define SPIO_QUIET 0x00000004
+
+/* Read a line from a socket. Use options above. Line
+ * will be found in io->line */
+int spio_read_line(struct spctx* ctx, spio_t* io, int opts);
/* Write data to socket (must supply line endings if needed).
* Guaranteed to accept all data or fail. */
-int spio_write_data(struct spctx* ctx, spio_t* io, const char* data);
-int spio_write_data_raw(struct spctx* ctx, spio_t* io, unsigned char* buf, int len);
+int spio_write_data(struct spctx* ctx, spio_t* io, const char* data);
+int spio_write_data_raw(struct spctx* ctx, spio_t* io, unsigned char* buf, int len);
/* Empty the given socket */
void spio_read_junk(struct spctx* sp, spio_t* io);
@@ -92,18 +107,6 @@ unsigned int spio_select(struct spctx* ctx, ...);
* SMTP PASS THROUGH FUNCTIONALITY
*/
-/*
- * A generous maximum line length. It needs to be longer than
- * a full path on this system can be, because we pass the file
- * name to clamd.
- */
-
-#if 2000 > MAXPATHLEN
- #define SP_LINE_LENGTH 2000
-#else
- #define SP_LINE_LENGTH (MAXPATHLEN + 128)
-#endif
-
typedef struct spctx
{
unsigned int id; /* Identifier for the connection */
@@ -111,14 +114,16 @@ typedef struct spctx
spio_t client; /* Connection to client */
spio_t server; /* Connection to server */
- char logline[SP_LINE_LENGTH]; /* Log line */
- char line[SP_LINE_LENGTH]; /* Working buffer */
- int linelen; /* Length of valid data in above */
-
FILE* cachefile; /* The file handle for the cached file */
char cachename[MAXPATHLEN]; /* The name of the file that we cache into */
+ char logline[SP_LINE_LENGTH]; /* Log line */
+
+ char* sender; /* The email of the sender */
+ char* recipients; /* The email of the recipients */
int _crlf; /* Private data */
+ char _l1[SP_LINE_LENGTH];
+ char _l2[SP_LINE_LENGTH];
}
spctx_t;
@@ -209,6 +214,13 @@ int sp_done_data(spctx_t* ctx, const char* header);
int sp_fail_data(spctx_t* ctx, const char* smtp_status);
/*
+ * Setup the environment with context info. This is useful
+ * if you're going to fork another process. Be sure to fork
+ * soon after to prevent the strings from going out of scope.
+ */
+void sp_setup_env(spctx_t* ctx);
+
+/*
* Log a message. levels are syslog levels. Syntax is just
* like printf etc.. Can specify a ctx of NULL in which case
* no connection prefix is prepended.
diff --git a/common/spio.c b/common/spio.c
index ff5a473..6677746 100644
--- a/common/spio.c
+++ b/common/spio.c
@@ -65,6 +65,7 @@
#define MAX_LOG_LINE 79
#define GET_IO_NAME(io) ((io)->name ? (io)->name : "??? ")
+#define HAS_EXTRA(io) ((io)->_ln > 0)
static void close_raw(int* fd)
{
@@ -127,6 +128,11 @@ int spio_connect(spctx_t* ctx, spio_t* io, const struct sockaddr_any* sany,
if(connect(io->fd, &SANY_ADDR(*sany), SANY_LEN(*sany)) == -1)
RETURN(-1);
+ /* As a double check */
+ io->line[0] = 0;
+ io->_nx = NULL;
+ io->_ln = 0;
+
cleanup:
if(ret < 0)
{
@@ -173,7 +179,7 @@ unsigned int spio_select(spctx_t* ctx, ...)
break;
/* Check if the buffer has something in it */
- if(io->_ln > 0)
+ if(HAS_EXTRA(io))
ret |= (1 << i);
/* Mark for select */
@@ -221,134 +227,210 @@ unsigned int spio_select(spctx_t* ctx, ...)
return ret;
}
-int spio_read_line(spctx_t* ctx, spio_t* io, int opts)
+int read_raw(spctx_t* ctx, spio_t* io, int opts)
{
- int l, x;
- char* t;
- unsigned char* p;
+ int len, x;
+ char* at;
+ char* p;
+
+ /*
+ * Just a refresher:
+ *
+ * _nx: Extra data read on last read.
+ * _ln: Length of that extra data.
+ *
+ * _nx should never be equal to line when entering this
+ * function. And _ln should always be less than a full
+ * buffer.
+ */
+
+ /* Remaining data in the buffer */
+ if(io->_nx && io->_ln > 0)
+ {
+ ASSERT(!io->_nx || io->_nx > io->line);
+ ASSERT(io->_ln < SP_LINE_LENGTH);
+ ASSERT(io->_nx + io->_ln < io->line + SP_LINE_LENGTH);
- ASSERT(ctx && io);
+ /* Check for a return in the current buffer */
+ if((p = (char*)memchr(io->_nx, '\n', io->_ln)) != NULL)
+ {
+ /* Move data to front */
+ x = (p - io->_nx) + 1;
+ ASSERT(x > 0);
+ memmove(io->line, io->_nx, x);
- if(!spio_valid(io))
- {
- sp_messagex(ctx, LOG_WARNING, "tried to read from a closed connection");
- return 0;
+ /* Null teriminate it */
+ io->line[x] = 0;
+
+ /* Do maintanence for next time around */
+ io->_ln -= x;
+ io->_nx += x;
+
+ /* A double check on the return value */
+ ASSERT(strlen(io->line) == x);
+ return x;
+ }
+
+ /* Otherwise move all old data to front */
+ memmove(io->line, io->_nx, io->_ln);
+
+ /* We always leave space for a null terminator */
+ len = (SP_LINE_LENGTH - io->_ln) - 1;
+ at = io->line + io->_ln;
}
- ctx->line[0] = 0;
- t = ctx->line;
- l = SP_LINE_LENGTH - 1;
+ /* No data at front just read straight in */
+ else
+ {
+ /* We always leave space for a null terminator */
+ len = SP_LINE_LENGTH - 1;
+ at = io->line;
+ }
for(;;)
{
- /* refil buffer if necessary */
- if(io->_ln == 0)
- {
- ASSERT(io->fd != -1);
- io->_ln = read(io->fd, io->_bf, sizeof(char) * SPIO_BUFLEN);
+ /* Read a block of data */
+ ASSERT(io->fd != -1);
+ x = read(io->fd, at, sizeof(char) * len);
- if(io->_ln == -1)
+ if(x == -1)
+ {
+ if(errno == EINTR)
{
- io->_ln = 0;
-
- if(errno == EINTR)
- {
- /* When the application is quiting */
- if(sp_is_quit())
- return -1;
+ /* When the application is quiting */
+ if(sp_is_quit())
+ return -1;
- /* For any other signal we go again */
- continue;
- }
+ /* For any other signal we go again */
+ continue;
+ }
- if(errno == ECONNRESET) /* Not usually a big deal so supresse the error */
- sp_messagex(ctx, LOG_DEBUG, "connection disconnected by peer: %s", GET_IO_NAME(io));
- else if(errno == EAGAIN)
- sp_messagex(ctx, LOG_WARNING, "network read operation timed out: %s", GET_IO_NAME(io));
- else
- sp_message(ctx, LOG_ERR, "couldn't read data from socket: %s", GET_IO_NAME(io));
+ if(errno == ECONNRESET) /* Not usually a big deal so supresse the error */
+ sp_messagex(ctx, LOG_DEBUG, "connection disconnected by peer: %s", GET_IO_NAME(io));
+ else if(errno == EAGAIN)
+ sp_messagex(ctx, LOG_WARNING, "network read operation timed out: %s", GET_IO_NAME(io));
+ else
+ sp_message(ctx, LOG_ERR, "couldn't read data from socket: %s", GET_IO_NAME(io));
- /*
- * The basic logic here is that if we've had a fatal error
- * reading from the socket once then we shut it down as it's
- * no good trying to read from again later.
- */
- close_raw(&(io->fd));
+ /*
+ * The basic logic here is that if we've had a fatal error
+ * reading from the socket once then we shut it down as it's
+ * no good trying to read from again later.
+ */
+ close_raw(&(io->fd));
- return -1;
- }
+ return -1;
}
/* End of data */
- if(io->_ln == 0)
- break;
+ else if(x == 0)
+ {
+ /* Maintenance for remaining data */
+ io->_nx = NULL;
+ io->_ln = 0;
- /* Check for a new line */
- p = (unsigned char*)memchr(io->_bf, '\n', io->_ln);
+ /* A double check on the return value */
+ ASSERT(strlen(io->line) == at - io->line);
+ return at - io->line;
+ }
+ /* Check for a new line */
+ p = (char*)memchr(at, '\n', x);
if(p != NULL)
{
- x = (p - io->_bf) + 1;
- io->_ln -= x;
+ p++;
+ len = x - (p - at);
+
+ /* Insert the null terminator */
+ memmove(p, p + 1, len);
+ *p = 0;
+
+ /* Do maintenence for remaining data */
+ io->_nx = p + 1;
+ io->_ln = len;
+
+ /* A double check on the return value */
+ ASSERT(strlen(io->line) == p - io->line);
+ return p - io->line;
}
- else
+ if(len <= 0)
{
- x = io->_ln;
+ /* Keep reading until we hit a new line */
+ if(opts & SPIO_DISCARD)
+ {
+ /*
+ * K, basically the logic is that we're discarding
+ * data ond the data will be screwed up. So overwriting
+ * some valid data in order to flush the line and
+ * keep the buffering simple is a price we pay gladly :)
+ */
+
+ ASSERT(128 < SP_LINE_LENGTH);
+ at = (io->line + SP_LINE_LENGTH) - 128;
+ len = 128;
+
+ /* Go for next read */
+ continue;
+ }
+
+ io->_nx = NULL;
io->_ln = 0;
- }
- if(x > l)
- x = l;
+ /* Null terminate */
+ io->line[SP_LINE_LENGTH] = 0;
- /* Copy from buffer line */
- memcpy(t, io->_bf, x);
- t += x;
- l -= x;
+ /* A double check on the return value */
+ ASSERT(strlen(io->line) == p - io->line);
+ return SP_LINE_LENGTH;
+ }
+ }
+}
- /* Move whatever we have in the buffer to the front */
- if(io->_ln > 0)
- memmove(io->_bf, io->_bf + x, io->_ln);
+int spio_read_line(spctx_t* ctx, spio_t* io, int opts)
+{
+ int x, l;
+ char* t;
- /* Found a new line, done */
- if(p != NULL)
- break;
+ ASSERT(ctx && io);
- /* If discarding then don't break when full */
- if(!(opts && SPIO_DISCARD) && l == 0)
- break;
+ if(!spio_valid(io))
+ {
+ sp_messagex(ctx, LOG_WARNING, "tried to read from a closed connection");
+ return 0;
}
- ctx->linelen = (SP_LINE_LENGTH - l) - 1;
- ASSERT(ctx->linelen < SP_LINE_LENGTH);
- ctx->line[ctx->linelen] = 0;
+ x = read_raw(ctx, io, opts);
- if(opts & SPIO_TRIM && ctx->linelen > 0)
+ if(x > 0)
{
- t = ctx->line;
+ if(opts & SPIO_TRIM)
+ {
+ t = io->line;
- while(*t && isspace(*t))
- t++;
+ while(*t && isspace(*t))
+ t++;
- /* Bump the entire line down */
- l = t - ctx->line;
- memmove(ctx->line, t, (ctx->linelen + 1) - l);
- ctx->linelen -= l;
+ /* Bump the entire line down */
+ l = t - io->line;
+ memmove(io->line, t, (x + 1) - l);
+ x -= l;
- /* Now the end */
- t = ctx->line + ctx->linelen;
+ /* Now the end */
+ t = io->line + x;
- while(t > ctx->line && isspace(*(t - 1)))
- {
- *(--t) = 0;
- ctx->linelen--;
+ while(t > io->line && isspace(*(t - 1)))
+ {
+ *(--t) = 0;
+ x--;
+ }
}
- }
- if(!(opts & SPIO_QUIET))
- log_io_data(ctx, io, ctx->line, 1);
+ if(!(opts & SPIO_QUIET))
+ log_io_data(ctx, io, io->line, 1);
+ }
- return ctx->linelen;
+ return x;
}
int spio_write_data(spctx_t* ctx, spio_t* io, const char* data)
@@ -428,6 +510,7 @@ void spio_read_junk(spctx_t* ctx, spio_t* io)
/* Truncate any data in buffer */
io->_ln = 0;
+ io->_nx = 0;
if(!spio_valid(io))
return;