blob: 72d5e3c89aca9a0916d364ba210237794fd0c533 [file] [log] [blame]
/*
* stunnel TLS offloading and load-balancing proxy
* Copyright (C) 1998-2015 Michal Trojnara <Michal.Trojnara@mirt.net>
*
* 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, see <http://www.gnu.org/licenses>.
*
* Linking stunnel statically or dynamically with other modules is making
* a combined work based on stunnel. Thus, the terms and conditions of
* the GNU General Public License cover the whole combination.
*
* In addition, as a special exception, the copyright holder of stunnel
* gives you permission to combine stunnel with free software programs or
* libraries that are released under the GNU LGPL and with code included
* in the standard release of OpenSSL under the OpenSSL License (or
* modified versions of such code, with unchanged license). You may copy
* and distribute such a system following the terms of the GNU GPL for
* stunnel and the licenses of the other code concerned.
*
* Note that people who make modified versions of stunnel are not obligated
* to grant this special exception for their modified versions; it is their
* choice whether to do so. The GNU General Public License gives permission
* to release a modified version without this exception; this exception
* also makes it possible to release a modified version which carries
* forward this exception.
*/
#if defined(_WIN32) || defined(_WIN32_WCE)
/* bypass automatic index bound checks in the FD_SET() macro */
#define FD_SETSIZE 1000000
#endif
#include "common.h"
#include "prototypes.h"
/* #define DEBUG_UCONTEXT */
NOEXPORT void s_poll_realloc(s_poll_set *);
NOEXPORT int get_socket_error(const SOCKET);
/**************************************** s_poll functions */
#ifdef USE_POLL
s_poll_set *s_poll_alloc() {
/* it needs to be filled with zeros */
return str_alloc(sizeof(s_poll_set));
}
void s_poll_free(s_poll_set *fds) {
if(fds) {
str_free(fds->ufds);
str_free(fds);
}
}
void s_poll_init(s_poll_set *fds) {
fds->nfds=0;
fds->allocated=4; /* prealloc 4 file desciptors */
s_poll_realloc(fds);
}
void s_poll_add(s_poll_set *fds, SOCKET fd, int rd, int wr) {
unsigned i;
for(i=0; i<fds->nfds && fds->ufds[i].fd!=fd; i++)
;
if(i==fds->nfds) { /* not found */
if(i==fds->allocated) {
fds->allocated=i+1;
s_poll_realloc(fds);
}
fds->ufds[i].fd=fd;
fds->ufds[i].events=0;
fds->nfds++;
}
if(rd) {
fds->ufds[i].events|=POLLIN;
#ifdef POLLRDHUP
fds->ufds[i].events|=POLLRDHUP;
#endif
}
if(wr)
fds->ufds[i].events|=POLLOUT;
}
void s_poll_remove(s_poll_set *fds, SOCKET fd) {
unsigned i;
for(i=0; i<fds->nfds && fds->ufds[i].fd!=fd; i++)
;
if(i<fds->nfds) { /* found */
memmove(fds->ufds+i, fds->ufds+i+1,
(fds->nfds-i-1)*sizeof(struct pollfd));
fds->nfds--;
}
}
int s_poll_canread(s_poll_set *fds, SOCKET fd) {
unsigned i;
for(i=0; i<fds->nfds; i++)
if(fds->ufds[i].fd==fd)
return fds->ufds[i].revents&(POLLIN|POLLERR);
return 0; /* not listed in fds */
}
int s_poll_canwrite(s_poll_set *fds, SOCKET fd) {
unsigned i;
for(i=0; i<fds->nfds; i++)
if(fds->ufds[i].fd==fd)
return fds->ufds[i].revents&(POLLOUT|POLLERR);
return 0; /* not listed in fds */
}
/* best doc: http://lxr.free-electrons.com/source/net/ipv4/tcp.c#L456 */
int s_poll_hup(s_poll_set *fds, SOCKET fd) {
unsigned i;
for(i=0; i<fds->nfds; i++)
if(fds->ufds[i].fd==fd)
return fds->ufds[i].revents&POLLHUP; /* read and write closed */
return 0; /* not listed in fds */
}
int s_poll_rdhup(s_poll_set *fds, SOCKET fd) {
unsigned i;
for(i=0; i<fds->nfds; i++)
if(fds->ufds[i].fd==fd)
#ifdef POLLRDHUP
return fds->ufds[i].revents&POLLRDHUP; /* read closed */
#else
return fds->ufds[i].revents&POLLHUP; /* read and write closed */
#endif
return 0; /* not listed in fds */
}
NOEXPORT void s_poll_realloc(s_poll_set *fds) {
fds->ufds=str_realloc(fds->ufds, fds->allocated*sizeof(struct pollfd));
}
void s_poll_dump(s_poll_set *fds, int level) {
unsigned i;
for(i=0; i<fds->nfds; i++)
s_log(level, "fd=%d events=0x%X revents=0x%X",
fds->ufds[i].fd, fds->ufds[i].events, fds->ufds[i].revents);
}
#ifdef USE_UCONTEXT
/* move ready contexts from waiting queue to ready queue */
NOEXPORT void scan_waiting_queue(void) {
int retval;
CONTEXT *context, *prev;
int min_timeout;
unsigned nfds, i;
time_t now;
static unsigned max_nfds=0;
static struct pollfd *ufds=NULL;
time(&now);
/* count file descriptors */
min_timeout=-1; /* infinity */
nfds=0;
for(context=waiting_head; context; context=context->next) {
nfds+=context->fds->nfds;
if(context->finish>=0) /* finite time */
if(min_timeout<0 || min_timeout>context->finish-now)
min_timeout=
(int)(context->finish-now<0 ? 0 : context->finish-now);
}
/* setup ufds structure */
if(nfds>max_nfds) { /* need to allocate more memory */
ufds=str_realloc(ufds, nfds*sizeof(struct pollfd));
max_nfds=nfds;
}
nfds=0;
for(context=waiting_head; context; context=context->next)
for(i=0; i<context->fds->nfds; i++) {
ufds[nfds].fd=context->fds->ufds[i].fd;
ufds[nfds].events=context->fds->ufds[i].events;
nfds++;
}
#ifdef DEBUG_UCONTEXT
s_log(LOG_DEBUG, "Waiting %d second(s) for %d file descriptor(s)",
min_timeout, nfds);
#endif
do { /* skip "Interrupted system call" errors */
retval=poll(ufds, nfds, min_timeout<0 ? -1 : 1000*min_timeout);
} while(retval<0 && get_last_socket_error()==S_EINTR);
time(&now);
/* process the returned data */
nfds=0;
prev=NULL; /* previous element of the waiting queue */
context=waiting_head;
while(context) {
context->ready=0;
/* count ready file descriptors in each context */
for(i=0; i<context->fds->nfds; i++) {
context->fds->ufds[i].revents=ufds[nfds].revents;
#ifdef DEBUG_UCONTEXT
s_log(LOG_DEBUG, "CONTEXT %ld, FD=%d,%s%s ->%s%s%s%s%s",
context->id, ufds[nfds].fd,
ufds[nfds].events & POLLIN ? " IN" : "",
ufds[nfds].events & POLLOUT ? " OUT" : "",
ufds[nfds].revents & POLLIN ? " IN" : "",
ufds[nfds].revents & POLLOUT ? " OUT" : "",
ufds[nfds].revents & POLLERR ? " ERR" : "",
ufds[nfds].revents & POLLHUP ? " HUP" : "",
ufds[nfds].revents & POLLNVAL ? " NVAL" : "");
#endif
if(ufds[nfds].revents)
context->ready++;
nfds++;
}
if(context->ready || (context->finish>=0 && context->finish<=now)) {
/* remove context from the waiting queue */
if(prev)
prev->next=context->next;
else
waiting_head=context->next;
if(!context->next) /* same as context==waiting_tail */
waiting_tail=prev;
/* append context context to the ready queue */
context->next=NULL;
if(ready_tail)
ready_tail->next=context;
ready_tail=context;
if(!ready_head)
ready_head=context;
} else { /* leave the context context in the waiting queue */
prev=context;
}
context=prev ? prev->next : waiting_head;
}
}
int s_poll_wait(s_poll_set *fds, int sec, int msec) {
CONTEXT *context; /* current context */
static CONTEXT *to_free=NULL; /* delayed memory deallocation */
/* FIXME: msec parameter is currently ignored with UCONTEXT threads */
(void)msec; /* skip warning about unused parameter */
/* remove the current context from ready queue */
context=ready_head;
ready_head=ready_head->next;
if(!ready_head) /* the queue is empty */
ready_tail=NULL;
/* it is safe to s_log() after new ready_head is set */
/* it is illegal to deallocate the stack of the current context */
if(to_free) { /* a delayed deallocation is scheduled */
#ifdef DEBUG_UCONTEXT
s_log(LOG_DEBUG, "Releasing context %ld", to_free->id);
#endif
str_free(to_free->stack);
str_free(to_free);
to_free=NULL;
}
/* manage the current thread */
if(fds) { /* something to wait for -> swap the context */
context->fds=fds; /* set file descriptors to wait for */
context->finish=sec<0 ? -1 : time(NULL)+sec;
/* append the current context to the waiting queue */
context->next=NULL;
if(waiting_tail)
waiting_tail->next=context;
waiting_tail=context;
if(!waiting_head)
waiting_head=context;
} else { /* nothing to wait for -> drop the context */
to_free=context; /* schedule for delayed deallocation */
}
while(!ready_head) /* wait until there is a thread to switch to */
scan_waiting_queue();
/* switch threads */
if(fds) { /* swap the current context */
if(context->id!=ready_head->id) {
#ifdef DEBUG_UCONTEXT
s_log(LOG_DEBUG, "Context swap: %ld -> %ld",
context->id, ready_head->id);
#endif
swapcontext(&context->context, &ready_head->context);
#ifdef DEBUG_UCONTEXT
s_log(LOG_DEBUG, "Current context: %ld", ready_head->id);
#endif
}
return ready_head->ready;
} else { /* drop the current context */
#ifdef DEBUG_UCONTEXT
s_log(LOG_DEBUG, "Context set: %ld (dropped) -> %ld",
context->id, ready_head->id);
#endif
setcontext(&ready_head->context);
ioerror("setcontext"); /* should not ever happen */
return 0;
}
}
#else /* USE_UCONTEXT */
int s_poll_wait(s_poll_set *fds, int sec, int msec) {
int retval;
do { /* skip "Interrupted system call" errors */
retval=poll(fds->ufds, fds->nfds, sec<0 ? -1 : 1000*sec+msec);
} while(retval<0 && get_last_socket_error()==S_EINTR);
return retval;
}
#endif /* USE_UCONTEXT */
#else /* select */
s_poll_set *s_poll_alloc() {
/* it needs to be filled with zeros */
return str_alloc(sizeof(s_poll_set));
}
void s_poll_free(s_poll_set *fds) {
if(fds) {
str_free(fds->irfds);
str_free(fds->iwfds);
str_free(fds->ixfds);
str_free(fds->orfds);
str_free(fds->owfds);
str_free(fds->oxfds);
str_free(fds);
}
}
void s_poll_init(s_poll_set *fds) {
#ifdef USE_WIN32
fds->allocated=4; /* prealloc 4 file desciptors */
#endif
s_poll_realloc(fds);
FD_ZERO(fds->irfds);
FD_ZERO(fds->iwfds);
FD_ZERO(fds->ixfds);
fds->max=0; /* no file descriptors */
}
void s_poll_add(s_poll_set *fds, SOCKET fd, int rd, int wr) {
#ifdef USE_WIN32
/* fds->ixfds contains union of fds->irfds and fds->iwfds */
if(fds->ixfds->fd_count>=fds->allocated) {
fds->allocated=fds->ixfds->fd_count+1;
s_poll_realloc(fds);
}
#endif
if(rd)
FD_SET(fd, fds->irfds);
if(wr)
FD_SET(fd, fds->iwfds);
/* always expect errors (and the Spanish Inquisition) */
FD_SET(fd, fds->ixfds);
if(fd>fds->max)
fds->max=fd;
}
void s_poll_remove(s_poll_set *fds, SOCKET fd) {
FD_CLR(fd, fds->irfds);
FD_CLR(fd, fds->iwfds);
FD_CLR(fd, fds->ixfds);
}
int s_poll_canread(s_poll_set *fds, SOCKET fd) {
return FD_ISSET(fd, fds->orfds) || FD_ISSET(fd, fds->oxfds);
}
int s_poll_canwrite(s_poll_set *fds, SOCKET fd) {
return FD_ISSET(fd, fds->owfds) || FD_ISSET(fd, fds->oxfds);
}
int s_poll_hup(s_poll_set *fds, SOCKET fd) {
(void)fds; /* skip warning about unused parameter */
(void)fd; /* skip warning about unused parameter */
return 0; /* FIXME: how to detect HUP condition with select()? */
}
int s_poll_rdhup(s_poll_set *fds, SOCKET fd) {
(void)fds; /* skip warning about unused parameter */
(void)fd; /* skip warning about unused parameter */
return 0; /* FIXME: how to detect RDHUP condition with select()? */
}
#ifdef USE_WIN32
#define FD_SIZE(fds) (sizeof(u_int)+(fds)->allocated*sizeof(SOCKET))
#else
#define FD_SIZE(fds) (sizeof(fd_set))
#endif
int s_poll_wait(s_poll_set *fds, int sec, int msec) {
int retval;
struct timeval tv, *tv_ptr;
do { /* skip "Interrupted system call" errors */
memcpy(fds->orfds, fds->irfds, FD_SIZE(fds));
memcpy(fds->owfds, fds->iwfds, FD_SIZE(fds));
memcpy(fds->oxfds, fds->ixfds, FD_SIZE(fds));
if(sec<0) { /* infinite timeout */
tv_ptr=NULL;
} else {
tv.tv_sec=sec;
tv.tv_usec=1000*msec;
tv_ptr=&tv;
}
retval=select((int)fds->max+1,
fds->orfds, fds->owfds, fds->oxfds, tv_ptr);
} while(retval<0 && get_last_socket_error()==S_EINTR);
return retval;
}
NOEXPORT void s_poll_realloc(s_poll_set *fds) {
fds->irfds=str_realloc(fds->irfds, FD_SIZE(fds));
fds->iwfds=str_realloc(fds->iwfds, FD_SIZE(fds));
fds->ixfds=str_realloc(fds->ixfds, FD_SIZE(fds));
fds->orfds=str_realloc(fds->orfds, FD_SIZE(fds));
fds->owfds=str_realloc(fds->owfds, FD_SIZE(fds));
fds->oxfds=str_realloc(fds->oxfds, FD_SIZE(fds));
}
void s_poll_dump(s_poll_set *fds, int level) {
SOCKET fd;
int ir, iw, ix, or, ow, ox;
for(fd=0; fd<fds->max; fd++) {
ir=FD_ISSET(fd, fds->irfds);
iw=FD_ISSET(fd, fds->iwfds);
ix=FD_ISSET(fd, fds->ixfds);
or=FD_ISSET(fd, fds->orfds);
ow=FD_ISSET(fd, fds->owfds);
ox=FD_ISSET(fd, fds->oxfds);
if(ir || iw || ix || or || ow || ox)
s_log(level, "fd=%d ifds=%c%c%c ofds=%c%c%c", fd,
ir?'r':'-', iw?'w':'-', ix?'x':'-',
or?'r':'-', ow?'w':'-', ox?'x':'-');
}
}
#endif /* USE_POLL */
/**************************************** fd management */
int set_socket_options(SOCKET s, int type) {
SOCK_OPT *ptr;
extern SOCK_OPT sock_opts[];
static char *type_str[3]={"accept", "local", "remote"};
socklen_t opt_size;
int retval=0; /* no error found */
for(ptr=sock_opts; ptr->opt_str; ptr++) {
if(!ptr->opt_val[type])
continue; /* default */
switch(ptr->opt_type) {
case TYPE_LINGER:
opt_size=sizeof(struct linger);
break;
case TYPE_TIMEVAL:
opt_size=sizeof(struct timeval);
break;
case TYPE_STRING:
opt_size=(socklen_t)strlen(ptr->opt_val[type]->c_val)+1;
break;
default:
opt_size=sizeof(int);
}
if(setsockopt(s, ptr->opt_level, ptr->opt_name,
(void *)ptr->opt_val[type], opt_size)) {
if(get_last_socket_error()==S_EOPNOTSUPP) {
/* most likely stdin/stdout or AF_UNIX socket */
s_log(LOG_DEBUG,
"Option %s not supported on %s socket",
ptr->opt_str, type_str[type]);
} else {
sockerror(ptr->opt_str);
retval=-1; /* failed to set this option */
}
}
#ifdef DEBUG_FD_ALLOC
else {
s_log(LOG_DEBUG, "Option %s set on %s socket",
ptr->opt_str, type_str[type]);
}
#endif /* DEBUG_FD_ALLOC */
}
return retval; /* returns 0 when all options succeeded */
}
NOEXPORT int get_socket_error(const SOCKET fd) {
int err;
socklen_t optlen=sizeof err;
if(getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&err, &optlen))
err=get_last_socket_error(); /* failed -> ask why */
return err==S_ENOTSOCK ? 0 : err;
}
/**************************************** simulate blocking I/O */
int s_connect(CLI *c, SOCKADDR_UNION *addr, socklen_t addrlen) {
int error;
char *dst;
dst=s_ntop(addr, addrlen);
s_log(LOG_INFO, "s_connect: connecting %s", dst);
if(!connect(c->fd, &addr->sa, addrlen)) {
s_log(LOG_INFO, "s_connect: connected %s", dst);
str_free(dst);
return 0; /* no error -> success (on some OSes over the loopback) */
}
error=get_last_socket_error();
if(error!=S_EINPROGRESS && error!=S_EWOULDBLOCK) {
s_log(LOG_ERR, "s_connect: connect %s: %s (%d)",
dst, s_strerror(error), error);
str_free(dst);
return -1;
}
s_log(LOG_DEBUG, "s_connect: s_poll_wait %s: waiting %d seconds",
dst, c->opt->timeout_connect);
s_poll_init(c->fds);
s_poll_add(c->fds, c->fd, 1, 1);
switch(s_poll_wait(c->fds, c->opt->timeout_connect, 0)) {
case -1:
error=get_last_socket_error();
s_log(LOG_ERR, "s_connect: s_poll_wait %s: %s (%d)",
dst, s_strerror(error), error);
str_free(dst);
return -1;
case 0:
s_log(LOG_ERR, "s_connect: s_poll_wait %s:"
" TIMEOUTconnect exceeded", dst);
str_free(dst);
return -1;
default:
error=get_socket_error(c->fd);
if(error) {
s_log(LOG_ERR, "s_connect: connect %s: %s (%d)",
dst, s_strerror(error), error);
str_free(dst);
return -1;
}
if(s_poll_canwrite(c->fds, c->fd)) {
s_log(LOG_NOTICE, "s_connect: connected %s", dst);
str_free(dst);
return 0; /* success */
}
s_log(LOG_ERR, "s_connect: s_poll_wait %s: internal error",
dst);
str_free(dst);
return -1;
}
return -1; /* should not be possible */
}
void s_write(CLI *c, SOCKET fd, const void *buf, size_t len) {
/* simulate a blocking write */
uint8_t *ptr=(uint8_t *)buf;
ssize_t num;
while(len>0) {
s_poll_init(c->fds);
s_poll_add(c->fds, fd, 0, 1); /* write */
switch(s_poll_wait(c->fds, c->opt->timeout_busy, 0)) {
case -1:
sockerror("s_write: s_poll_wait");
longjmp(c->err, 1); /* error */
case 0:
s_log(LOG_INFO, "s_write: s_poll_wait:"
" TIMEOUTbusy exceeded: sending reset");
longjmp(c->err, 1); /* timeout */
case 1:
break; /* OK */
default:
s_log(LOG_ERR, "s_write: s_poll_wait: unknown result");
longjmp(c->err, 1); /* error */
}
num=writesocket(fd, (void *)ptr, len);
if(num==-1) { /* error */
sockerror("writesocket (s_write)");
longjmp(c->err, 1);
}
ptr+=(size_t)num;
len-=(size_t)num;
}
}
void s_read(CLI *c, SOCKET fd, void *ptr, size_t len) {
/* simulate a blocking read */
ssize_t num;
while(len>0) {
s_poll_init(c->fds);
s_poll_add(c->fds, fd, 1, 0); /* read */
switch(s_poll_wait(c->fds, c->opt->timeout_busy, 0)) {
case -1:
sockerror("s_read: s_poll_wait");
longjmp(c->err, 1); /* error */
case 0:
s_log(LOG_INFO, "s_read: s_poll_wait:"
" TIMEOUTbusy exceeded: sending reset");
longjmp(c->err, 1); /* timeout */
case 1:
break; /* OK */
default:
s_log(LOG_ERR, "s_read: s_poll_wait: unknown result");
longjmp(c->err, 1); /* error */
}
num=readsocket(fd, ptr, len);
switch(num) {
case -1: /* error */
sockerror("readsocket (s_read)");
longjmp(c->err, 1);
case 0: /* EOF */
s_log(LOG_ERR, "Unexpected socket close (s_read)");
longjmp(c->err, 1);
}
ptr=(uint8_t *)ptr+num;
len-=(size_t)num;
}
}
void fd_putline(CLI *c, SOCKET fd, const char *line) {
char *tmpline;
const char crlf[]="\r\n";
size_t len;
tmpline=str_printf("%s%s", line, crlf);
len=strlen(tmpline);
s_write(c, fd, tmpline, len);
tmpline[len-2]='\0'; /* remove CRLF */
s_log(LOG_DEBUG, " -> %s", tmpline);
str_free(tmpline);
}
char *fd_getline(CLI *c, SOCKET fd) {
char *line;
size_t ptr=0, allocated=32;
line=str_alloc(allocated);
for(;;) {
if(ptr>65536) { /* >64KB --> DoS protection */
s_log(LOG_ERR, "fd_getline: Line too long");
str_free(line);
longjmp(c->err, 1);
}
if(allocated<ptr+1) {
allocated*=2;
line=str_realloc(line, allocated);
}
s_read(c, fd, line+ptr, 1);
if(line[ptr]=='\r')
continue;
if(line[ptr]=='\n')
break;
if(line[ptr]=='\0')
break;
++ptr;
}
line[ptr]='\0';
s_log(LOG_DEBUG, " <- %s", line);
return line;
}
void fd_printf(CLI *c, SOCKET fd, const char *format, ...) {
va_list ap;
char *line;
va_start(ap, format);
line=str_vprintf(format, ap);
va_end(ap);
if(!line) {
s_log(LOG_ERR, "fd_printf: str_vprintf failed");
longjmp(c->err, 1);
}
fd_putline(c, fd, line);
str_free(line);
}
void s_ssl_write(CLI *c, const void *buf, int len) {
/* simulate a blocking SSL_write */
uint8_t *ptr=(uint8_t *)buf;
int num;
while(len>0) {
s_poll_init(c->fds);
s_poll_add(c->fds, c->ssl_wfd->fd, 0, 1); /* write */
switch(s_poll_wait(c->fds, c->opt->timeout_busy, 0)) {
case -1:
sockerror("s_write: s_poll_wait");
longjmp(c->err, 1); /* error */
case 0:
s_log(LOG_INFO, "s_write: s_poll_wait:"
" TIMEOUTbusy exceeded: sending reset");
longjmp(c->err, 1); /* timeout */
case 1:
break; /* OK */
default:
s_log(LOG_ERR, "s_write: s_poll_wait: unknown result");
longjmp(c->err, 1); /* error */
}
num=SSL_write(c->ssl, (void *)ptr, len);
if(num==-1) { /* error */
sockerror("SSL_write (s_ssl_write)");
longjmp(c->err, 1);
}
ptr+=num;
len-=num;
}
}
void s_ssl_read(CLI *c, void *ptr, int len) {
/* simulate a blocking SSL_read */
int num;
while(len>0) {
if(!SSL_pending(c->ssl)) {
s_poll_init(c->fds);
s_poll_add(c->fds, c->ssl_rfd->fd, 1, 0); /* read */
switch(s_poll_wait(c->fds, c->opt->timeout_busy, 0)) {
case -1:
sockerror("s_read: s_poll_wait");
longjmp(c->err, 1); /* error */
case 0:
s_log(LOG_INFO, "s_read: s_poll_wait:"
" TIMEOUTbusy exceeded: sending reset");
longjmp(c->err, 1); /* timeout */
case 1:
break; /* OK */
default:
s_log(LOG_ERR, "s_read: s_poll_wait: unknown result");
longjmp(c->err, 1); /* error */
}
}
num=SSL_read(c->ssl, ptr, len);
switch(num) {
case -1: /* error */
sockerror("SSL_read (s_ssl_read)");
longjmp(c->err, 1);
case 0: /* EOF */
s_log(LOG_ERR, "Unexpected socket close (s_ssl_read)");
longjmp(c->err, 1);
}
ptr=(uint8_t *)ptr+num;
len-=num;
}
}
char *ssl_getstring(CLI *c) { /* get null-terminated string */
char *line;
size_t ptr=0, allocated=32;
line=str_alloc(allocated);
for(;;) {
if(ptr>65536) { /* >64KB --> DoS protection */
s_log(LOG_ERR, "fd_getline: Line too long");
str_free(line);
longjmp(c->err, 1);
}
if(allocated<ptr+1) {
allocated*=2;
line=str_realloc(line, allocated);
}
s_ssl_read(c, line+ptr, 1);
if(line[ptr]=='\0')
break;
++ptr;
}
return line;
}
#define INET_SOCKET_PAIR
int make_sockets(SOCKET fd[2]) { /* make a pair of connected ipv4 sockets */
#ifdef INET_SOCKET_PAIR
struct sockaddr_in addr;
socklen_t addrlen;
SOCKET s; /* temporary socket awaiting for connection */
/* create two *blocking* sockets first */
s=s_socket(AF_INET, SOCK_STREAM, 0, 0, "make_sockets: s_socket#1");
if(s==INVALID_SOCKET)
return 1;
fd[1]=s_socket(AF_INET, SOCK_STREAM, 0, 0, "make_sockets: s_socket#2");
if(fd[1]==INVALID_SOCKET) {
closesocket(s);
return 1;
}
addrlen=sizeof addr;
memset(&addr, 0, sizeof addr);
addr.sin_family=AF_INET;
addr.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
addr.sin_port=htons(0); /* dynamic port allocation */
if(bind(s, (struct sockaddr *)&addr, addrlen))
log_error(LOG_DEBUG, get_last_socket_error(), "make_sockets: bind#1");
if(bind(fd[1], (struct sockaddr *)&addr, addrlen))
log_error(LOG_DEBUG, get_last_socket_error(), "make_sockets: bind#2");
if(listen(s, 1)) {
sockerror("make_sockets: listen");
closesocket(s);
closesocket(fd[1]);
return 1;
}
if(getsockname(s, (struct sockaddr *)&addr, &addrlen)) {
sockerror("make_sockets: getsockname");
closesocket(s);
closesocket(fd[1]);
return 1;
}
if(connect(fd[1], (struct sockaddr *)&addr, addrlen)) {
sockerror("make_sockets: connect");
closesocket(s);
closesocket(fd[1]);
return 1;
}
fd[0]=s_accept(s, (struct sockaddr *)&addr, &addrlen, 1,
"make_sockets: s_accept");
if(fd[0]==INVALID_SOCKET) {
closesocket(s);
closesocket(fd[1]);
return 1;
}
closesocket(s); /* don't care about the result */
set_nonblock(fd[0], 1);
set_nonblock(fd[1], 1);
#else
if(s_socketpair(AF_UNIX, SOCK_STREAM, 0, fd, 1, "make_sockets: socketpair"))
return 1;
#endif
return 0;
}
/* end of network.c */