/*****************************************************************************
POPular -- A POP3 server and proxy for large mail systems
$Id: io.c,v 1.22 2004/08/21 10:19:29 sqrt Exp $
http://www.remote.org/jochen/mail/popular/
******************************************************************************
Copyright (C) 1999-2004 Jochen Topf <jochen@remote.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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 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
******************************************************************************
In this file a few functions for abstracting IO operations are defined.
These functions are needed so that every read or write operation can
transparently be performed on a normal socket or on a TLS connection if
TLS is compiled in. Also the IO context holds a buffer that is used
for read operations (especially line oriented read). There is no buffer
for write operations.
*****************************************************************************/
#if HAVE_CONFIG_H
# include <config.h>
#endif
#include "popular.h"
#ifdef USE_TLS
#define TLS_ERR_MSG_MAX 1024
void
tls_xlogdie(int err, int level, char *msg)
{
char tls_err_buf[TLS_ERR_MSG_MAX];
unsigned long tls_err = ERR_get_error();
xlog_printf(level, 0x0000, "tls_err %s tls_err_msg=%s", msg, ERR_error_string(tls_err, tls_err_buf));
exit(err);
}
/*****************************************************************************
io_tls_errmsg()
Returns last TLS error in static buffer.
*****************************************************************************/
char *
io_tls_errmsg()
{
static char tls_err_buf[TLS_ERR_MSG_MAX];
unsigned long tls_err = ERR_get_error();
return ERR_error_string(tls_err, tls_err_buf);
}
/*****************************************************************************
io_tls_seed_prng()
Seed PRNG (pseudo random number generator) from a file if USE_RANDOM_FILE
is defined. If it is not defined, the PRNG will not be seeded! This is
insecure! You have been warned!
*****************************************************************************/
int
io_tls_seed_prng(const char *filename, int bytes)
{
if (RAND_load_file(filename, bytes) != bytes) return 0;
return 1;
}
/*****************************************************************************
io_tls_main_setup()
Setup TLS stuff. This must be called once at the beginning of a program
that uses TLS.
*****************************************************************************/
void
io_tls_main_setup(void)
{
SSL_library_init();
SSL_load_error_strings();
}
/*****************************************************************************
io_tls_vserv_setup()
Setup TLS stuff for each virtual server.
*****************************************************************************/
int
io_tls_vserv_setup(const char *tlsdir, int allowsslv2, struct virt_serv *vs)
{
SSL_METHOD *ssl_meth;
char keyfile[MAX_FILE_LEN+MAXLEN_ID+20];
ssl_meth = SSLv23_server_method();
if (! ssl_meth) {
/* XLOG-DOC:SOS:0196:server_method_failed
* A SSL*_server_method() call failed. This probably means that there
* is something wrong with your openssl library setup. */
xlog_printf(xlog_sos, 0x0196, "server_method_failed tls_err_msg=%s", io_tls_errmsg());
exit(RCODE_ERR);
}
vs->ssl_ctx = SSL_CTX_new(ssl_meth);
if (! vs->ssl_ctx) {
/* XLOG-DOC:SOS:0197:SSL_CTX_new_failed
* A SSL_CTX_new call failed. This probably means that there
* is something wrong with your openssl library setup. */
xlog_printf(xlog_sos, 0x0197, "SSL_CTX_new_failed tls_err_msg=%s", io_tls_errmsg());
exit(RCODE_ERR);
}
SSL_CTX_set_mode(vs->ssl_ctx, SSL_MODE_AUTO_RETRY);
SSL_CTX_set_options(vs->ssl_ctx, SSL_OP_ALL | (allowsslv2 ? 0 : SSL_OP_NO_SSLv2));
strlcpy(keyfile, tlsdir, sizeof(keyfile));
strlcat(keyfile, "/", sizeof(keyfile));
strlcat(keyfile, vs->id, sizeof(keyfile));
strlcat(keyfile, ".pem", sizeof(keyfile));
if (! SSL_CTX_use_RSAPrivateKey_file(vs->ssl_ctx, keyfile, SSL_FILETYPE_PEM)) {
/* XLOG-DOC:ADM:019d:key_file_error
* The TLS key file could not be opened. (Call to
* SSL_CTX_use_RSAPrivateKey_file failed.) */
xlog_printf(xlog_adm, 0x019d, "key_file_error tls_err_msg=%s", io_tls_errmsg());
return 0;
}
if (! SSL_CTX_use_certificate_file(vs->ssl_ctx, keyfile, SSL_FILETYPE_PEM)) {
/* XLOG-DOC:ADM:019e:certificate_file_error
* The TLS key file could not be opened. (Call to
* SSL_CTX_use_RSAPrivateKey_file failed.) */
xlog_printf(xlog_adm, 0x019e, "certificate_file_error tls_err_msg=%s", io_tls_errmsg());
return 0;
}
return 1;
}
/*****************************************************************************
io_tls_vserv_shutdown()
Shuts down SSL stuff for virtual server.
*****************************************************************************/
void
io_tls_vserv_shutdown(struct virt_serv *vs)
{
SSL_CTX_free(vs->ssl_ctx);
vs->ssl_ctx = NULL;
}
/*****************************************************************************
io_tls_init()
Initialize TLS part of IO context.
*****************************************************************************/
struct io_ctx *
io_tls_init(struct io_ctx *ioc)
{
DEBUG0(DG_TLS, "io_tls_init", "start");
ioc->io_type = iot_tls;
ioc->io_tls = SSL_new(ioc->io_vserv->ssl_ctx);
if (! ioc->io_tls) {
/* XLOG-DOC:ERR:0199:tls_init_failed
* Initialization of new TLS connection failed (SSL_new call failed). */
xlog_printf(xlog_err, 0x0199, "tls_init_failed tls_err_msg=%s", io_tls_errmsg());
return 0;
}
if (! SSL_set_fd(ioc->io_tls, ioc->io_fd)) {
tls_xlogdie(RCODE_ERR, xlog_err, "SSL_set_fd");
/* XLOG-DOC:ERR:019a:tls_set_fd_failed
* Initialization of new TLS connection failed (SSL_set_fd call failed). */
xlog_printf(xlog_err, 0x019a, "tls_set_fd_failed tls_err_msg=%s", io_tls_errmsg());
return 0;
}
if (! SSL_accept(ioc->io_tls)) {
/* XLOG-DOC:ERR:019b:tls_accept
* Initialization of new TLS connection failed (SSL_accept call failed). */
xlog_printf(xlog_err, 0x019b, "tls_accept tls_err_msg=%s", io_tls_errmsg());
return 0;
}
DEBUG0(DG_TLS, "io_tls_init", "end");
return ioc;
}
#endif
/*****************************************************************************
io_init()
Initialize IO context.
*****************************************************************************/
struct io_ctx *
io_init(io_type_t iot, int fd, const char *desc, int nullcheck, int timeout, struct virt_serv *vserv)
{
struct io_ctx *ioc = calloc(1, sizeof(struct io_ctx));
if (! ioc) return NULL;
ioc->io_type = iot;
ioc->io_fd = fd;
ioc->io_nullcheck = nullcheck;
ioc->io_timeout = timeout;
ioc->io_desc = desc;
ioc->io_bufptr = ioc->io_ibuf;
ioc->io_vserv = vserv;
#ifdef USE_TLS
if (iot == iot_tls) {
if (! io_tls_init(ioc)) {
free(ioc);
return NULL;
}
}
#endif /* USE_TLS */
return ioc;
}
/*****************************************************************************
io_destroy()
Destroy IO context. This frees the memory allocated for the buffer and
context structure. The file descriptor is *not* closed.
*****************************************************************************/
int
io_destroy(struct io_ctx *ioc)
{
if (!ioc) return 0;
#ifdef USE_TLS
if (ioc->io_type == iot_tls) {
SSL_shutdown(ioc->io_tls);
}
#endif /* USE_TLS */
free(ioc);
return 0;
}
/*****************************************************************************
io_sysread()
*****************************************************************************/
ssize_t
io_sysread(struct io_ctx *ioc, char *buf, size_t count)
{
int len, i;
#ifdef USE_TLS
if (ioc->io_type == iot_tls) {
len = SSL_read(ioc->io_tls, buf, count);
} else {
len = read(ioc->io_fd, buf, count-1);
}
#else
len = read(ioc->io_fd, buf, count-1);
#endif
if (ioc->io_nullcheck) {
for (i=0; i<len; i++) {
if (! buf[i]) {
/* XLOG-DOC:ADM:0035:null_byte_in_input
* A null byte was encountered in input from a network socket. This
* can never happen in a normal POP3 connection. */
xlog_printf(xlog_adm, 0x0035, "null_byte_in_input ctx='%s' len=%d data='%s'", ioc->io_desc, len, buf);
/* Uh oh, nice to overload socket errcos for application level signalization */
errno = EPIPE;
return -1;
}
}
}
buf[len] = '\0';
return len;
}
/*****************************************************************************
io_pending()
This function returns true if there is data pending in an application
buffer for this IO context. For normal and TLS contexts something can be
buffered in the IO context buffer. If nothing is buffered there it will
check SSL_pending() for TLS contexts.
Note that this function doesn't take it into account if there is data
pending in some kernel buffer. You still have to call select () for this.
*****************************************************************************/
int
io_pending(struct io_ctx *ioc)
{
if ((ioc->io_ibuf != ioc->io_bufptr) && (ioc->io_bufptr[0] != '\0')) return 1;
#ifdef USE_TLS
if (ioc->io_type == iot_tls) return SSL_pending(ioc->io_tls);
#endif
return 0;
}
/*****************************************************************************
io_read()
*****************************************************************************/
ssize_t
io_read(struct io_ctx *ioc, char *buf, size_t count)
{
int len;
DEBUG1(DG_IO, "io_read", "called for ctx='%s'", ioc->io_desc);
if ((ioc->io_ibuf == ioc->io_bufptr) || (ioc->io_bufptr[0] == '\0')) {
ioc->io_bufptr = ioc->io_ibuf;
DEBUG0(DG_IO, "io_read", "calling sysread");
return io_sysread(ioc, buf, count);
} else {
DEBUG1(DG_IO, "io_read", "reading from buffer content='%s'", ioc->io_bufptr);
len = strlcpy(buf, ioc->io_bufptr, count);
if (len >= count) {
ioc->io_bufptr += count;
DEBUG0(DG_IO, "io_read", "still something left");
return count;
} else {
ioc->io_bufptr = ioc->io_ibuf;
DEBUG0(DG_IO, "io_read", "got all");
return len;
}
}
}
/*****************************************************************************
io_readln()
Read line ending in '\n'. Remove '\n' and '\r' if found. Works (hopefully)
correctly, when multiple reads are necessary or one read returns more than
one line.
*****************************************************************************/
char *
io_readln(struct io_ctx *ioc)
{
unsigned int offset=0;
int n;
int sn;
char *pos;
fd_set readset;
DEBUG1(DG_IO, "io_readln", "called for desc='%s'", ioc->io_desc);
/* if there is something left from the last read, shift it to the beginning
* of the buffer */
if (ioc->io_ibuf != ioc->io_bufptr) {
DEBUG1(DG_IO, "io_readln", "something left from last time around ='%s'", ioc->io_bufptr);
strlcpy(ioc->io_ibuf, ioc->io_bufptr, sizeof(ioc->io_ibuf));
ioc->io_bufptr = ioc->io_ibuf;
}
/* if there is a complete line from the last read, return it */
pos = strchr(ioc->io_ibuf, '\n');
if (pos) goto complete_line;
offset = strlen(ioc->io_ibuf);
/* read until newline is found */
while (offset < sizeof(ioc->io_ibuf)) {
if (! io_pending(ioc)) {
FD_ZERO(&readset);
FD_SET(ioc->io_fd, &readset);
sn = rel_select(&readset, ioc->io_timeout, 0);
if (sn < 0) {
/* XLOG-DOC:ADM:0178:select_error
* The rel_select() call failed. */
xlog_printf(xlog_adm, 0x0178, "select_error errno=%d errmgs='%s'", errno, strerror(errno));
return NULL;
}
if (sn == 0) {
/* XLOG-DOC:ERR:004f:readln_timeout
* A timeout occurred while waiting for an input line from a socket.
* This probably means that the client went away. */
xlog_printf(xlog_err, 0x004f, "readln_timeout");
return NULL;
}
}
n = io_sysread(ioc, ioc->io_ibuf+offset, sizeof(ioc->io_ibuf) - offset);
if (n < 0) {
/* XLOG-DOC:ERR:0050:readln_error
* An error occurred while waiting for an input line from a socket.
* This probably means that the client went away. */
xlog_printf(xlog_err, 0x0050, "readln_error errno=%d errmsg='%s'", errno, strerror(errno));
return NULL;
} else if (n == 0) {
/* XLOG-DOC:ERR:0051:readln_eof
* An end-of-file occurred while waiting for an input line from a socket.
* This probably means that the client went away. */
xlog_printf(xlog_err, 0x0051, "readln_eof");
return NULL;
}
ioc->io_ibuf[offset+n] = '\0';
pos = strchr(ioc->io_ibuf, '\n');
if (pos) goto complete_line;
offset += n;
}
/* input too long */
ioc->io_ibuf[sizeof(ioc->io_ibuf)-1] = '\0';
/* XLOG-DOC:ERR:0052:readln_too_long
* A line was read that is too long. This might indicate an attack. The
* buffer is long enough for any reasonable POP3 command etc. */
xlog_printf(xlog_err, 0x0052, "readln_too_long line='%s'", ioc->io_ibuf);
ioc->io_ibuf[0] = '\0';
ioc->io_bufptr = ioc->io_ibuf;
return NULL;
complete_line:
*pos = '\0';
ioc->io_bufptr = pos+1;
if ((pos > ioc->io_ibuf) && (*--pos == '\r')) *pos = '\0';
DEBUG2(DG_IO, "io_readln", "got complete line ='%s', bufptr='%s'", ioc->io_ibuf, ioc->io_bufptr);
return ioc->io_ibuf;
}
/*****************************************************************************
io_syswrite()
*****************************************************************************/
ssize_t
io_syswrite(struct io_ctx *ioc, const char *buf, size_t count)
{
/* DEBUG2(DG_IO, "io_syswrite", "str='%s' len=%d", buf, count);*/
DEBUG3(DG_IO, "io_syswrite", "str='%.*s' len=%d", count, buf, count);
#ifdef USE_TLS
if (ioc->io_type == iot_tls) return SSL_write(ioc->io_tls, buf, count);
#endif
return rel_write(ioc->io_fd, buf, count);
}
/*****************************************************************************
io_buf_flush()
Flushes an output buffer of an IO context.
*****************************************************************************/
int
io_buf_flush(struct io_ctx *ioc)
{
int len = strlen(ioc->io_obuf);
DEBUG0(DG_IO, "io_buf_flush", "start");
if (len > 0) {
DEBUG1(DG_IO, "io_buf_flush", "flushing %d bytes", len);
if (io_syswrite(ioc, ioc->io_obuf, len) != len) return -1;
ioc->io_obuf[0] = '\0';
}
return 0;
}
/*****************************************************************************
io_write()
Writes a string to an IO context. No null byte are allowed in the string.
*****************************************************************************/
int
io_write(struct io_ctx *ioc, const char *string)
{
DEBUG1(DG_IO, "io_write", "start str='%s'", string);
if (io_buf_write(ioc, string) < 0) return -1;
return io_buf_flush(ioc);
}
/*****************************************************************************
io_writeln()
Writes a line to an IO context. Appends \r\n. No null bytes are allowed
in the string.
*****************************************************************************/
int
io_writeln(struct io_ctx *ioc, const char *string)
{
DEBUG1(DG_IO, "io_writeln", "start str='%s'", string);
if (io_buf_write(ioc, string) < 0) return -1;
return io_write(ioc, "\r\n");
}
/*****************************************************************************
io_buf_write()
*****************************************************************************/
int
io_buf_write(struct io_ctx *ioc, const char *string)
{
int offset=0;
DEBUG1(DG_IO, "io_buf_write", "start str='%s'", string);
/* if the new data doesn't fit in the output buffer, flush it */
if (strlen(ioc->io_obuf) + strlen(string) + 1 /* '\0' */ >= sizeof(ioc->io_obuf)) {
DEBUG1(DG_IO, "io_buf_write", "flushing rest in output buffer str='%s'", ioc->io_obuf);
if (io_buf_flush(ioc) < 0) return -1;
}
while (1) {
int len = strlcat(ioc->io_obuf, string+offset, sizeof(ioc->io_obuf));
if (len >= sizeof(ioc->io_obuf)) {
DEBUG1(DG_IO, "io_buf_write", "flushing part in output buffer str='%s'", ioc->io_obuf);
if (io_buf_flush(ioc) < 0) return -1;
offset += sizeof(ioc->io_obuf) - 1;
} else {
break;
}
}
return 0;
}
/*****************************************************************************
io_buf_writeln()
*****************************************************************************/
int
io_buf_writeln(struct io_ctx *ioc, const char *string)
{
DEBUG1(DG_IO, "io_buf_writeln", "start str='%s'", string);
if (io_buf_write(ioc, string) < 0) return -1;
return io_buf_write(ioc, "\r\n");
}
/*****************************************************************************
io_buf_printf()
*****************************************************************************/
int
io_buf_printf(struct io_ctx *ioc, const char *format, ...)
{
va_list args;
int len = strlen(ioc->io_obuf);
int r;
DEBUG1(DG_IO, "io_buf_printf", "start format='%s'", format);
va_start(args, format);
r = vsnprintf(ioc->io_obuf+len, sizeof(ioc->io_obuf)-len, format, args);
va_end(args);
if (r < 0 || r >= sizeof(ioc->io_obuf)-len) {
/* if the buffer overflowed, we flush it and try again */
DEBUG0(DG_IO, "io_buf_printf", "overflow. try flushing");
ioc->io_obuf[len] = '\0';
io_buf_flush(ioc);
va_start(args, format);
r = vsnprintf(ioc->io_obuf, sizeof(ioc->io_obuf), format, args);
va_end(args);
if (r < 0 || r >= sizeof(ioc->io_obuf)) {
DEBUG0(DG_IO, "io_buf_printf", "overflow. give up");
/* still overflowing. give up */
return -1;
}
}
return 0;
}
/** THE END *****************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1