From 4f078c1accf0fcc0c7feec6a55b3ba89adade829 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Sat, 18 Sep 2004 00:01:41 +0000 Subject: - Support for per io line buffers - Support for recipient and sender parsing - Added environment variable support - Better logging. --- common/smtppass.c | 235 +++++++++++++++++++++++++++++++++++-------------- common/smtppass.h | 64 ++++++++------ common/spio.c | 259 +++++++++++++++++++++++++++++++++++------------------- 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) @@ -831,30 +847,15 @@ static int smtp_passthru(spctx_t* ctx) continue; } - /* - * 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 + * 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 . 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; @@ -208,6 +213,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 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; -- cgit v1.2.3