/*
* stone.c simple repeater
* Copyright(c)1995-2007 by Hiroaki Sengoku <sengoku@gcd.org>
* Version 1.0 Jan 28, 1995
* Version 1.1 Jun 7, 1995
* Version 1.2 Aug 20, 1995
* Version 1.3 Feb 16, 1996 relay UDP
* Version 1.5 Nov 15, 1996 for Win32
* Version 1.6 Jul 5, 1997 for SSL
* Version 1.7 Aug 20, 1997 return packet of UDP
* Version 1.8 Oct 18, 1997 pseudo parallel using SIGALRM
* Version 2.0 Nov 3, 1997 http proxy & over http
* Version 2.1 Nov 14, 1998 respawn & pop
* Version 2.2 May 25, 2003 Posix Thread, XferBufMax, no ALRM, SSL verify
* Version 2.3 Jan 1, 2006 LB, healthCheck, NonBlock, IPv6, sockaddr_un
*
* 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, 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 GNU Emacs; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
* Usage: stone [-d] [-n] [-u <max>] [-f <n>] [-a <file>] [-L <file>] [-l]
* [-o <n>] [-g <n>] [-t <dir>] [-z <SSL>] [-D]
* [-C <file>] [-P <command>]
* <st> [-- <st>]...
* <st> := <host>:<port> <sport> [<xhost>...]
* | proxy <sport> [<xhost>...]
* | <host>:<port#>/http <sport> <request> [<xhost>...]
* | <host>:<port#>/proxy <sport> <header> [<xhost>...]
* <port> := <port#>[/udp | /ssl | /apop]
* <sport> := [<host>:]<port#>[/udp | /ssl | /http]
* <xhost> := <host>[/<mask>]
*
* Any packets received by <sport> are passed to <host>:<port>
* as long as these packets are sent from <xhost>...
* if <xhost> are not given, any hosts are welcome.
*
* Make:
* gcc -o stone stone.c
* or
* cl -DWINDOWS stone.c /MT wsock32.lib
* or
* gcc -DWINDOWS -o stone.exe stone.c -lwsock32
*
* POP -> APOP conversion
* gcc -DUSE_POP -o stone stone.c md5c.c
* or
* cl -DWINDOWS -DUSE_POP stone.c md5c.c /MT wsock32.lib
* or
* gcc -DWINDOWS -DUSE_POP -o stone.exe stone.c md5c.c -lwsock32
*
* md5c.c global.h md5.h are contained in RFC1321
*
* Using OpenSSL
* gcc -DUSE_SSL -I/usr/local/ssl/include -o stone stone.c \
* -L/usr/local/ssl/lib -lssl -lcrypto
* or
* cl -DWINDOWS -DUSE_SSL stone.c /MT wsock32.lib ssleay32.lib libeay32.lib
* or
* gcc -DWINDOWS -DUSE_SSL -o stone.exe stone.c -lwsock32 -lssl32 -leay32
*
* -DUSE_POP use POP -> APOP conversion
* -DUSE_SSL use OpenSSL
* -DCPP preprocessor for reading config. file
* -DIGN_SIGTERM ignore SIGTERM signal
* -DUNIX_DAEMON fork into background and become a UNIX Daemon
* -DNO_BCOPY without bcopy(3)
* -DNO_SNPRINTF without snprintf(3)
* -DNO_SYSLOG without syslog(2)
* -DNO_RINDEX without rindex(3)
* -DNO_STRDUP without strdup(3)
* -DNO_THREAD without thread
* -DNO_PID_T without pid_t
* -DNO_SOCKLEN_T without socklen_t
* -DNO_ADDRINFO without getaddrinfo
* -DNO_FAMILY_T without sa_family_t
* -DADDRCACHE cache address used in proxy
* -DUSE_EPOLL use epoll(4) (Linux)
* -DPTHREAD use Posix Thread
* -DPRCTL use prctl(2) - operations on a process
* -DOS2 OS/2 with EMX
* -DWINDOWS Windows95/98/NT
* -DNT_SERVICE WindowsNT/2000 native service
*/
#define VERSION "2.3d"
static char *CVS_ID =
"@(#) $Id: stone.c,v 2.3.2.2 2007/10/22 03:07:17 hiroaki_sengoku Exp $";
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <ctype.h>
#include <stdarg.h>
#include <signal.h>
#ifdef USE_PCRE
#include <pcreposix.h>
#else
#include <regex.h>
#endif
typedef void (*FuncPtr)(void*);
#ifdef WINDOWS
#define FD_SETSIZE 4096
#include <process.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#if !defined(EINPROGRESS) && defined(WSAEWOULDBLOCK)
#define EINPROGRESS WSAEWOULDBLOCK
#endif
#if !defined(EMSGSIZE) && defined(WSAEMSGSIZE)
#define EMSGSIZE WSAEMSGSIZE
#endif
#if !defined(EADDRINUSE) && defined(WSAEADDRINUSE)
#define EADDRINUSE WSAEADDRINUSE
#endif
#if !defined(ECONNABORTED) && defined(WSAECONNABORTED)
#define ECONNABORTED WSAECONNABORTED
#endif
#if !defined(ECONNRESET) && defined(WSAECONNRESET)
#define ECONNRESET WSAECONNRESET
#endif
#if !defined(EISCONN) && defined(WSAEISCONN)
#define EISCONN WSAEISCONN
#endif
#include <time.h>
#ifdef NT_SERVICE
#include <windows.h>
#include "logmsg.h"
#endif
#define NO_SYSLOG
#define NO_FORK
#define NO_SETUID
#define NO_CHROOT
#define NO_GETTIMEOFDAY
#define NO_FAMILY_T
#define NO_UNIXDOMAIN
#define ValidSocket(sd) ((sd) != INVALID_SOCKET)
#define FD_SET_BUG
#undef EINTR
#define EINTR WSAEINTR
#define NO_BCOPY
#define bzero(b,n) memset(b,0,n)
#define usleep(usec) Sleep(usec)
#define ASYNC(func,arg) {\
if (Debug > 7) message(LOG_DEBUG, "ASYNC: %d", AsyncCount);\
waitMutex(AsyncMutex);\
AsyncCount++;\
freeMutex(AsyncMutex);\
if (_beginthread((FuncPtr)func, 0, arg) < 0) {\
message(LOG_ERR, "_beginthread error err=%d", errno);\
func(arg);\
}\
}
#else /* ! WINDOWS */
#include <sys/param.h>
#ifdef OS2
#define INCL_DOSSEMAPHORES
#define INCL_DOSERRORS
#include <process.h>
#include <os2.h>
#define NO_SYSLOG
#define NO_UNIXDOMAIN
#define ASYNC(func,arg) {\
if (Debug > 7) message(LOG_DEBUG,"ASYNC: %d",AsyncCount);\
waitMutex(AsyncMutex);\
AsyncCount++;\
freeMutex(AsyncMutex);\
if (_beginthread((FuncPtr)func,NULL,32768,arg) < 0) {\
message(LOG_ERR,"_beginthread error err=%d",errno);\
func(arg);\
}\
}
#else /* ! WINDOWS & ! OS2 */
#ifdef PTHREAD
#include <pthread.h>
pthread_attr_t thread_attr;
typedef void *(*aync_start_routine) (void *);
#define ASYNC(func,arg) {\
pthread_t thread;\
int err;\
if (Debug > 7) message(LOG_DEBUG,"ASYNC: %d",AsyncCount);\
waitMutex(AsyncMutex);\
AsyncCount++;\
freeMutex(AsyncMutex);\
err=pthread_create(&thread,&thread_attr,(aync_start_routine)func,arg);\
if (err) {\
message(LOG_ERR,"pthread_create error err=%d",err);\
func(arg);\
} else if (Debug > 7) {\
message(LOG_DEBUG,"pthread ID=%lu",thread);\
}\
}
#else /* ! PTHREAD */
#define ASYNC(func,arg) {\
waitMutex(AsyncMutex);\
AsyncCount++;\
freeMutex(AsyncMutex);\
func(arg);\
}
#define NO_THREAD
#endif /* ! PTHREAD */
#endif /* ! WINDOWS & ! OS2 */
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#ifndef NO_SETUID
#include <grp.h>
#endif
#ifdef PRCTL
#include <sys/prctl.h>
#endif
#ifdef MEMLEAK_CHECK
#include <mcheck.h>
#endif
typedef int SOCKET;
#define INVALID_SOCKET -1
#define ValidSocket(sd) ((sd) >= 0)
#define closesocket(sd) close(sd)
#endif /* ! WINDOWS */
#define InvalidSocket(sd) (!ValidSocket(sd))
#ifdef USE_EPOLL
#include <sys/epoll.h>
#define EVSMAX 100
#else
#ifdef FD_SET_BUG
int FdSetBug = 0;
#define FdSet(fd,set) do{if (!FdSetBug || !FD_ISSET((fd),(set))) \
FD_SET((fd),(set));}while(0)
#else
#define FdSet(fd,set) FD_SET((fd),(set))
#endif
#endif
#ifdef NO_THREAD
#define ASYNC_BEGIN /* */
#define _ASYNC_END /* */
#else
#define ASYNC_BEGIN \
if (Debug > 7) message(LOG_DEBUG,"ASYNC_BEGIN: %d",AsyncCount)
#define _ASYNC_END \
if (Debug > 7) message(LOG_DEBUG,"ASYNC_END: %d",AsyncCount);\
waitMutex(AsyncMutex);\
AsyncCount--;\
freeMutex(AsyncMutex)
#endif
#ifdef USE_SSL
#define ASYNC_END \
_ASYNC_END;\
ERR_remove_state(0)
#else
#define ASYNC_END _ASYNC_END
#endif
#ifdef NO_SYSLOG
#define LOG_CRIT 2 /* critical conditions */
#define LOG_ERR 3 /* error conditions */
#define LOG_WARNING 4 /* warning conditions */
#define LOG_NOTICE 5 /* normal but signification condition */
#define LOG_INFO 6 /* informational */
#define LOG_DEBUG 7 /* debug-level messages */
#else /* SYSLOG */
#include <syslog.h>
#endif
#define BACKLOG_MAX 50
#define XPORT 6000
#define BUFMAX 2048
#define LONGSTRMAX 1024
#define STRMAX 127 /* > 38 */
#define CONN_TIMEOUT 60 /* 1 min */
#define LB_MAX 100
#define FREE_TIMEOUT 600 /* 10 min */
#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 255
#endif
#define TICK_SELECT 100000 /* 0.1 sec */
#define SPIN_MAX 10 /* 1 sec */
#define NERRS_MAX 10 /* # of select errors */
#define REF_UNIT 10 /* unit of pair->count */
#ifdef USE_SSL
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/ssl.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/pkcs12.h>
#include <openssl/rand.h>
#ifdef CRYPTOAPI
int SSL_CTX_use_CryptoAPI_certificate(SSL_CTX *ssl_ctx, const char *cert_prop);
int CryptoAPI_verify_certificate(X509 *x509);
#endif
#define NMATCH_MAX 9 /* \1 ... \9 */
#define DEPTH_MAX 10
#ifndef TLSEXT_NAMETYPE_host_name
#define OPENSSL_NO_TLSEXT
#endif
typedef struct {
int verbose;
int shutdown_mode;
int depth;
long serial;
SSL_CTX *ctx;
regex_t *re[DEPTH_MAX];
char *name;
unsigned char lbmod;
unsigned char lbparm;
unsigned char sslparm;
} StoneSSL;
const int sslparm_ignore = 0x01;
const int sslparm_storeca = 0x02;
const int sslparm_sni = 0x04;
typedef struct {
int verbose;
int shutdown_mode;
int mode;
int depth;
int vflags;
long off;
long serial;
const SSL_METHOD *meth;
int (*callback)(int, X509_STORE_CTX *);
unsigned char *sid_ctx;
int useSNI;
char *keyFile;
char *certFile;
char *keyFilePat;
char *certFilePat;
char *caFile;
char *caPath;
char *pfxFile;
char *pfxFilePat;
char *passFile;
char *passFilePat;
char *passwd;
char *servername;
int certIgnore;
#ifdef CRYPTOAPI
int certStoreCA;
char *certStore;
#endif
char *cipherList;
char *regexp[DEPTH_MAX];
unsigned char lbmod;
unsigned char lbparm;
} SSLOpts;
SSLOpts ServerOpts;
SSLOpts ClientOpts;
int PairIndex;
int MatchIndex;
int NewMatchCount = 0;
#ifdef WINDOWS
HANDLE *SSLMutex = NULL;
#else
#ifdef PTHREAD
pthread_mutex_t *SSLMutex = NULL;
#endif
#endif
int NSSLMutexs = 0;
#include <openssl/md5.h>
#define MD5Init MD5_Init
#define MD5Update MD5_Update
#define MD5Final MD5_Final
#else
#ifdef USE_POP
#include "global.h"
#include "md5.h"
#endif
#endif
#ifdef CPP
char *CppCommand = CPP;
char *CppOptions = NULL;
#endif
#ifdef NO_ADDRINFO
#undef AF_INET6
#endif
#ifdef NO_SOCKLEN_T
typedef int socklen_t;
#endif
typedef struct _Chat {
struct _Chat *next;
char *send;
int len;
regex_t expect;
} Chat;
typedef struct {
socklen_t len;
struct sockaddr addr;
} SockAddr;
#define SockAddrBaseSize (sizeof(SockAddr) - sizeof(struct sockaddr))
typedef struct _XHosts {
struct _XHosts *next;
short mbits;
short mode;
SockAddr xhost; /* must be the last member */
} XHosts;
#define XHostsBaseSize (sizeof(XHosts) - sizeof(struct sockaddr))
#define XHostsMode_Dump 0xF
typedef struct _XPorts {
struct _XPorts *next;
short from; /* port range from */
short end; /* port range to, or equals to from */
} XPorts;
typedef struct _PortXHosts {
struct _PortXHosts *next;
XPorts *ports;
XHosts *xhosts;
} PortXHosts;
typedef struct _Backup {
struct _Backup *next;
SockAddr *check;
/* host:port for check (usually same as master) */
SockAddr *master;
SockAddr *backup;
int proto;
Chat *chat; /* chat script for health check */
short interval; /* interval of health check */
short bn; /* 0: health, 1: backup */
short used; /* 0: not used, 1: assigned, 2: used */
time_t last; /* last health check */
} Backup;
typedef struct _LBSet {
struct _LBSet *next;
int proto;
short ndsts;
SockAddr *dsts[0];
} LBSet;
#define type_mask 0x000f
#define type_pair 0x0001
#define type_origin 0x0002
#define type_stone 0x0003
#define type_pktbuf 0x0004
typedef struct _Stone {
int common;
SOCKET sd; /* socket descriptor to listen */
int port;
SockAddr *listen;
short ndsts; /* # of destinations */
SockAddr **dsts; /* destinations */
SockAddr *from;
int proto;
Backup **backups;
struct _Pair *pairs;
char *p;
int timeout;
struct _Stone *next;
struct _Stone *children;
struct _Stone *parent;
#ifdef USE_SSL
StoneSSL *ssl_server;
StoneSSL *ssl_client;
#endif
int nhosts; /* # of hosts */
XHosts *xhosts; /* hosts permitted to connect */
} Stone;
typedef struct _TimeLog {
time_t clock; /* time of beginning */
int pri; /* log priority */
char str[0]; /* Log message */
} TimeLog;
const int data_parm_mask = 0x00ff;
const int data_apop = 0x0100;
const int data_identuser = 0x0200;
const int data_ucred = 0x0300;
const int data_peeraddr = 0x0400;
#define DATA_HEAD_LEN sizeof(int)
typedef struct _ExBuf { /* extensible buffer */
struct _ExBuf *next;
int start; /* index of buf */
int len; /* last data is at buf[start+len-1] */
int bufmax; /* buffer size */
char buf[BUFMAX];
} ExBuf;
typedef struct _Pair {
int common;
struct _Pair *pair;
struct _Pair *prev;
struct _Pair *next;
Stone *stone; /* parent */
#ifdef USE_SSL
SSL *ssl; /* SSL handle */
int ssl_flag;
#endif
XHosts *xhost;
time_t clock;
int timeout;
SOCKET sd; /* socket descriptor */
int proto;
int count; /* reference counter */
ExBuf *d;
TimeLog *log;
int tx; /* sent bytes */
int rx; /* received bytes */
int loop; /* loop count */
int nbuf;
ExBuf *t; /* top */
ExBuf *b; /* bottom */
} Pair;
typedef struct _Conn {
SockAddr *dst; /* destination */
Pair *pair;
int lock;
struct _Conn *next;
} Conn;
typedef struct _Origin {
int common;
SOCKET sd; /* peer */
Stone *stone;
SockAddr *from; /* from where */
int lock;
XHosts *xhost;
time_t clock;
struct _Origin *next;
} Origin;
typedef struct _PktBuf { /* packet buffer */
int common;
struct _PktBuf *next;
int type;
Origin *origin;
int len;
int bufmax; /* buffer size */
char buf[BUFMAX];
} PktBuf;
typedef struct _Comm {
char *str;
int (*func)(Pair*, char*, int);
} Comm;
Stone *stones = NULL;
Stone *oldstones = NULL;
int ReuseAddr = 0;
PortXHosts *portXHosts = NULL;
XHosts *XHostsTrue = NULL;
Chat *healthChat = NULL;
Backup *backups = NULL;
LBSet *lbsets = NULL;
int MinInterval = 0;
time_t lastScanBackups = 0;
time_t lastEstablished = 0;
time_t lastReadWrite = 0;
Pair *PairTop = NULL;
Pair trash;
Pair *freePairs = NULL;
int nFreePairs = 0;
ExBuf *freeExBuf = NULL;
int nFreeExBuf = 0;
ExBuf *freeExBot = NULL;
int nFreeExBot = 0;
time_t freeExBotClock = 0;
Conn conns;
Origin *OriginTop = NULL;
int OriginMax = 100;
PktBuf *freePktBuf = NULL;
int nFreePktBuf = 0;
#ifdef USE_EPOLL
int ePollFd;
#else
fd_set rin, win, ein;
#endif
int PairTimeOut = 10 * 60; /* 10 min */
int AsyncCount = 0;
int MutexConflict = 0;
const int state_mask = 0x00ff;
const int proto_command = 0x0f00; /* command (dest. only) */
/* only for Stone */
const int proto_ident = 0x1000; /* need ident */
const int proto_nobackup = 0x2000; /* no backup */
const int proto_udp_s = 0x4000; /* UDP source */
const int proto_udp_d = 0x8000; /* destination */
const int proto_v6_s = 0x10000; /* IPv6 source */
const int proto_v6_d = 0x20000; /* destination */
const int proto_ip_only_s = 0x40000; /* IPv6 only source */
const int proto_ip_only_d = 0x80000; /* destination */
const int proto_unix_s = 0x100000; /* unix socket source */
const int proto_unix_d = 0x200000; /* destination */
const int proto_block_s = 0x400000; /* blocking I/O source */
const int proto_block_d = 0x800000; /* destination*/
const int proto_ssl_s = 0x1000000; /* SSL source */
const int proto_ssl_d = 0x2000000; /* destination */
/* only for Pair */
const int proto_dirty = 0x1000; /* ev must be updated */
const int proto_noconnect = 0x2000; /* no connection needed */
const int proto_connect = 0x4000; /* connection established */
const int proto_dgram = 0x8000; /* UDP */
const int proto_first_r = 0x10000; /* first read packet */
const int proto_first_w = 0x20000; /* first written packet */
const int proto_select_r = 0x40000; /* select to read */
const int proto_select_w = 0x80000; /* select to write */
const int proto_shutdown = 0x100000; /* sent shutdown */
const int proto_close = 0x200000; /* request to close */
const int proto_eof = 0x400000; /* EOF was received */
const int proto_error = 0x800000; /* error reported */
const int proto_thread = 0x1000000; /* on thread */
const int proto_conninprog = 0x2000000; /* connect in progress */
const int proto_ohttp_s = 0x4000000; /* over http source */
const int proto_ohttp_d = 0x8000000; /* destination */
const int proto_base_s = 0x10000000; /* base64 source */
const int proto_base_d = 0x20000000; /* destination */
#define command_ihead 0x0100 /* insert header */
#define command_iheads 0x0200 /* insert header repeatedly */
#define command_pop 0x0300 /* POP -> APOP conversion */
#define command_health 0x0400 /* is stone healthy ? */
#define command_identd 0x0500 /* identd of stone */
#define command_proxy 0x0600 /* http proxy */
#define command_source 0x0f00 /* source flag */
#define proto_ssl (proto_ssl_s|proto_ssl_d)
#define proto_v6 (proto_v6_s|proto_v6_d)
#define proto_udp (proto_udp_s|proto_udp_d)
#define proto_ip_only (proto_ip_only_s|proto_ip_only_d)
#define proto_unix (proto_unix_s|proto_unix_d)
#define proto_block (proto_block_s|proto_block_d)
#define proto_ohttp (proto_ohttp_s|proto_ohttp_d)
#define proto_base (proto_base_s|proto_base_d)
#define proto_stone_s (proto_udp_s|proto_command|\
proto_ohttp_s|proto_base_s|\
proto_v6_s|proto_ip_only_s|\
proto_ssl_s|proto_ident)
#define proto_stone_d (proto_udp_d|proto_command|\
proto_ohttp_d|proto_base_d|\
proto_v6_d|proto_ip_only_d|\
proto_ssl_d|proto_nobackup)
#define proto_pair_s (proto_ohttp_s|proto_base_s)
#define proto_pair_d (proto_ohttp_d|proto_base_d|proto_command)
#ifdef USE_SSL
const int sf_mask = 0x0000f;
const int sf_depth = 0x000f0; /* depth of cert chain */
const int sf_depth_bit = 4;
const int sf_sb_on_r = 0x00100; /* SSL_shutdown blocked on read */
const int sf_sb_on_w = 0x00200; /* SSL_shutdown blocked on write */
const int sf_wb_on_r = 0x00400; /* SSL_write blocked on read */
const int sf_rb_on_w = 0x00800; /* SSL_read blocked on write */
const int sf_cb_on_r = 0x01000; /* SSL_connect blocked on read */
const int sf_cb_on_w = 0x02000; /* SSL_connect blocked on write */
const int sf_ab_on_r = 0x04000; /* SSL_accept blocked on read */
const int sf_ab_on_w = 0x08000; /* SSL_accept blocked on write */
#endif
int BacklogMax = BACKLOG_MAX;
int XferBufMax = 1000; /* TCP packet buffer initial size (must < 1024 ?) */
#define PKT_LEN_INI 2048 /* initial size */
int pkt_len_max = PKT_LEN_INI; /* size of UDP packet buffer */
int AddrFlag = 0;
#ifndef NO_SYSLOG
int Syslog = 0;
char SyslogName[STRMAX+1];
#endif
FILE *LogFp = NULL;
char *LogFileName = NULL;
FILE *AccFp = NULL;
char *AccFileName = NULL;
char *ConfigFile = NULL;
char *PidFile = NULL;
SockAddr *ConnectFrom = NULL;
int DryRun = 0;
int ConfigArgc = 0;
int OldConfigArgc = 0;
char **ConfigArgv = NULL;
char **OldConfigArgv = NULL;
#ifdef UNIX_DAEMON
int DaemonMode = 0;
#endif
#ifndef NO_CHROOT
char *RootDir = NULL;
#endif
#ifndef NO_SETUID
uid_t SetUID = 0;
gid_t SetGID = 0;
#endif
char *CoreDumpDir = NULL;
#ifdef NO_PID_T
typedef int pid_t;
#endif
pid_t MyPid;
#ifndef NO_FORK
int NForks = 0;
pid_t *Pid;
#endif
int Debug = 0; /* debugging level */
#ifdef ADDRCACHE
#define CACHE_TIMEOUT 180 /* 3 min */
int AddrCacheSize = 0;
#endif
#ifdef PTHREAD
pthread_mutex_t FastMutex = PTHREAD_MUTEX_INITIALIZER;
char FastMutexs[11];
#define PairMutex 0
#define ConnMutex 1
#define OrigMutex 2
#define AsyncMutex 3
#ifndef USE_EPOLL
#define FdRinMutex 4
#define FdWinMutex 5
#define FdEinMutex 6
#endif
#define ExBufMutex 7
#define FPairMutex 8
#define PkBufMutex 9
#ifdef ADDRCACHE
#define HashMutex 10
#endif
#endif
#ifdef WINDOWS
HANDLE PairMutex, ConnMutex, OrigMutex, AsyncMutex;
HANDLE FdRinMutex, FdWinMutex, FdEinMutex;
HANDLE ExBufMutex, FPairMutex, PkBufMutex;
#ifdef ADDRCACHE
HANDLE HashMutex;
#endif
#endif
#ifdef OS2
HMTX PairMutex, ConnMutex, OrigMutex, AsyncMutex;
HMTX FdRinMutex, FdWinMutex, FdEinMutex;
HMTX ExBufMutex, FPairMutex, PkBufMutex;
#ifdef ADDRCACHE
HMTX HashMutex;
#endif
#endif
#ifdef NT_SERVICE
SERVICE_STATUS NTServiceStatus;
SERVICE_STATUS_HANDLE NTServiceStatusHandle;
#define NTServiceDisplayPrefix "Stone "
char *NTServiceDisplayName = NULL;
char *NTServiceName = NULL;
HANDLE NTServiceLog = NULL;
HANDLE NTServiceThreadHandle = NULL;
#endif
#ifdef NO_VSNPRINTF
int vsnprintf(char *str, size_t len, char *fmt, va_list ap) {
int ret;
ret = vsprintf(str, fmt, ap);
if (strlen(str) >= len) {
fprintf(stderr, "Buffer overrun\n");
exit(1);
}
return ret;
}
#endif
#ifdef NO_SNPRINTF
int snprintf(char *str, size_t len, char *fmt, ...) {
va_list ap;
int ret;
va_start(ap, fmt);
ret = vsnprintf(str, len, fmt, ap);
va_end(ap);
return ret;
}
#endif
#ifdef NO_BCOPY
void bcopy(const void *b1, void *b2, int len) {
if (b1 < b2 && (char*)b2 < (char*)b1 + len) { /* overlapping */
char *p, *q;
q = (char*)b2 + len - 1;
for (p=(char*)b1+len-1; (char*)b1 <= p; p--, q--) *q = *p;
} else {
memcpy(b2, b1, len);
}
}
#endif
#ifdef NO_RINDEX
char *rindex(char *p, int ch) {
char *save = NULL;
do {
if (*p == ch) save = p;
} while (*p++);
return save;
}
#endif
#ifdef NO_STRDUP
char *strdup(const char *s) {
int len = strlen(s);
char *ret = malloc(len+1);
if (ret) {
bcopy(s, ret, len+1);
}
return ret;
}
#endif
#ifdef WINDOWS
struct tm *localtime_r(const time_t *clock, struct tm *t) {
FILETIME utc, local;
SYSTEMTIME system;
LONGLONG ll;
ll = Int32x32To64(*clock, 10000000) + 116444736000000000ULL;
utc.dwLowDateTime = (DWORD)ll;
utc.dwHighDateTime = ll >> 32;
if (!FileTimeToLocalFileTime(&utc, &local)) return NULL;
if (!FileTimeToSystemTime(&local, &system)) return NULL;
t->tm_sec = system.wSecond;
t->tm_min = system.wMinute;
t->tm_hour = system.wHour;
t->tm_mday = system.wDay;
t->tm_mon = system.wMonth-1;
t->tm_year = system.wYear-1900;
t->tm_wday = system.wDayOfWeek;
return t;
}
#endif
static char Month[][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
char *strntime(char *str, int len, time_t *clock, long micro) {
#ifdef THREAD_UNSAFE
struct tm *t = localtime(clock);
#else
struct tm tm;
struct tm *t = localtime_r(clock, &tm);
#endif
if (micro >= 0) {
snprintf(str, len, "%s %2d %02d:%02d:%02d.%06ld ",
Month[t->tm_mon], t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec, micro);
} else {
snprintf(str, len, "%s %2d %02d:%02d:%02d ",
Month[t->tm_mon], t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec);
}
return str;
}
#ifdef NO_GETTIMEOFDAY
int gettimeofday(struct timeval *tv, void *tz) {
static u_long start = 0;
u_long tick = GetTickCount();
time_t now;
time(&now);
if (start == 0) start = now - tick / 1000;
if (tz) return -1;
if (tv) {
tv->tv_usec = (tick % 1000) * 1000;
tv->tv_sec = start + (tick / 1000);
if (now < tv->tv_sec - 1 || tv->tv_sec + 1 < now) {
start = 0;
tv->tv_usec = -1; /* diff is too large */
}
return 0;
}
return -1;
}
#endif
#if defined (__STDC__) && __STDC__
void message(int pri, char *fmt, ...)
__attribute__ ((__format__ (__printf__, 2, 3)));
#endif
void message(int pri, char *fmt, ...) {
char str[LONGSTRMAX+1];
int pos = 0;
unsigned long thid = 0;
va_list ap;
#ifndef NO_SYSLOG
if (!Syslog)
#endif
{
struct timeval tv;
if (gettimeofday(&tv, NULL) >= 0) {
strntime(str+pos, LONGSTRMAX-pos, &tv.tv_sec, tv.tv_usec);
}
str[LONGSTRMAX] = '\0';
pos = strlen(str);
}
#ifdef WINDOWS
thid = (unsigned long)GetCurrentThreadId();
#else
#ifdef PTHREAD
thid = (unsigned long)pthread_self();
#endif
#endif
if (thid) {
snprintf(str+pos, LONGSTRMAX-pos, "%lu ", thid);
pos += strlen(str+pos);
}
va_start(ap, fmt);
vsnprintf(str+pos, LONGSTRMAX-pos, fmt, ap);
va_end(ap);
str[LONGSTRMAX] = '\0';
#ifndef NO_SYSLOG
if (Syslog) {
if (Syslog == 1
|| pri != LOG_DEBUG) syslog(pri, "%s", str);
if (Syslog > 1) fprintf(stdout, "%s\n", str); /* daemontools */
} else
#else
#ifdef NT_SERVICE
if (NTServiceLog) {
LPCTSTR msgs[] = {str, NULL};
int type = EVENTLOG_INFORMATION_TYPE;
if (pri <= LOG_ERR) type = EVENTLOG_ERROR_TYPE;
else if (pri <= LOG_NOTICE) type = EVENTLOG_WARNING_TYPE;
ReportEvent(NTServiceLog, type, 0, EVLOG, NULL, 1, 0, msgs, NULL);
} else
#endif
#endif
if (LogFp) fprintf(LogFp, "%s\n", str);
}
void message_time(Pair *pair, int pri, char *fmt, ...) {
va_list ap;
char str[LONGSTRMAX+1];
TimeLog *log;
log = pair->log;
if (log) {
pair->log = NULL;
free(log);
}
va_start(ap, fmt);
vsnprintf(str, LONGSTRMAX, fmt, ap);
va_end(ap);
str[LONGSTRMAX] = '\0';
log = (TimeLog*)malloc(sizeof(TimeLog)+strlen(str)+1);
if (log) {
time(&log->clock);
log->pri = pri;
strcpy(log->str, str);
pair->log = log;
}
}
int priority(Pair *pair) {
int pri = LOG_ERR;
if (pair) {
if (pair->proto & proto_error) pri = LOG_DEBUG;
else pair->proto |= proto_error;
}
return pri;
}
void packet_dump(char *head, char *buf, int len, XHosts *xhost) {
char line[LONGSTRMAX+1];
int mode = (xhost->mode & XHostsMode_Dump);
int i, j, k, l;
int nb = 8;
j = k = l = 0;
for (i=0; i < len; i += j) {
if (mode <= 2) {
nb = 16;
l = 0;
line[l++] = ' ';
for (j=0; k <= j/10 && i+j < len && l < LONGSTRMAX-10; j++) {
if (' ' <= buf[i+j] && buf[i+j] <= '~')
line[l++] = buf[i+j];
else {
sprintf(&line[l], "<%02x>", buf[i+j]);
l += strlen(&line[l]);
if (buf[i+j] == '\n') {
k = 0;
j++;
break;
}
if (buf[i+j] != '\t' && buf[i+j] != '\r'
&& buf[i+j] != '\033')
k++;
}
}
}
if (k > j/10 || nb < 16) {
j = l = 0;
for (j=0; j < nb && i+j < len; j++) {
if (mode == 1 && (' ' <= buf[i+j] && buf[i+j] <= '~')) {
sprintf(&line[l], " '%c", buf[i+j]);
} else {
sprintf(&line[l], " %02x", (unsigned char)buf[i+j]);
if (buf[i+j] == '\n') k = 0; else k++;
}
l += strlen(&line[l]);
}
if (nb < 16) {
while (l < (nb * 3) + 2) line[l++] = ' ';
for (j=0; j < nb && i+j < len; j++) {
if (' ' <= buf[i+j] && buf[i+j] <= '~')
line[l++] = buf[i+j];
else
line[l++] = '.';
}
}
}
line[l] = '\0';
message(LOG_DEBUG, "%s%s", head, line);
}
}
void message_buf(Pair *pair, int len, char *str) { /* dump for debug */
char head[STRMAX+1];
Pair *p = pair->pair;
if (p == NULL) return;
head[STRMAX] = '\0';
if ((pair->proto & proto_command) == command_source) {
snprintf(head, STRMAX, "%d %s%d<%d",
pair->stone->sd, str, pair->sd, p->sd);
} else {
snprintf(head, STRMAX, "%d %s%d>%d",
pair->stone->sd, str, p->sd, pair->sd);
}
packet_dump(head, pair->t->buf + pair->t->start, len, pair->xhost);
}
char *addr2ip(struct in_addr *addr, char *str, int len) {
union {
u_long l;
unsigned char c[4];
} u;
if (len >= 1) {
u.l = addr->s_addr;
snprintf(str, len-1, "%d.%d.%d.%d", u.c[0], u.c[1], u.c[2], u.c[3]);
str[len-1] = '\0';
}
return str;
}
#ifdef AF_INET6
char *addr2ip6(struct in6_addr *addr, char *str, int len) {
u_short *s;
if (len >= 1) {
s = (u_short*)addr;
snprintf(str, len-1, "%x:%x:%x:%x:%x:%x:%x:%x",
ntohs(s[0]), ntohs(s[1]), ntohs(s[2]), ntohs(s[3]),
ntohs(s[4]), ntohs(s[5]), ntohs(s[6]), ntohs(s[7]));
str[len-1] = '\0';
}
return str;
}
#endif
char *addr2numeric(struct sockaddr *sa, char *str, int len) {
if (sa->sa_family == AF_INET) {
addr2ip(&((struct sockaddr_in*)sa)->sin_addr, str, len);
#ifdef AF_INET6
} else if (sa->sa_family == AF_INET6) {
addr2ip6(&((struct sockaddr_in6*)sa)->sin6_addr, str, len);
#endif
} else {
snprintf(str, len, "%s", "???");
}
return str;
}
char *ext2str(int ext, char *str, int len) {
char sep = '/';
int i = 0;
if (!str || len <= 1) return "";
if (ext & proto_udp) {
if (i < len) str[i++] = sep;
sep = ',';
strncpy(str+i, "udp", len-i);
i += 3;
}
if (ext & proto_ohttp) {
if (i < len) str[i++] = sep;
sep = ',';
strncpy(str+i, "http", len-i);
i += 4;
}
if (ext & proto_ssl) {
if (i < len) str[i++] = sep;
sep = ',';
strncpy(str+i, "ssl", len-i);
i += 3;
}
if (ext & proto_v6) {
if (i < len) str[i++] = sep;
sep = ',';
if (ext & proto_ip_only) {
strncpy(str+i, "v6only", len-i);
i += 6;
} else {
strncpy(str+i, "v6", len-i);
i += 2;
}
} else if (ext & proto_ip_only) {
sep = ',';
strncpy(str+i, "v4only", len-i);
i += 6;
}
if (ext & proto_base) {
if (i < len) str[i++] = sep;
sep = ',';
strncpy(str+i, "base", len-i);
i += 4;
}
if (ext & proto_block) {
if (i < len) str[i++] = sep;
sep = ',';
strncpy(str+i, "block", len-i);
i += 5;
}
if (ext & proto_ident) {
if (i < len) str[i++] = sep;
sep = ',';
strncpy(str+i, "ident", len-i);
i += 5;
}
if (ext & proto_nobackup) {
if (i < len) str[i++] = sep;
sep = ',';
strncpy(str+i, "nobackup", len-i);
i += 8;
}
switch(ext & proto_command) {
case command_ihead:
if (i < len) str[i++] = sep;
sep = ',';
strncpy(str+i, "proxy", len-i);
i += 5;
break;
case command_iheads:
if (i < len) str[i++] = sep;
sep = ',';
strncpy(str+i, "mproxy", len-i);
i += 6;
break;
case command_pop:
if (i < len) str[i++] = sep;
sep = ',';
strncpy(str+i, "apop", len-i);
i += 4;
break;
}
return str;
}
int islocalhost(struct sockaddr *sa) {
if (sa->sa_family == AF_INET) {
if (ntohl(((struct sockaddr_in*)sa)->sin_addr.s_addr) == 0x7F000001L)
return 1; /* localhost */
if (ntohl(((struct sockaddr_in*)sa)->sin_addr.s_addr) == 0L)
return -1; /* null */
}
#ifdef AF_INET6
if (sa->sa_family == AF_INET6) {
int i;
struct in6_addr *addrp = &((struct sockaddr_in6*)sa)->sin6_addr;
for (i=0; i < 12; i+=4)
if (*(u_long*)&addrp->s6_addr[i] != 0) return 0;
if (*(u_long*)&addrp->s6_addr[i] == ntohl(1)) return 1; /* localhost */
if (*(u_long*)&addrp->s6_addr[i] == 0) return -1; /* null */
}
#endif
return 0;
}
#ifdef NO_ADDRINFO
#define NTRY_MAX 10
#ifndef NI_NUMERICHOST
#define NI_NUMERICHOST 1
#endif
char *addr2str(struct sockaddr *sa, socklen_t salen,
char *str, int len, int flags) {
struct hostent *ent;
struct in_addr *addr;
int ntry = NTRY_MAX;
if (!str || len <= 1) return "";
str[len-1] = '\0';
if (sa->sa_family != AF_INET) {
message(LOG_ERR, "Unknown family=%d", sa->sa_family);
strncpy(str, "?.?.?.?", len-1);
return str;
}
addr = &((struct sockaddr_in*)sa)->sin_addr;
addr2ip(addr, str, len);
if (!AddrFlag || flags) {
do {
ent = gethostbyaddr((char*)&addr->s_addr,
sizeof(addr->s_addr), AF_INET);
if (ent) {
strncpy(str, ent->h_name, len-1);
return str;
}
} while (h_errno == TRY_AGAIN && ntry-- > 0);
message(LOG_ERR, "Unknown address: %s err=%d", str, h_errno);
}
return str;
}
char *addrport2str(struct sockaddr *sa, socklen_t salen,
int proto, char *str, int len, int flags) {
struct servent *ent;
int port;
int i = 0;
if (!str || len <= 1) return "";
str[len-1] = '\0';
if (sa->sa_family == AF_INET) {
addr2str(sa, salen, str, len, 0);
i = strlen(str);
if (i < len-2) {
str[i++] = ':';
str[i] = '\0';
}
} else {
message(LOG_ERR, "Unknown address family=%d len=%d",
sa->sa_family, salen);
}
port = ((struct sockaddr_in*)sa)->sin_port;
if (!AddrFlag) {
ent = getservbyport(port, ((proto & proto_udp) ? "udp" : "tcp"));
if (ent) strncpy(str+i, ent->s_name, len-i-5);
}
if (str[i] == '\0')
snprintf(str+i, len-i-5, "%d", ntohs((unsigned short)port));
i = strlen(str);
ext2str(proto, str+i, len-i);
return str;
}
#else
char *addr2str(struct sockaddr *sa, socklen_t salen,
char *str, int len, int flags) {
int err;
if (AddrFlag) flags |= NI_NUMERICHOST;
err = getnameinfo(sa, salen, str, len, NULL, 0, flags);
if (err) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
addr2numeric(sa, str, len);
if (len >= 1) str[len-1] = '\0';
message(LOG_ERR, "Unknown address: %s err=%d errno=%d",
str, err, errno);
}
return str;
}
char *addrport2str(struct sockaddr *sa, socklen_t salen,
int proto, char *str, int len, int flags) {
char serv[STRMAX+1];
int err;
int i;
if (!str || len <= 1) return "";
str[len-1] = '\0';
serv[0] = '\0';
if (AddrFlag) flags |= (NI_NUMERICHOST | NI_NUMERICSERV);
else if (proto & proto_udp) flags |= NI_DGRAM;
if (!(flags & NI_NUMERICHOST) && islocalhost(sa)) flags |= NI_NUMERICHOST;
if (Debug > 10) {
addr2numeric(sa, serv, STRMAX);
serv[STRMAX] = '\0';
message(LOG_DEBUG, "getnameinfo: %s family=%d len=%d flags=%d",
serv, sa->sa_family, salen, flags);
}
#ifndef NO_UNIXDOMAIN
if (sa->sa_family == AF_UNIX) {
int j;
j = salen - (((struct sockaddr_un*)sa)->sun_path - (char*)sa);
strncpy(serv, ((struct sockaddr_un*)sa)->sun_path, j);
serv[j] = '\0';
snprintf(str, len, "%s", "unix");
err = 0;
} else
#endif
err = getnameinfo(sa, salen, str, len, serv, STRMAX, flags);
#ifdef WSANO_DATA
if (err == WSANO_DATA && !(flags & NI_NUMERICSERV)) {
/*
WinSock32 returns WSANO_DATA if serv can't be lookup although
the hostname itself is resolvable. So we must call again
without looking up serv
*/
if (Debug > 10)
message(LOG_DEBUG, "getnameinfo: WSANO_DATA flags=%d", flags);
flags |= NI_NUMERICSERV;
err = getnameinfo(sa, salen, str, len, serv, STRMAX, flags);
}
#endif
if (err) {
if (sa->sa_family == AF_INET) {
addr2ip(&((struct sockaddr_in*)sa)->sin_addr, str, len);
i = strlen(str);
snprintf(str+i, len-i-5, ":%d",
ntohs(((struct sockaddr_in*)sa)->sin_port));
#ifdef AF_INET6
} else if (sa->sa_family == AF_INET6) {
addr2ip6(&((struct sockaddr_in6*)sa)->sin6_addr, str, len);
i = strlen(str);
snprintf(str+i, len-i-5, ":%d",
ntohs(((struct sockaddr_in6*)sa)->sin6_port));
#endif
} else {
snprintf(str, len, "%s:?", "???");
}
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
message(LOG_ERR, "Unknown node:serv %s len=%d err=%d errno=%d",
str, salen, err, errno);
} else {
i = strlen(str);
snprintf(str+i, len-i, ":%s", serv);
}
i = strlen(str);
ext2str(proto, str+i, len-i);
return str;
}
#endif
char *addrport2strOnce(struct sockaddr *sa, socklen_t salen,
int proto, char *str, int len, int flags) {
if (! *str) {
addrport2str(sa, salen, proto, str, len, flags);
str[len] = '\0';
}
return str;
}
int isdigitstr(char *str) {
while (*str && !isspace(*str)) {
if (!isdigit(*str)) return 0;
str++;
}
return 1;
}
int isdigitaddr(char *name) {
int ndigits = 0;
int isdot = 1;
while(*name) {
if (*name == '.') {
if (isdot) return 0; /* `.' appears twice */
isdot = 1;
} else if (isdigit(*name)) {
if (isdot) ndigits++;
isdot = 0;
} else {
return 0; /* not digit nor dot */
}
name++;
}
return ndigits;
}
/* set port into struct sockaddr */
void saPort(struct sockaddr *sa, u_short port) {
if (sa->sa_family == AF_INET) {
((struct sockaddr_in*)sa)->sin_port = htons(port);
return;
}
#ifdef AF_INET6
if (sa->sa_family == AF_INET6) {
((struct sockaddr_in6*)sa)->sin6_port = htons(port);
return;
}
#endif
message(LOG_ERR, "saPort: unknown family=%d", sa->sa_family);
}
/* get port from struct sockaddr */
int getport(struct sockaddr *sa) {
if (sa->sa_family == AF_INET) {
return ntohs(((struct sockaddr_in*)sa)->sin_port);
#ifdef AF_INET6
} else if (sa->sa_family == AF_INET6) {
return ntohs(((struct sockaddr_in6*)sa)->sin6_port);
#endif
}
return -1;
}
int hostPortExt(char *str, char *host, char *port) {
int port_pos = 0;
int ext_pos = 0;
int i;
for (i=0; i < STRMAX && str[i]; i++) {
host[i] = str[i];
port[i-port_pos] = str[i];
if (str[i] == ':') port_pos = i+1;
if (str[i] == '/') ext_pos = i+1;
}
if (!port_pos) return -1; /* illegal format */
host[port_pos-1] = '\0';
if (ext_pos) port[ext_pos - port_pos - 1] = '\0';
else port[i - port_pos] = '\0';
return ext_pos;
}
#ifdef NO_ADDRINFO
int str2port(char *str, char *proto) { /* host byte order */
struct servent *ent;
ent = getservbyname(str, proto);
if (ent) {
return ntohs(ent->s_port);
} else if (isdigitstr(str)) {
return atoi(str);
} else {
return -1;
}
}
int host2sa(char *name, char *serv, struct sockaddr *sa, socklen_t *salenp,
int *socktypep, int *protocolp, int flags) {
struct hostent *hp;
int ntry = NTRY_MAX;
int port = -1;
struct sockaddr_in *sinp = (struct sockaddr_in*)sa;
struct in_addr *addrp = &sinp->sin_addr;
if (*salenp < sizeof(struct sockaddr_in)) {
message(LOG_ERR, "host2sa: too small salen=%d", *salenp);
return 0; /* too small */
}
*salenp = sizeof(struct sockaddr_in);
if (!name) {
bzero(sa, *salenp);
sa->sa_family = AF_INET;
goto hostok;
}
if (isdigitaddr(name)) {
if ((addrp->s_addr=inet_addr(name)) != -1) {
sa->sa_family = AF_INET;
goto hostok;
}
} else {
do {
hp = gethostbyname(name);
if (hp) {
bcopy(hp->h_addr, (char *)addrp, hp->h_length);
sa->sa_family = hp->h_addrtype;
hostok:
if (serv) {
if (protocolp && *protocolp == IPPROTO_UDP) {
port = str2port(serv, "udp");
} else {
port = str2port(serv, "tcp");
}
if (port < 0) {
message(LOG_ERR, "Unknown service: %s", serv);
return 0;
}
saPort(sa, port);
}
return 1;
}
} while (h_errno == TRY_AGAIN && ntry-- > 0);
}
message(LOG_ERR, "Unknown host: %s err=%d", name, h_errno);
return 0;
}
#else
int host2sa(char *name, char *serv, struct sockaddr *sa, socklen_t *salenp,
int *socktypep, int *protocolp, int flags) {
struct addrinfo *ai = NULL;
struct addrinfo hint;
int err;
hint.ai_flags = flags;
hint.ai_family = sa->sa_family;
if (socktypep) hint.ai_socktype = *socktypep;
else hint.ai_socktype = SOCK_STREAM;
if (protocolp) hint.ai_protocol = *protocolp;
else hint.ai_protocol = 0;
hint.ai_addrlen = 0;
hint.ai_addr = NULL;
hint.ai_canonname = NULL;
hint.ai_next = NULL;
if (Debug > 10) {
message(LOG_DEBUG, "getaddrinfo: %s:%s family=%d socktype=%d flags=%d",
(name ? name : ""), (serv ? serv : ""),
sa->sa_family, hint.ai_socktype, flags);
}
err = getaddrinfo(name, serv, &hint, &ai);
if (err != 0) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
message(LOG_ERR, "getaddrinfo for %s:%s failed err=%d errno=%d",
(name ? name : ""), (serv ? serv : ""), err, errno);
fail:
if (ai) freeaddrinfo(ai);
return 0;
}
if (ai->ai_addrlen > *salenp) {
message(LOG_ERR,
"getaddrinfo for %s:%s returns unexpected addr size=%d",
(name ? name : ""), (serv ? serv : ""), ai->ai_addrlen);
goto fail;
}
*salenp = ai->ai_addrlen;
if (socktypep) *socktypep = ai->ai_socktype;
if (protocolp) *protocolp = ai->ai_protocol;
bcopy(ai->ai_addr, sa, *salenp);
freeaddrinfo(ai);
return 1;
}
#endif
int hostPort2sa(char *str, struct sockaddr *sa, socklen_t *salenp, int flags) {
char host[STRMAX+1];
char port[STRMAX+1];
int pos = hostPortExt(str, host, port);
if (pos < 0) return 0;
#ifdef AF_INET6
if (pos && !strcmp(str+pos, "v6")) sa->sa_family = AF_INET6;
#endif
return host2sa(host, port, sa, salenp, NULL, NULL, flags);
}
SockAddr *saDup(struct sockaddr *sa, socklen_t salen) {
SockAddr *ret = malloc(SockAddrBaseSize + salen);
if (ret) {
bcopy(sa, &ret->addr, salen);
ret->len = salen;
}
return ret;
}
int saComp(struct sockaddr *a, struct sockaddr *b) {
if (a->sa_family != b->sa_family) {
if (Debug > 10) {
message(LOG_DEBUG, "saComp: sa_family differ: %d, %d",
a->sa_family, b->sa_family);
}
return 0;
}
if (a->sa_family == AF_INET) {
struct in_addr *an, *bn;
short ap, bp;
an = &((struct sockaddr_in*)a)->sin_addr;
bn = &((struct sockaddr_in*)b)->sin_addr;
ap = ((struct sockaddr_in*)a)->sin_port;
bp = ((struct sockaddr_in*)b)->sin_port;
if (Debug > 10) {
message(LOG_DEBUG, "saComp: %lx:%d, %lx:%d",
(long unsigned)ntohl(an->s_addr), ntohs(ap),
(long unsigned)ntohl(bn->s_addr), ntohs(bp));
}
return (an->s_addr == bn->s_addr) && (ap == bp);
}
#ifdef AF_INET6
if (a->sa_family == AF_INET6) {
struct in6_addr *an, *bn;
short ap, bp;
int i;
an = &((struct sockaddr_in6*)a)->sin6_addr;
bn = &((struct sockaddr_in6*)b)->sin6_addr;
ap = ((struct sockaddr_in6*)a)->sin6_port;
bp = ((struct sockaddr_in6*)b)->sin6_port;
if (ap != bp) return 0;
for (i=0; i < 16; i+=4)
if (*(u_long*)&an->s6_addr[i]
!= *(u_long*)&bn->s6_addr[i]) return 0;
return 1;
}
#endif
message(LOG_ERR, "saComp: unknown family=%d", a->sa_family);
return 0;
}
/* *addrp is permitted to connect to *stone ? */
XHosts *checkXhost(XHosts *xhosts, struct sockaddr *sa, socklen_t salen) {
int match = 1;
if (!xhosts) return XHostsTrue; /* any hosts can access */
for (; xhosts != NULL; xhosts = xhosts->next) {
if (xhosts->mbits < 0) {
match = !match;
continue;
}
if (sa->sa_family == AF_INET
&& xhosts->xhost.addr.sa_family == AF_INET) {
if (xhosts->mbits > 0) {
u_long addr = ntohl(((struct sockaddr_in*)sa)
->sin_addr.s_addr);
u_long xadr = ntohl(((struct sockaddr_in*)&xhosts->xhost.addr)
->sin_addr.s_addr);
u_long bits = ((u_long)~0 << (32 - xhosts->mbits));
if ((addr & bits) != (xadr & bits)) continue;
}
if (match) return xhosts;
return NULL;
#ifdef AF_INET6
} else if (sa->sa_family == AF_INET6
&& xhosts->xhost.addr.sa_family == AF_INET6) {
struct in6_addr *adrp = &((struct sockaddr_in6*)sa)->sin6_addr;
struct in6_addr *xadp = &((struct sockaddr_in6*)
&xhosts->xhost.addr)->sin6_addr;
int j, k;
for (j=0, k=xhosts->mbits; k > 0; j+=4, k -= 32) {
u_long addr, xadr, mask;
addr = ntohl(*(u_long*)&adrp->s6_addr[j]);
xadr = ntohl(*(u_long*)&xadp->s6_addr[j]);
if (k >= 32) mask = (u_long)~0;
else mask = ((u_long)~0 << (32-k)); /* premise: k > 0 */
if (Debug > 12)
message(LOG_DEBUG, "compare addr=%lx x=%lx m=%lx",
addr, xadr, mask);
if ((addr & mask) != (xadr & mask)) break;
}
if (k <= 0) {
if (match) return xhosts;
return NULL;
}
} else if (sa->sa_family == AF_INET6
&& xhosts->xhost.addr.sa_family == AF_INET) {
struct in6_addr *adrp = &((struct sockaddr_in6*)sa)->sin6_addr;
if (*(u_long*)&adrp->s6_addr[0] != 0
|| *(u_long*)&adrp->s6_addr[4] != 0
|| ntohl(*(u_long*)&adrp->s6_addr[8]) != 0xFFFF) continue;
if (xhosts->mbits > 0) {
u_long addr = ntohl(*(u_long*)&adrp->s6_addr[12]);
u_long xadr = ntohl(((struct sockaddr_in*)&xhosts->xhost.addr)
->sin_addr.s_addr);
u_long bits = ((u_long)~0 << (32 - xhosts->mbits));
if ((addr & bits) != (xadr & bits)) continue;
}
if (match) return xhosts;
return NULL;
#endif
}
}
if (!match) return XHostsTrue;
return NULL;
}
#ifdef WINDOWS
void waitMutex(HANDLE h) {
DWORD ret;
if (h) {
ret = WaitForSingleObject(h, 5000); /* 5 sec */
if (ret == WAIT_FAILED) {
message(LOG_ERR, "Fail to wait mutex err=%d, existing",
(int)GetLastError());
exit(1);
} else if (ret == WAIT_TIMEOUT) {
message(LOG_ERR, "timeout to wait mutex, existing");
exit(1);
}
}
}
void freeMutex(HANDLE h) {
if (h) {
if (!ReleaseMutex(h)) {
message(LOG_ERR, "Fail to release mutex err=%d",
(int)GetLastError());
}
}
}
#else /* ! WINDOWS */
#ifdef OS2
void waitMutex(HMTX h) {
APIRET ret;
if (h) {
ret = DosRequestMutexSem(h, 500); /* 0.5 sec */
if (ret == ERROR_TIMEOUT) {
message(LOG_WARNING, "timeout to wait mutex");
} else if (ret) {
message(LOG_ERR, "Fail to request mutex err=%d", ret);
}
}
}
void freeMutex(HMTX h) {
APIRET ret;
if (h) {
ret = DosReleaseMutexSem(h);
if (ret) {
message(LOG_ERR, "Fail to release mutex err=%d", ret);
}
}
}
#else /* ! OS2 & ! WINDOWS */
#ifdef PTHREAD
void waitMutex(int h) {
int err;
for (;;) {
err = pthread_mutex_lock(&FastMutex);
if (err) {
message(LOG_ERR, "Mutex %d err=%d", h, err);
}
if (FastMutexs[h] == 0) {
int i = ++FastMutexs[h];
pthread_mutex_unlock(&FastMutex);
if (Debug > 20) message(LOG_DEBUG, "Lock Mutex %d = %d", h, i);
break;
}
pthread_mutex_unlock(&FastMutex);
if (Debug > 10) message(LOG_DEBUG, "Mutex conflict %d = %d",
h, FastMutexs[h]);
MutexConflict++;
usleep(100);
}
}
void freeMutex(int h) {
int err = pthread_mutex_lock(&FastMutex);
if (err) {
message(LOG_ERR, "Mutex %d err=%d", h, err);
}
if (FastMutexs[h] > 0) {
if (FastMutexs[h] > 1)
message(LOG_ERR, "Mutex %d Locked Recursively (%d)",
h, FastMutexs[h]);
FastMutexs[h]--;
if (Debug > 20) message(LOG_DEBUG, "Unlock Mutex %d = %d",
h, FastMutexs[h]);
}
pthread_mutex_unlock(&FastMutex);
}
#else /* ! OS2 & ! WINDOWS & PTHREAD */
#define waitMutex(sem) /* */
#define freeMutex(sem) /* */
#endif
#endif
#endif
/* backup */
int healthCheck(struct sockaddr *sa, socklen_t salen,
int proto, int timeout, Chat *chat) {
SOCKET sd;
int ret;
char addrport[STRMAX+1];
#ifdef WINDOWS
u_long param;
#endif
#ifdef USE_EPOLL
int epfd;
struct epoll_event ev;
struct epoll_event evs[1];
#endif
time_t start, now;
time(&start);
sd = socket(sa->sa_family, SOCK_STREAM, IPPROTO_TCP);
if (InvalidSocket(sd)) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
message(LOG_ERR, "health check: can't create socket err=%d",
errno);
return 1; /* I can't tell the master is healthy or not */
}
#ifdef USE_EPOLL
epfd = epoll_create(BACKLOG_MAX);
if (epfd < 0) {
message(LOG_ERR, "health check: can't create epoll err=%d", errno);
return 1; /* I can't tell the master is healthy or not */
}
ev.events = (EPOLLOUT | EPOLLONESHOT);
if (epoll_ctl(epfd, EPOLL_CTL_ADD, sd, &ev) < 0) {
message(LOG_ERR, "health check: epoll_ctl ADD err=%d", errno);
close(epfd);
return 1; /* I can't tell the master is healthy or not */
}
#endif
addrport[0] = '\0';
if (!(proto & proto_block_d)) {
#ifdef WINDOWS
param = 1;
ioctlsocket(sd, FIONBIO, ¶m);
#else
fcntl(sd, F_SETFL, O_NONBLOCK);
#endif
}
ret = connect(sd, sa, salen);
if (ret < 0) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
if (errno == EINPROGRESS) {
#ifndef USE_EPOLL
fd_set wout;
struct timeval tv;
#endif
int optval;
socklen_t optlen = sizeof(optval);
do {
time(&now);
if (now - start >= timeout) goto timeout;
#ifndef USE_EPOLL
tv.tv_sec = 1;
tv.tv_usec = 0;
FD_ZERO(&wout);
FdSet(sd, &wout);
#endif
} while (
#ifdef USE_EPOLL
epoll_wait(epfd, evs, 1, 1000) == 0
#else
select(FD_SETSIZE, NULL, &wout, NULL, &tv) == 0
#endif
);
getsockopt(sd, SOL_SOCKET, SO_ERROR, (char*)&optval, &optlen);
if (optval) {
addrport2strOnce(sa, salen, (proto & proto_pair_d),
addrport, STRMAX, 0);
message(LOG_ERR, "health check: connect %s getsockopt err=%d",
addrport, optval);
goto fail;
}
} else {
addrport2strOnce(sa, salen, (proto & proto_pair_d),
addrport, STRMAX, 0);
message(LOG_ERR, "health check: connect %s err=%d",
addrport, errno);
goto fail;
}
}
time(&now);
if (now - start >= timeout) goto timeout;
while (chat) {
char buf[BUFMAX];
int len;
int err;
ret = send(sd, chat->send, chat->len, 0);
if (ret < 0 || ret != chat->len) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
addrport2strOnce(sa, salen, (proto & proto_pair_d),
addrport, STRMAX, 0);
message(LOG_ERR, "health check: send %s err=%d",
addrport, errno);
goto fail;
}
len = 0;
do {
#ifdef USE_EPOLL
ev.events = (EPOLLIN | EPOLLONESHOT);
epoll_ctl(epfd, EPOLL_CTL_MOD, sd, &ev);
#else
fd_set rout;
struct timeval tv;
#endif
do {
time(&now);
if (now - start >= timeout) goto timeout;
#ifndef USE_EPOLL
tv.tv_sec = 1;
tv.tv_usec = 0;
FD_ZERO(&rout);
FdSet(sd, &rout);
#endif
} while (
#ifdef USE_EPOLL
epoll_wait(epfd, evs, 1, 1000) == 0
#else
select(FD_SETSIZE, &rout, NULL, NULL, &tv) == 0
#endif
);
ret = recv(sd, buf+len, BUFMAX-1-len, 0);
if (ret < 0) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
addrport2strOnce(sa, salen, (proto & proto_pair_d),
addrport, STRMAX, 0);
message(LOG_ERR, "health check: recv from %s err=%d",
addrport, errno);
goto fail;
}
len += ret;
buf[len] = '\0';
err = regexec(&chat->expect, buf, 0, NULL, 0);
if (Debug > 8) {
addrport2strOnce(sa, salen, (proto & proto_pair_d),
addrport, STRMAX, 0);
message(LOG_DEBUG, "health check: %s regexec=%d",
addrport, err);
}
if (len > BUFMAX/2) {
bcopy(buf+(len-BUFMAX/2), buf, BUFMAX/2);
len = BUFMAX/2;
}
} while (ret > 0 && err == REG_NOMATCH);
#ifndef REG_NOERROR
#ifdef REG_OK
#define REG_NOERROR REG_OK
#else
#define REG_NOERROR 0
#endif
#endif
if (err != REG_NOERROR) goto fail;
chat = chat->next;
}
shutdown(sd, 2);
#ifdef USE_EPOLL
close(epfd);
#endif
closesocket(sd);
return 1; /* healthy ! */
timeout:
if (Debug > 8) {
addrport2strOnce(sa, salen, (proto & proto_pair_d),
addrport, STRMAX, 0);
message(LOG_DEBUG, "health check: %s timeout", addrport);
}
fail:
shutdown(sd, 2);
#ifdef USE_EPOLL
close(epfd);
#endif
closesocket(sd);
return 0; /* fail */
}
void asyncHealthCheck(Backup *b) {
time_t now;
char addrport[STRMAX+1];
ASYNC_BEGIN;
time(&now);
b->last = now + 60 * 60; /* suppress further check */
addrport[0] = '\0';
if (Debug > 8) {
addrport2strOnce(&b->check->addr, b->check->len,
(b->proto & proto_pair_d), addrport, STRMAX, 0);
message(LOG_DEBUG, "asyncHealthCheck %s", addrport);
}
if (healthCheck(&b->check->addr, b->check->len,
b->proto, b->interval, b->chat)) { /* healthy ? */
if (Debug > 3 || (b->bn && Debug > 1)) {
addrport2strOnce(&b->check->addr, b->check->len,
(b->proto & proto_pair_d), addrport, STRMAX, 0);
message(LOG_DEBUG, "health check %s success", addrport);
}
if (b->bn) b->bn = 0;
} else { /* unhealthy */
if (Debug > 3 || (b->bn == 0 && Debug > 0)) {
addrport2strOnce(&b->check->addr, b->check->len,
(b->proto & proto_pair_d), addrport, STRMAX, 0);
message(LOG_DEBUG, "health check %s fail", addrport);
}
if (b->bn == 0) b->bn++;
}
b->last = now;
ASYNC_END;
}
void scanBackups(void) {
Backup *b;
time_t now;
time(&now);
for (b=backups; b != NULL; b=b->next) {
if (b->used < 2) continue; /* not used */
if (b->interval <= 0 || now - b->last < b->interval) continue;
ASYNC(asyncHealthCheck, b);
}
}
Backup *findBackup(struct sockaddr *sa) {
Backup *b;
for (b=backups; b != NULL; b=b->next) {
if (saComp(sa, &b->master->addr)) { /* found */
if (Debug > 1) {
char mhostport[STRMAX+1];
char bhostport[STRMAX+1];
addrport2str(&b->master->addr, b->master->len,
(b->proto & proto_pair_d), mhostport, STRMAX, 0);
mhostport[STRMAX] = '\0';
addrport2str(&b->backup->addr, b->backup->len,
(b->proto & proto_pair_d), bhostport, STRMAX, 0);
bhostport[STRMAX] = '\0';
message(LOG_DEBUG, "master %s backup %s interval %d",
mhostport, bhostport, b->interval);
}
return b;
}
}
return NULL;
}
int gcd(int a, int b) {
int m;
if (a > b) {
m = a % b;
if (m == 0) return b;
return gcd(m, b);
} else {
m = b % a;
if (m == 0) return a;
return gcd(m, a);
}
}
int mkBackup(int argc, int argi, char *argv[]) {
char master_host[STRMAX+1];
char master_port[STRMAX+1];
char *master_ext = NULL;
char backup_host[STRMAX+1];
char backup_port[STRMAX+1];
int pos;
char *check_host = NULL;
char *check_port = NULL;
char *check_ext = NULL;
struct sockaddr_storage ss;
struct sockaddr *sa = (struct sockaddr*)&ss;
socklen_t salen;
Backup *b = malloc(sizeof(Backup));
argi++;
for ( ; argi < argc; argi++) {
if (!strncmp(argv[argi], "host=", 5)) {
check_host = argv[argi]+5;
} else if (!strncmp(argv[argi], "port=", 5)) {
check_port = argv[argi]+5;
} else if (!strncmp(argv[argi], "ext=", 4)) {
check_ext = argv[argi]+4;
} else {
break;
}
}
if (argi+2 >= argc) {
message(LOG_ERR, "Irregular backup option");
exit(1);
}
if (b) {
b->last = 0;
b->bn = 0; /* healthy */
b->used = 0;
b->interval = atoi(argv[argi]);
} else {
memerr:
message(LOG_CRIT, "Out of memory, no backup for %s", argv[argi+1]);
return argi+2;
}
if (b->interval > 0) {
if (MinInterval > 0) {
MinInterval = gcd(MinInterval, b->interval);
} else {
MinInterval = b->interval;
}
} else {
b->bn = 1; /* force unhealthy */
}
b->proto = 0;
pos = hostPortExt(argv[argi+1], master_host, master_port);
if (pos < 0) {
message(LOG_ERR, "Illegal master: %s", argv[argi+1]);
free(b);
return argi+2;
} else if (pos > 0) {
master_ext = argv[argi+1] + pos;
}
salen = sizeof(ss);
sa->sa_family = AF_UNSPEC;
#ifdef AF_INET6
if (master_ext && !strcmp(master_ext, "v6")) sa->sa_family = AF_INET6;
#endif
if (host2sa(master_host, master_port, sa, &salen, NULL, NULL, 0)) {
b->master = saDup(sa, salen);
if (!b->master) {
free(b);
goto memerr;
}
b->check = b->master;
} else {
free(b);
return argi+2;
}
pos = hostPortExt(argv[argi+2], backup_host, backup_port);
if (pos < 0) {
message(LOG_ERR, "Illegal backup: %s", argv[argi+2]);
free(b);
return argi+2;
}
salen = sizeof(ss);
sa->sa_family = AF_UNSPEC;
#ifdef AF_INET6
if (pos && !strcmp(argv[argi+2]+pos, "v6")) sa->sa_family = AF_INET6;
#endif
if (host2sa(backup_host, backup_port, sa, &salen, NULL, NULL, 0)) {
b->backup = saDup(sa, salen);
if (!b->backup) {
free(b->master);
free(b);
goto memerr;
}
} else {
free(b->master);
free(b);
return argi+2;
}
if (check_host || check_port || check_ext) {
if (!check_host) check_host = master_host;
if (!check_port) check_port = master_port;
if (!check_ext) check_ext = master_ext;
salen = sizeof(ss);
sa->sa_family = AF_UNSPEC;
#ifdef AF_INET6
if (check_ext && !strcmp(check_ext, "v6")) sa->sa_family = AF_INET6;
#endif
if (host2sa(check_host, check_port, sa, &salen, NULL, NULL, 0)) {
b->check = saDup(sa, salen);
if (!b->check) {
free(b->backup);
free(b->master);
free(b);
goto memerr;
}
}
}
b->chat = healthChat;
b->next = backups;
backups = b;
return argi+2;
}
int str2num(char **pp, int rad) {
char *p;
int num;
int i;
p = *pp;
num = 0;
for (i=0; i < 3; i++) { /* 3 digit at most */
char c = p[i];
if ('0' <= c && c <= '9') {
num = num * rad + c;
} else {
c = toupper(c);
if (rad > 10 && ('A' <= c && c <= ('A' + rad - 11))) {
num = num * rad + (c - 'A' + 10);
} else {
break;
}
}
}
*pp = p;
return num;
}
char *str2bin(char *p, int *lenp) {
char buf[BUFMAX];
char c;
int i = 0;
while ((c=*p++) && i < BUFMAX-5) {
if (c == '\\') {
c = *p++;
switch(c) {
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case '0': c = str2num(&p, 8); break;
case 'x': c = str2num(&p, 16); break;
case '\0':
c = '\\';
p--;
}
}
buf[i++] = c;
}
p = malloc(i);
if (!p) {
message(LOG_CRIT, "Out of memory, can't make str");
exit(1);
}
bcopy(buf, p, i);
*lenp = i;
return p;
}
int mkChat(int argc, int i, char *argv[]) {
Chat *top, *bot;
top = bot = NULL;
i++;
for ( ; i+1 < argc; i+=2) {
Chat *cur;
int err;
if (argv[i][0] == '-' && argv[i][1] == '-') {
healthChat = top;
return i;
}
cur = malloc(sizeof(Chat));
if (!cur) {
memerr:
message(LOG_CRIT, "Out of memory, can't make Chat");
exit(1);
}
cur->send = str2bin(argv[i], &cur->len);
if (!cur->send) {
free(cur);
goto memerr;
}
err = regcomp(&cur->expect, argv[i+1], REG_EXTENDED);
if (err) {
message(LOG_ERR, "RegEx compiling error: \"%s\" err=%d",
argv[i+1], err);
exit(1);
}
cur->next = NULL;
if (!top) top = cur;
if (bot) bot->next = cur;
bot = cur;
}
message(LOG_ERR, "chat script ends unexpectedly");
exit(1);
return i;
}
LBSet *findLBSet(struct sockaddr *sa) {
LBSet *s;
for (s=lbsets; s != NULL; s=s->next) {
if (saComp(&s->dsts[0]->addr, sa)) { /* found */
if (Debug > 1) {
char buf[LONGSTRMAX+1];
int len;
int i;
buf[LONGSTRMAX] = '\0';
strcpy(buf, "LB set:");
len = strlen(buf);
for (i=0; i < s->ndsts && len < LONGSTRMAX-2; i++) {
buf[len++] = ' ';
addrport2str(&s->dsts[i]->addr, s->dsts[i]->len,
(s->proto & proto_pair_d),
buf+len, LONGSTRMAX-1-len, 0);
len += strlen(buf+len);
}
message(LOG_DEBUG, "%s", buf);
}
return s;
}
}
return NULL;
}
int lbsopts(int argc, int i, char *argv[]) {
SockAddr *dsts[LB_MAX];
int ndsts = 0;
LBSet *lbs;
struct sockaddr_storage ss;
struct sockaddr *sa = (struct sockaddr*)&ss;
socklen_t salen;
int proto = 0;
i++;
for ( ; i < argc; i++) {
if (argv[i][0] == '-' && argv[i][1] == '-') break;
if (ndsts >= LB_MAX) {
message(LOG_ERR, "Too many load balancing hosts");
exit(1);
}
salen = sizeof(ss);
sa->sa_family = AF_UNSPEC;
if (!hostPort2sa(argv[i], sa, &salen, 0)) {
message(LOG_ERR, "Illegal load balancing host: %s", argv[i]);
exit(1);
}
dsts[ndsts] = saDup(sa, salen);
if (!dsts[ndsts]) goto memerr;
ndsts++;
}
lbs = malloc(sizeof(LBSet) + sizeof(SockAddr*) * ndsts);
if (lbs) {
int j;
lbs->next = lbsets;
lbs->proto = proto;
lbs->ndsts = ndsts;
for (j=0; j < ndsts; j++) lbs->dsts[j] = dsts[j];
lbsets = lbs;
} else {
memerr:
message(LOG_CRIT, "Out of memory, can't make LB set");
exit(1);
}
return i;
}
char *stone2str(Stone *stone, char *str, int strlen) {
int proto;
char src[STRMAX+1];
addrport2str(&stone->listen->addr, stone->listen->len,
(stone->proto & proto_stone_s), src, STRMAX, 0);
src[STRMAX] = '\0';
proto = stone->proto;
if ((proto & proto_command) == command_proxy) {
snprintf(str, strlen, "stone %d: proxy <- %s", stone->sd, src);
} else if ((proto & proto_command) == command_health) {
snprintf(str, strlen, "stone %d: health <- %s", stone->sd, src);
} else if ((proto & proto_command) == command_identd) {
snprintf(str, strlen, "stone %d: identd <- %s", stone->sd, src);
} else {
char dst[STRMAX+1];
addrport2str(&stone->dsts[0]->addr, stone->dsts[0]->len,
(stone->proto & proto_stone_d), dst, STRMAX, 0);
dst[STRMAX] = '\0';
snprintf(str, strlen, "stone %d: %s <- %s", stone->sd, dst, src);
}
str[strlen] = '\0';
return str;
}
void ungetExBuf(ExBuf *ex) {
ExBuf *freeptr = NULL;
time_t now;
time(&now);
waitMutex(ExBufMutex);
if (ex->start < 0) {
freeMutex(ExBufMutex);
message(LOG_ERR, "ungetExBuf duplication. can't happen, ignore");
return;
}
if (now - freeExBotClock > FREE_TIMEOUT) {
if (nFreeExBot > 2) {
freeptr = freeExBot->next;
freeExBot->next = NULL;
nFreeExBuf -= (nFreeExBot - 1);
} else {
freeExBot = freeExBuf;
nFreeExBot = nFreeExBuf;
}
freeExBotClock = now;
}
ex->start = -1;
ex->len = 0;
ex->next = freeExBuf;
freeExBuf = ex;
nFreeExBuf++;
freeMutex(ExBufMutex);
if (freeptr) {
if (Debug > 3) message(LOG_DEBUG, "freeExBot %d nfex=%d",
nFreeExBot, nFreeExBuf);
freeExBot = NULL;
nFreeExBot = 0;
while (freeptr) {
ExBuf *p = freeptr;
freeptr = freeptr->next;
free(p);
}
}
}
ExBuf *getExBuf(void) {
ExBuf *ret = NULL;
time_t now;
time(&now);
waitMutex(ExBufMutex);
if (freeExBuf) {
ret = freeExBuf;
freeExBuf = ret->next;
nFreeExBuf--;
if (nFreeExBuf < nFreeExBot) {
nFreeExBot = nFreeExBuf;
freeExBot = freeExBuf;
freeExBotClock = now;
}
}
freeMutex(ExBufMutex);
if (!ret) {
int size = XferBufMax;
do {
ret = malloc(sizeof(ExBuf) + size - BUFMAX);
} while (!ret && XferBufMax > BUFMAX && (XferBufMax /= 2));
if (!ret) {
message(LOG_CRIT, "Out of memory, no ExBuf");
return ret;
}
ret->bufmax = size;
}
ret->next = NULL;
ret->start = 0;
ret->len = 0;
return ret;
}
ExBuf *getExData(Pair *pair, int type, int rmflag) {
ExBuf *ex = pair->d;
ExBuf *prev = NULL;
while (ex) {
int t = *(int*)ex->buf;
if (t == type) {
if (rmflag) {
if (prev) prev->next = ex->next;
else pair->d = ex->next;
}
return ex;
}
prev = ex;
ex = ex->next;
}
return NULL;
}
ExBuf *newExData(Pair *pair, int type) {
ExBuf *ex = getExBuf();
if (!ex) return NULL;
*(int*)ex->buf = type;
ex->next = pair->d;
pair->d = ex;
return ex;
}
/* modify dest if needed */
int modPairDest(Pair *p1, struct sockaddr *dst, socklen_t dstlenmax) {
Pair *p2;
socklen_t dstlen = 0;
int offset = -1; /* offset in load balancing group */
#ifdef USE_SSL
SSL *ssl;
#endif
p2 = p1->pair;
if (p2 == NULL) return -1;
#ifdef USE_SSL
ssl = p2->ssl;
if (ssl) {
SSL_SESSION *sess = SSL_get1_session(ssl);
if (sess) {
unsigned char **match;
if (Debug > 2) {
char str[SSL_MAX_SSL_SESSION_ID_LENGTH * 2 + 1];
int i;
for (i=0; i < sess->session_id_length; i++)
sprintf(&str[i*2], "%02x", sess->session_id[i]);
message(LOG_DEBUG, "%d TCP %d: SSL session ID=%s",
p2->stone->sd, p2->sd, str);
}
match = SSL_SESSION_get_ex_data(sess, MatchIndex);
if (match && p2->stone->ssl_server) {
int lbparm = p2->stone->ssl_server->lbparm;
int lbmod = p2->stone->ssl_server->lbmod;
unsigned char *s;
if (0 <= lbparm && lbparm <= 9) s = match[lbparm];
else s = match[1];
if (!s) s = match[0];
if (lbmod) {
int offset2 = 0;
offset = 0;
while (*s) {
if (offset2 >= 0) {
if ('0' <= *s && *s <= '9') {
offset2 = offset2 * 10 + (*s - '0');
} else {
offset2 = -1;
}
}
offset <<= 6;
offset += (*s & 0x3f);
s++;
}
if (offset2 > 0) offset = offset2;
offset %= lbmod;
if (Debug > 2)
message(LOG_DEBUG, "%d TCP %d: pair %d lb%d=%d",
p1->stone->sd, p1->sd, p2->sd, lbparm, offset);
}
}
SSL_SESSION_free(sess);
}
}
#endif
if (offset < 0 && p1->stone->ndsts > 1) { /* load balancing */
int n = p1->stone->ndsts;
offset = (p1->stone->proto & state_mask) % n;
if (p1->stone->backups) {
int i;
for (i=0; i < n; i++) {
Backup *b = p1->stone->backups[(offset+i) % n];
if (!b || b->bn == 0) { /* no backup or healthy, use it */
offset = (offset+i) % n;
break;
}
if (Debug > 8)
message(LOG_DEBUG,
"%d TCP %d: ofs=%d is unhealthy, skipped",
p1->stone->sd, p1->sd, (offset+i) % n);
}
}
/* round robin */
p1->stone->proto = ((p1->stone->proto & ~state_mask)
| ((offset+1) & state_mask));
}
if (offset >= 0) {
dstlen = p1->stone->dsts[offset]->len;
if (dstlen < dstlenmax)
bcopy(&p1->stone->dsts[offset]->addr, dst, dstlen);
}
if (p1->stone->backups) {
Backup *backup;
if (offset >= 0) backup = p1->stone->backups[offset];
else backup = p1->stone->backups[0];
if (backup) {
backup->used = 2;
if (backup->bn) { /* unhealthy */
dstlen = backup->backup->len;
if (dstlen < dstlenmax)
bcopy(&backup->backup->addr, dst, dstlen);
}
}
}
return dstlen;
}
/* relay UDP */
void message_origin(int pri, Origin *origin) {
struct sockaddr_storage ss;
struct sockaddr *name = (struct sockaddr*)&ss;
socklen_t namelen = sizeof(ss);
SOCKET sd;
Stone *stone;
int i;
char str[LONGSTRMAX+1];
str[LONGSTRMAX] = '\0';
strntime(str, LONGSTRMAX, &origin->clock, -1);
i = strlen(str);
if (ValidSocket(origin->sd)) {
if (getsockname(origin->sd, name, &namelen) < 0) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
if (Debug > 3)
message(LOG_DEBUG, "%d UDP %d: Can't get socket's name err=%d",
origin->stone->sd, origin->sd, errno);
} else {
addrport2str(name, namelen, proto_udp, str+i, LONGSTRMAX-i, 0),
i = strlen(str);
if (i < LONGSTRMAX-2) str[i++] = ' ';
}
}
if (i > LONGSTRMAX) i = LONGSTRMAX;
str[i] = '\0';
stone = origin->stone;
if (stone) sd = stone->sd;
else sd = INVALID_SOCKET;
addrport2str(&origin->from->addr, origin->from->len, proto_udp,
str+i, STRMAX-i, 0);
str[STRMAX] = '\0';
message(pri, "%d UDP%3d:%3d %s", origin->stone->sd, origin->sd, sd, str);
}
void ungetPktBuf(PktBuf *pb) {
if (pb->bufmax < pkt_len_max) {
free(pb); /* never reuse short buffer */
return;
}
waitMutex(PkBufMutex);
pb->next = freePktBuf;
freePktBuf = pb;
nFreePktBuf++;
freeMutex(PkBufMutex);
}
PktBuf *getPktBuf(void) {
PktBuf *ret = NULL;
waitMutex(PkBufMutex);
if (freePktBuf) {
ret = freePktBuf;
freePktBuf = ret->next;
nFreePktBuf--;
}
freeMutex(PkBufMutex);
if (ret && ret->bufmax < pkt_len_max) {
free(ret); /* discard short buffer */
ret = NULL;
}
if (!ret) {
int size = pkt_len_max;
do {
ret = malloc(sizeof(PktBuf) + size - BUFMAX);
} while (!ret && pkt_len_max > BUFMAX && (pkt_len_max /= 2));
if (!ret) {
message(LOG_CRIT, "Out of memory, no ExBuf");
return ret;
}
ret->common = type_pktbuf;
ret->bufmax = size;
}
ret->next = NULL;
ret->origin = NULL;
ret->len = 0;
return ret;
}
void freeOrigin(Origin *origin) {
if (origin->from) free(origin->from);
free(origin);
}
Origin *getOrigins(struct sockaddr *from, socklen_t fromlen, Stone *stone) {
Origin *origin;
Origin *origins = (Origin*)stone->p;
SOCKET sd;
#ifdef USE_EPOLL
struct epoll_event ev;
#endif
for (origin=origins->next; origin != NULL && origin->from;
origin=origin->next) {
if (InvalidSocket(origin->sd)) continue;
if (saComp(&origin->from->addr, from)) {
origin->lock = 1; /* lock origin */
return origin;
}
}
/* can't find origin, so create */
sd = socket(from->sa_family, SOCK_DGRAM, IPPROTO_UDP);
if (InvalidSocket(sd)) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
message(LOG_ERR, "%d UDP: can't create datagram socket err=%d",
stone->sd, errno);
return NULL;
}
if (Debug > 3) {
char addrport[STRMAX+1];
message(LOG_DEBUG, "%d UDP %d: New origin %s",
stone->sd, sd,
addrport2str(from, fromlen, proto_udp, addrport, STRMAX, 0));
}
if (!(stone->proto & proto_block_d)) {
#ifdef WINDOWS
u_long param;
param = 1;
ioctlsocket(sd, FIONBIO, ¶m);
#else
fcntl(sd, F_SETFL, O_NONBLOCK);
#endif
}
origin = malloc(sizeof(Origin));
if (!origin) {
memerr:
message(LOG_CRIT, "%d UDP %d: Out of memory, closing socket",
stone->sd, sd);
return NULL;
}
origin->common = type_origin;
origin->sd = sd;
origin->stone = stone;
origin->from = saDup(from, fromlen);
if (!origin->from) {
free(origin);
goto memerr;
}
origin->lock = 0;
origin->xhost = NULL;
#ifdef USE_EPOLL
ev.events = EPOLLIN;
ev.data.ptr = origin;
if (epoll_ctl(ePollFd, EPOLL_CTL_ADD, sd, &ev) < 0) {
message(LOG_ERR, "%d UDP %d: epoll_ctl ADD err=%d",
stone->sd, sd, errno);
freeOrigin(origin);
return NULL;
}
#else
waitMutex(FdRinMutex);
FdSet(origin->sd, &rin);
freeMutex(FdRinMutex);
#endif
waitMutex(OrigMutex);
origin->next = origins->next; /* insert origin */
origins->next = origin;
freeMutex(OrigMutex);
return origin;
}
PktBuf *recvUDP(Stone *stone) {
struct sockaddr_storage ss;
struct sockaddr *from = (struct sockaddr*)&ss;
socklen_t fromlen = sizeof(ss);
Origin *origin;
SOCKET sd;
int flags = 0;
char *dirstr;
PktBuf *pb = getPktBuf();
pb->type = (stone->common & type_mask);
if (pb->type == type_origin) {
origin = (Origin*)stone;
sd = origin->sd;
stone = origin->stone;
dirstr = "<";
#ifdef MSG_DONTWAIT
if (!(stone->proto & proto_block_d)) flags = MSG_DONTWAIT;
#endif
} else {
origin = NULL;
sd = stone->sd;
dirstr = ">";
#ifdef MSG_DONTWAIT
if (!(stone->proto & proto_block_s)) flags = MSG_DONTWAIT;
#endif
}
#ifdef MSG_TRUNC
flags |= MSG_TRUNC;
#endif
pb->len = recvfrom(sd, pb->buf, pb->bufmax, flags, from, &fromlen);
if (pb->len < 0) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
if (errno == EMSGSIZE) {
if (Debug > 4)
message(LOG_DEBUG, "%d UDP%s%d: recvfrom received larger msg",
stone->sd, dirstr, sd);
pb->len = pb->bufmax + 1;
} else {
message(LOG_ERR, "%d UDP%s%d: recvfrom failed err=%d",
stone->sd, dirstr, sd, errno);
end:
ungetPktBuf(pb);
return NULL;
}
}
if (pb->type == type_stone) { /* outward */
XHosts *xhost = checkXhost(stone->xhosts, from, fromlen);
if (!xhost) {
if (Debug > 4) {
char addrport[STRMAX+1];
addrport2str(from, fromlen, proto_udp, addrport, STRMAX, 0);
addrport[STRMAX] = '\0';
message(LOG_DEBUG, "%d UDP%s%d: recvfrom denied %s",
stone->sd, dirstr, sd, addrport);
}
goto end;
}
origin = getOrigins(from, fromlen, stone);
if (!origin) goto end;
origin->xhost = xhost;
time(&origin->clock);
}
pb->origin = origin;
if (pb->len > pb->bufmax || Debug > 4) {
char addrport[STRMAX+1];
addrport2str(from, fromlen, proto_udp, addrport, STRMAX, 0);
addrport[STRMAX] = '\0';
if (Debug > 4)
message(LOG_DEBUG, "%d UDP%s%d: %d bytes received from %s",
stone->sd, dirstr, origin->sd, pb->len, addrport);
if (pb->len > pb->bufmax) {
message(LOG_NOTICE, "%d UDP%s%d: recvfrom failed: larger packet "
"(%d bytes) arrived from %s",
stone->sd, dirstr, origin->sd, pb->len, addrport);
while (pkt_len_max < pb->len) pkt_len_max <<= 1;
ungetPktBuf(pb);
return NULL; /* drop */
}
}
return pb;
}
int sendUDP(PktBuf *pb) {
Origin *origin = pb->origin;
Stone *stone = origin->stone;
SOCKET sd;
int flags = 0;
struct sockaddr *sa;
socklen_t salen;
char *dirstr;
if (pb->type == type_stone) {
sd = origin->sd;
sa = &stone->dsts[0]->addr;
salen = stone->dsts[0]->len;
dirstr = ">";
#ifdef MSG_DONTWAIT
if (!(stone->proto & proto_block_d)) flags = MSG_DONTWAIT;
#endif
} else {
sd = stone->sd;
sa = &origin->from->addr;
salen = origin->from->len;
dirstr = "<";
#ifdef MSG_DONTWAIT
if (!(stone->proto & proto_block_s)) flags = MSG_DONTWAIT;
#endif
}
if (sendto(sd, pb->buf, pb->len, flags, sa, salen) != pb->len) {
char addrport[STRMAX+1];
addrport2str(sa, salen, proto_udp, addrport, STRMAX, 0);
addrport[STRMAX] = '\0';
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
message(LOG_ERR, "%d UDP%s%d: sendto failed err=%d: to %s",
stone->sd, dirstr, origin->sd, errno, addrport);
return -1;
}
if (Debug > 4) {
char addrport[STRMAX+1];
addrport2str(sa, salen, proto_udp, addrport, STRMAX, 0);
addrport[STRMAX] = '\0';
message(LOG_DEBUG, "%d UDP%s%d: %d bytes sent to %s",
stone->sd, dirstr, origin->sd, pb->len, addrport);
}
if ((origin->xhost->mode & XHostsMode_Dump) > 0) {
char head[STRMAX+1];
snprintf(head, STRMAX, "%d UDP%s%d:", stone->sd, dirstr, origin->sd);
head[STRMAX] = '\0';
packet_dump(head, pb->buf, pb->len, origin->xhost);
}
return pb->len;
}
void docloseUDP(Origin *origin) {
#ifdef USE_EPOLL
SOCKET sd = origin->sd;
#endif
if (Debug > 2) message(LOG_DEBUG, "%d UDP %d: close",
origin->stone->sd, origin->sd);
origin->lock = -1; /* request to close */
#ifdef USE_EPOLL
origin->sd = INVALID_SOCKET;
closesocket(sd);
#else
waitMutex(FdRinMutex);
FD_CLR(origin->sd, &rin);
freeMutex(FdRinMutex);
#endif
}
int scanUDP(
#ifndef USE_EPOLL
fd_set *rop, fd_set *eop,
#endif
Origin *origins
) {
Origin *origin, *prev;
int n = 0;
int all;
time_t now;
time(&now);
if (origins) {
all = 0;
} else {
origins = OriginTop;
all = 1;
}
prev = origins;
for (origin=origins->next; origin != NULL && (all || origin->from != NULL);
prev=origin, origin=origin->next) {
if (all && origin->from == NULL) {
origins = origin;
continue;
}
if (InvalidSocket(origin->sd) || origin->lock > 0) {
Origin *old = origin;
waitMutex(OrigMutex);
if (prev->next == origin) {
origin = prev;
origin->next = old->next; /* remove `old' from list */
if (InvalidSocket(old->sd)) {
freeOrigin(old);
} else {
old->lock = 0;
old->next = origins->next; /* insert old on top */
origins->next = old;
}
}
freeMutex(OrigMutex);
goto next;
}
#ifndef USE_EPOLL
if (origin->lock < 0) {
int isset;
waitMutex(FdRinMutex);
isset = FD_ISSET(origin->sd, &rin);
if (isset) FD_CLR(origin->sd, &rin);
freeMutex(FdRinMutex);
if (!isset) {
closesocket(origin->sd);
origin->sd = INVALID_SOCKET;
}
goto next;
}
if (FD_ISSET(origin->sd, rop) && FD_ISSET(origin->sd, &rin)) {
PktBuf *pb = recvUDP((Stone*)origin);
if (pb) {
sendUDP(pb);
ungetPktBuf(pb);
}
goto next;
}
#endif
if (++n >= OriginMax || now - origin->clock > CONN_TIMEOUT)
docloseUDP(origin);
next:
;
}
return 1;
}
#define UDP_HEAD_LEN 2 /* sizeof(short): UDP packet length */
int recvPairUDP(Pair *pair) {
Stone *stone = pair->stone;
SOCKET sd = pair->sd;
Pair *p;
ExBuf *ex;
ExBuf *t;
int len;
int flags = 0;
struct sockaddr_storage ss;
struct sockaddr *from = (struct sockaddr*)&ss;
socklen_t fromlen = sizeof(ss);
p = pair->pair;
if (p == NULL) { /* no pair, no more read */
message(priority(pair), "%d UDP %d: no pair, closing",
stone->sd, sd);
return -1;
}
ex = p->b; /* bottom */
if (ex->len > 0) { /* not emply */
ex = getExBuf();
if (!ex) return -1; /* out of memory */
if (Debug > 4) message(LOG_DEBUG, "%d UDP %d: get ExBuf nbuf=%d",
stone->sd, p->sd, p->nbuf);
}
ex->start = 0;
#ifdef MSG_DONTWAIT
if (!(stone->proto & proto_block_d)) flags = MSG_DONTWAIT;
#endif
#ifdef MSG_TRUNC
flags |= MSG_TRUNC;
#endif
len = recvfrom(sd, ex->buf + UDP_HEAD_LEN,
ex->bufmax - UDP_HEAD_LEN,
flags, from, &fromlen);
if (len < 0) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
message(LOG_ERR, "%d UDP %d: recvfrom err=%d",
stone->sd, sd, errno);
if (ex != p->b) ungetExBuf(ex);
return -1;
}
time(&pair->clock);
p->clock = pair->clock;
pair->rx += len;
if (Debug > 8)
message(LOG_DEBUG, "%d UDP %d: recvfrom len=%d",
stone->sd, sd, len);
t = getExData(pair, data_peeraddr, 0);
if (t) {
SockAddr *peer = (SockAddr*)(t->buf + DATA_HEAD_LEN);
if (!saComp(&peer->addr, from)) goto unknown;
} else { /* from unknown */
char addrport[STRMAX+1];
unknown:
addrport2str(from, fromlen, proto_udp, addrport, STRMAX, 0);
addrport[STRMAX] = '\0';
message(LOG_ERR, "%d UDP %d: received from unknown %s",
stone->sd, sd, addrport);
if (ex != p->b) ungetExBuf(ex);
return -1;
}
if (ex != p->b) {
p->b->next = ex;
p->b = ex;
p->nbuf++;
}
ex->buf[0] = ((unsigned)len >> 8);
ex->buf[1] = ((unsigned)len % 256);
ex->len += UDP_HEAD_LEN + len;
return ex->len;
}
static int sendPairUDPbuf(Stone *stone, Pair *pair, char *buf, int len) {
int flags = 0;
ExBuf *t;
SockAddr *peer;
int issrc = ((pair->proto & proto_command) == command_source);
SOCKET sd;
Pair *p = pair->pair;
#ifdef MSG_DONTWAIT
if (!(stone->proto & proto_block_d)) flags = MSG_DONTWAIT;
#endif
t = getExData(pair, data_peeraddr, 0);
if (t) {
peer = (SockAddr*)(t->buf + DATA_HEAD_LEN);
} else if (!issrc) {
int lenmax;
int dstlen;
t = newExData(pair, data_peeraddr);
peer = (SockAddr*)(t->buf + DATA_HEAD_LEN);
lenmax = t->bufmax - DATA_HEAD_LEN - SockAddrBaseSize;
peer->len = stone->dsts[0]->len;
bcopy(&stone->dsts[0]->addr, &peer->addr, peer->len);
dstlen = modPairDest(pair, &peer->addr, lenmax);
if (dstlen > 0) peer->len = dstlen; /* dest is modified */
} else {
message(LOG_ERR, "%d UDP<TCP%d: can't happen: no peer",
stone->sd, (p ? p->sd : -1));
return -1;
}
if (issrc) sd = stone->sd;
else sd = pair->sd;
if (sendto(sd, buf, len, flags, &peer->addr, peer->len) != len) {
char addrport[STRMAX+1];
addrport2str(&peer->addr, peer->len, proto_udp, addrport, STRMAX, 0);
addrport[STRMAX] = '\0';
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
if (issrc) {
message(LOG_ERR, "%d UDP<TCP%d: sendto failed err=%d: to %s",
stone->sd, (p ? p->sd : -1), errno, addrport);
} else {
message(LOG_ERR, "%d TCP%d>UDP%d: sendto failed err=%d: to %s",
stone->sd, (p ? p->sd : -1), pair->sd, errno, addrport);
}
return -1; /* error */
}
time(&pair->clock);
if (p) p->clock = pair->clock;
pair->tx += len;
return 0; /* success */
}
int sendPairUDP(Pair *pair) {
Stone *stone = pair->stone;
ExBuf *next = pair->t;
ExBuf *cur = NULL;
ExBuf *ex = NULL; /* dummy init to suppress warnings */
unsigned char *buf = NULL;
int pos = 0;
int len = 0;
int err = 0;
char prefix[STRMAX+1];
if ((pair->proto & proto_command) == command_source) {
Pair *p = pair->pair;
snprintf(prefix, STRMAX, "%d UDP<TCP%d:",
stone->sd, (p ? p->sd : -1));
} else {
Pair *p = pair->pair;
snprintf(prefix, STRMAX, "%d TCP%d>UDP%d:",
stone->sd, (p ? p->sd : -1), pair->sd);
}
while (next) {
ex = next;
next = ex->next;
int add;
if (ex->len <= 0) { /* dispose empty buf */
if (ex != pair->b) ungetExBuf(ex);
continue;
}
if (!cur) {
cur = ex;
buf = (unsigned char*)&cur->buf[cur->start];
pos = cur->len;
len = (buf[0] << 8);
if (pos == 1) {
ExBuf *t;
for (t=cur->next; t; t=t->next) {
if (t->len > 0) {
len += (unsigned)t->buf[t->start];
break;
}
}
if (!t) break; /* must read header */
} else { /* assume UDP_HEAD_LEN == 2 */
len += buf[1];
}
if (Debug > 8)
message(LOG_DEBUG, "%s sendPairUDP len=%d (curbuf=%d)",
prefix, len, cur->len);
len += UDP_HEAD_LEN;
if (len > cur->bufmax) {
message(LOG_ERR, "%s sendPairUDP packet too large len=%d",
prefix, len);
err = -1;
} else if (len > cur->bufmax - cur->start) {
if (Debug > 6)
message(LOG_DEBUG, "%s sendPairUDP len=%d "
"is larger than (bufmax-start=%d)=%d, move",
prefix, len,
cur->start, cur->bufmax - cur->start);
bcopy(cur->buf+cur->start, cur->buf, cur->len);
buf = (unsigned char*)cur->buf;
cur->start = 0;
}
if (len < cur->len) { /* cur contains next packet */
cur->start += len;
cur->len -= len;
goto complete;
} else if (len == cur->len) {
cur->len = cur->bufmax; /* mark not to be used */
cur->start = 0;
goto complete;
} else {
cur->len = cur->bufmax; /* mark not to be used */
cur->start = 0;
}
continue;
}
add = len - pos;
if (ex->len > add) { /* ex contains next packet */
ex->start += add;
ex->len -= add;
} else { /* use entire buf */
add = ex->len;
ex->len = ex->bufmax; /* mark not to be used */
ex->start = 0;
}
if (!err) bcopy(ex->buf+ex->start, buf+pos, add);
pos += add;
if (ex != pair->b) ungetExBuf(ex);
if (pos >= len) { /* complete the packet */
complete:
if (!err) {
err = sendPairUDPbuf(stone, pair, (char*)(buf+UDP_HEAD_LEN),
len-UDP_HEAD_LEN);
if (!err) {
if ((pair->xhost->mode & XHostsMode_Dump) > 0
|| ((pair->proto & proto_first_w) && Debug > 3))
message_buf(pair, len, "tu");
}
}
if (cur != pair->b) ungetExBuf(cur);
cur = NULL;
}
}
if (ex == pair->b) {
if (ex->len == ex->bufmax) ex->len = 0;
pair->t = ex;
} else {
if (0 < ex->len && ex->len < ex->bufmax) {
pair->t = ex;
} else {
pair->t = ex->next;
ungetExBuf(ex);
}
}
return err;
}
/* relay TCP */
void message_pair(int pri, Pair *pair) {
struct sockaddr_storage ss;
struct sockaddr *name = (struct sockaddr*)&ss;
socklen_t namelen = sizeof(ss);
SOCKET sd, psd;
Pair *p;
int i;
char str[LONGSTRMAX+1];
str[LONGSTRMAX] = '\0';
strntime(str, LONGSTRMAX, &pair->clock, -1);
i = strlen(str);
sd = pair->sd;
if (ValidSocket(sd)) {
if (getsockname(sd, name, &namelen) < 0) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
if (Debug > 3)
message(LOG_DEBUG, "%d TCP %d: Can't get socket's name err=%d",
pair->stone->sd, sd, errno);
} else {
addrport2str(name, namelen, 0, str+i, LONGSTRMAX-i, 0);
i = strlen(str);
if (i < LONGSTRMAX-2) str[i++] = ' ';
}
namelen = sizeof(ss);
if (getpeername(sd, name, &namelen) < 0) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
if (Debug > 3)
message(LOG_DEBUG, "%d TCP %d: Can't get peer's name err=%d",
pair->stone->sd, sd, errno);
} else {
addrport2str(name, namelen, 0, str+i, LONGSTRMAX-i, 0);
i += strlen(str+i);
}
}
if (i > LONGSTRMAX) i = LONGSTRMAX;
str[i] = '\0';
p = pair->pair;
if (p) psd = p->sd;
else psd = INVALID_SOCKET;
message(pri, "%d TCP%3d:%3d %08x %d %s tx:%d rx:%d lp:%d",
pair->stone->sd, sd, psd, pair->proto, pair->count, str,
pair->tx, pair->rx, pair->loop);
}
#ifdef USE_SSL
static void printSSLinfo(int pri, SSL *ssl) {
X509 *peer;
char *p = (char *)SSL_get_cipher(ssl);
if (p == NULL) p = "<NULL>";
message(pri, "[SSL cipher=%s]", p);
peer = SSL_get_peer_certificate(ssl);
if (peer) {
char buf[LONGSTRMAX+1];
ASN1_INTEGER *n = X509_get_serialNumber(peer);
if (n) message(pri, "[SSL serial=%lx]", ASN1_INTEGER_get(n));
buf[LONGSTRMAX] = '\0';
if (X509_NAME_oneline(X509_get_subject_name(peer), buf, LONGSTRMAX))
message(pri, "[SSL subject=%s]", buf);
if (X509_NAME_oneline(X509_get_issuer_name(peer), buf, LONGSTRMAX))
message(pri, "[SSL issuer=%s]", buf);
X509_free(peer);
}
}
int doSSL_accept(Pair *pair) {
int err, ret;
SOCKET sd;
SSL *ssl;
if (!pair) return -1;
sd = pair->sd;
if (InvalidSocket(sd)) return -1;
ssl = pair->ssl;
if (!ssl) {
ssl = SSL_new(pair->stone->ssl_server->ctx);
if (!ssl) {
message(LOG_ERR, "%d TCP %d: SSL_new failed", pair->stone->sd, sd);
return -1;
}
SSL_set_ex_data(ssl, PairIndex, pair);
SSL_set_fd(ssl, sd);
pair->ssl = ssl;
}
pair->ssl_flag &= ~(sf_ab_on_r | sf_ab_on_w);
pair->proto |= proto_dirty;
ret = SSL_accept(ssl);
if (Debug > 7)
message(LOG_DEBUG, "%d TCP %d: SSL_accept ret=%d, state=%x, "
"finished=%x, in_init=%x/%x", pair->stone->sd,
sd, ret, SSL_state(ssl), SSL_is_init_finished(ssl),
SSL_in_init(ssl), SSL_in_accept_init(ssl));
if (ret > 0) { /* success */
if (SSL_in_accept_init(ssl)) {
if (pair->stone->ssl_server->verbose) {
message(LOG_NOTICE, "%d TCP %d: SSL_accept unexpected EOF",
pair->stone->sd, sd);
message_pair(LOG_NOTICE, pair);
}
return -1; /* unexpected EOF */
}
/* src & pair is connected */
pair->proto |= (proto_connect | proto_dirty);
if (Debug > 3) {
SSL_CTX *ctx = pair->stone->ssl_server->ctx;
message(LOG_DEBUG, "%d TCP %d: SSL_accept succeeded "
"sess=%ld accept=%ld hits=%ld", pair->stone->sd, sd,
SSL_CTX_sess_number(ctx), SSL_CTX_sess_accept(ctx),
SSL_CTX_sess_hits(ctx));
}
if (pair->stone->ssl_server->verbose) printSSLinfo(LOG_DEBUG, ssl);
return ret;
}
err = SSL_get_error(ssl, ret);
if (err == SSL_ERROR_WANT_READ) {
pair->ssl_flag |= sf_ab_on_r;
ret = 0;
} else if (err == SSL_ERROR_WANT_WRITE) {
pair->ssl_flag |= sf_ab_on_w;
ret = 0;
} else if (err == SSL_ERROR_SYSCALL) {
unsigned long e = ERR_get_error();
if (e == 0) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
if (errno == EINTR || errno == EAGAIN) {
pair->ssl_flag |= (sf_ab_on_r | sf_ab_on_r);
if (Debug > 8)
message(LOG_DEBUG, "%d TCP %d: SSL_accept "
"interrupted sf=%x",
pair->stone->sd, sd, pair->ssl_flag);
return 0;
}
if (errno == 0) {
if (Debug > 0)
message(LOG_DEBUG, "%d TCP %d: SSL_accept "
"shutdowned by peer sf=%x errno=%d",
pair->stone->sd, sd,
pair->ssl_flag, errno);
return -1; /* shutdowned */
}
message(priority(pair), "%d TCP %d: SSL_accept "
"I/O error sf=%x errno=%d", pair->stone->sd, sd,
pair->ssl_flag, errno);
} else {
message(priority(pair), "%d TCP %d: SSL_accept sf=%x %s",
pair->stone->sd, sd, pair->ssl_flag, ERR_error_string(e, NULL));
}
return ret;
} else if (err == SSL_ERROR_SSL) {
unsigned long e = ERR_get_error();
message(priority(pair), "%d TCP %d: SSL_accept lib %s",
pair->stone->sd, sd, ERR_error_string(e, NULL));
return -1; /* error */
}
if (Debug > 4)
message(LOG_DEBUG, "%d TCP %d: SSL_accept interrupted sf=%x err=%d",
pair->stone->sd, sd, pair->ssl_flag, err);
return ret;
}
int doSSL_connect(Pair *pair) {
int ret;
int err;
SOCKET sd;
SSL *ssl;
if (!pair) return -1;
sd = pair->sd;
if (InvalidSocket(sd)) return -1;
ssl = pair->ssl;
if (!ssl) {
ssl = SSL_new(pair->stone->ssl_client->ctx);
if (!ssl) {
message(LOG_ERR, "%d TCP %d: SSL_new failed", pair->stone->sd, sd);
return -1;
}
SSL_set_ex_data(ssl, PairIndex, pair);
SSL_set_fd(ssl, sd);
pair->ssl = ssl;
}
#ifndef OPENSSL_NO_TLSEXT
if (pair->stone->ssl_client->sslparm & sslparm_sni) {
if (!SSL_set_tlsext_host_name(ssl, pair->stone->ssl_client->name)) {
message(LOG_ERR, "%d TCP %d: Can't set TLS servername: %s",
pair->stone->sd, sd, pair->stone->ssl_client->name);
}
}
#endif
pair->ssl_flag &= ~(sf_cb_on_r | sf_cb_on_w);
pair->proto |= proto_dirty;
ret = SSL_connect(ssl);
if (ret > 0) { /* success */
Pair *p = pair->pair;
/* pair & dst is connected */
pair->proto |= (proto_connect | proto_dirty);
if (p) p->proto |= proto_dirty; /* src */
if (Debug > 3) {
SSL_CTX *ctx = pair->stone->ssl_client->ctx;
message(LOG_DEBUG, "%d TCP %d: SSL_connect succeeded "
"sess=%ld connect=%ld hits=%ld", pair->stone->sd, sd,
SSL_CTX_sess_number(ctx), SSL_CTX_sess_connect(ctx),
SSL_CTX_sess_hits(ctx));
message_pair(LOG_DEBUG, pair);
}
if (pair->stone->ssl_client->verbose) printSSLinfo(LOG_DEBUG, ssl);
return ret;
}
err = SSL_get_error(ssl, ret);
if (err == SSL_ERROR_WANT_READ) {
pair->ssl_flag |= sf_cb_on_r;
ret = 0;
} else if (err == SSL_ERROR_WANT_WRITE) {
pair->ssl_flag |= sf_cb_on_w;
ret = 0;
} else if (err == SSL_ERROR_SYSCALL) {
unsigned long e = ERR_get_error();
if (e == 0) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
if (errno == 0) {
return 1; /* success ? */
} else if (errno == EINTR || errno == EAGAIN) {
pair->ssl_flag |= (sf_cb_on_r | sf_cb_on_r);
if (Debug > 8)
message(LOG_DEBUG, "%d TCP %d: SSL_connect "
"interrupted sf=%x",
pair->stone->sd, sd, pair->ssl_flag);
return 0;
}
message(priority(pair), "%d TCP %d: SSL_connect "
"I/O error sf=%x errno=%d", pair->stone->sd, sd,
pair->ssl_flag, errno);
} else {
message(priority(pair), "%d TCP %d: SSL_connect sf=%x %s",
pair->stone->sd, sd, pair->ssl_flag, ERR_error_string(e, NULL));
}
return ret;
}
if (Debug > 4)
message(LOG_DEBUG, "%d TCP %d: SSL_connect interrupted sf=%x err=%d",
pair->stone->sd, sd, pair->ssl_flag, err);
return ret;
}
int doSSL_shutdown(Pair *pair, int how) {
int ret;
int err;
int i;
SOCKET sd;
SSL *ssl;
StoneSSL *ss;
if (!pair) return -1;
sd = pair->sd;
if (InvalidSocket(sd)) return -1;
ssl = pair->ssl;
if (!ssl) return -1;
if (how >= 0) pair->ssl_flag = (how & sf_mask);
else pair->ssl_flag = sf_mask;
if ((pair->proto & proto_command) == command_source) {
ss = pair->stone->ssl_server;
} else {
ss = pair->stone->ssl_client;
}
if (ss->shutdown_mode) {
int state = SSL_get_shutdown(ssl);
SSL_set_shutdown(ssl, (state | ss->shutdown_mode));
}
for (i=0; i < 4; i++) {
ret = SSL_shutdown(ssl);
if (ret != 0) break;
}
if (ret == 0 && ss->shutdown_mode == 0) {
if (Debug > 4)
message(LOG_DEBUG, "%d TCP %d: SSL_shutdown ret=%d sf=%x, "
"so don't wait peer's notify",
pair->stone->sd, sd, ret, pair->ssl_flag);
SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN);
ret = SSL_shutdown(ssl);
}
if (ret < 0) {
err = SSL_get_error(ssl, ret);
if (Debug > 4)
message(LOG_DEBUG, "%d TCP %d: SSL_shutdown ret=%d err=%d sf=%x",
pair->stone->sd, sd, ret, err, pair->ssl_flag);
if (err == SSL_ERROR_WANT_READ) {
pair->ssl_flag |= sf_sb_on_r;
} else if (err == SSL_ERROR_WANT_WRITE) {
pair->ssl_flag |= sf_sb_on_w;
} else if (err == SSL_ERROR_SYSCALL) {
unsigned long e = ERR_get_error();
if (e == 0) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
if (errno == 0) {
ret = 1; /* success ? */
} else if (errno == EINTR || errno == EAGAIN) {
pair->ssl_flag |= (sf_sb_on_r | sf_sb_on_r);
if (Debug > 8)
message(LOG_DEBUG, "%d TCP %d: SSL_shutdown "
"interrupted sf=%x", pair->stone->sd, sd,
pair->ssl_flag);
} else {
message(priority(pair), "%d TCP %d: SSL_shutdown "
"I/O error sf=%x errno=%d", pair->stone->sd, sd,
pair->ssl_flag, errno);
}
} else {
message(priority(pair), "%d TCP %d: SSL_shutdown sf=%x %s",
pair->stone->sd, sd,
pair->ssl_flag, ERR_error_string(e, NULL));
}
} else {
if (Debug > 4)
message(LOG_DEBUG,
"%d TCP %d: SSL_shutdown interrupted sf=%x err=%d",
pair->stone->sd, sd, pair->ssl_flag, err);
}
} else if (ret == 0) {
if (Debug > 4)
message(priority(pair), "%d TCP %d: SSL_shutdown error "
"ret=%d sf=%x, reset connection",
pair->stone->sd, sd, ret, pair->ssl_flag);
shutdown(sd, 2);
ret = 0;
}
if (ret > 0) { /* success */
if (Debug > 4)
message(LOG_DEBUG, "%d TCP %d: SSL_shutdown sf=%x",
pair->stone->sd, sd, pair->ssl_flag);
if ((pair->ssl_flag & sf_mask) != sf_mask)
shutdown(sd, (pair->ssl_flag & sf_mask));
}
return ret;
}
#endif /* USE_SSL */
int doshutdown(Pair *pair, int how) {
#ifdef USE_SSL
SSL *ssl;
#endif
if (!pair) return -1;
#ifdef USE_SSL
ssl = pair->ssl;
if (ssl) return doSSL_shutdown(pair, how);
else {
#endif
if (Debug > 4)
message(LOG_DEBUG, "%d TCP %d: shutdown how=%d",
pair->stone->sd, pair->sd, how);
return shutdown(pair->sd, how);
#ifdef USE_SSL
}
#endif
}
Pair *newPair(void) {
Pair *pair = NULL;
waitMutex(FPairMutex);
if (freePairs) {
pair = freePairs;
freePairs = pair->next;
nFreePairs--;
}
freeMutex(FPairMutex);
if (!pair) pair = malloc(sizeof(Pair));
if (pair) {
pair->common = type_pair;
pair->t = getExBuf();
if (!pair->t) {
free(pair);
return NULL;
}
pair->nbuf = 1;
pair->sd = INVALID_SOCKET;
pair->stone = NULL;
pair->proto = 0;
pair->xhost = NULL;
pair->timeout = PairTimeOut;
pair->count = 0;
pair->b = pair->t;
pair->d = NULL;
pair->log = NULL;
pair->tx = 0;
pair->rx = 0;
pair->loop = 0;
time(&pair->clock);
pair->pair = NULL;
pair->next = NULL;
pair->prev = NULL;
#ifdef USE_SSL
pair->ssl = NULL;
pair->ssl_flag = 0;
#endif
}
return pair;
}
void freePair(Pair *pair) {
SOCKET sd;
TimeLog *log;
#ifdef USE_SSL
SSL *ssl;
#endif
ExBuf *ex;
if (!pair) return;
sd = pair->sd;
pair->sd = INVALID_SOCKET;
if (Debug > 8) message(LOG_DEBUG, "%d TCP %d: freePair",
pair->stone->sd, sd);
ex = pair->d;
pair->d = NULL;
while (ex) {
ExBuf *f = ex;
ex = f->next;
f->next = NULL;
ungetExBuf(f);
}
log = pair->log;
if (log) {
pair->log = NULL;
free(log);
}
#ifdef USE_SSL
ssl = pair->ssl;
if (ssl) {
SSL_CTX *ctx = NULL;
int state;
pair->ssl = NULL;
state = SSL_get_shutdown(ssl);
if (!(state & SSL_RECEIVED_SHUTDOWN) && Debug > 2) {
message(LOG_DEBUG, "%d TCP %d: SSL close notify was not received",
pair->stone->sd, sd);
}
if (!(state & SSL_SENT_SHUTDOWN) && Debug > 2) {
message(LOG_DEBUG, "%d TCP %d: SSL close notify was not sent",
pair->stone->sd, sd);
SSL_set_shutdown(ssl, (state | SSL_SENT_SHUTDOWN));
}
SSL_free(ssl);
if (pair->stone->proto & proto_ssl_s) {
ctx = pair->stone->ssl_server->ctx;
}
if (ctx) SSL_CTX_flush_sessions(ctx, pair->clock);
}
#endif
pair->b = NULL;
ex = pair->t;
pair->t = NULL;
while (ex) {
ExBuf *f = ex;
ex = f->next;
f->next = NULL;
pair->nbuf--;
if (Debug > 4) message(LOG_DEBUG, "%d TCP %d: freePair "
"unget ExBuf nbuf=%d nfex=%d",
pair->stone->sd, sd, pair->nbuf, nFreeExBuf);
ungetExBuf(f);
}
if (ValidSocket(sd)) {
#ifdef USE_EPOLL
if (Debug > 6)
message(LOG_DEBUG, "%d TCP %d: freePair "
"epoll_ctl %d DEL %x",
pair->stone->sd, sd, ePollFd, (int)pair);
epoll_ctl(ePollFd, EPOLL_CTL_DEL, sd, NULL);
#endif
closesocket(sd);
}
waitMutex(FPairMutex);
if (pair->clock == 0) {
freeMutex(FPairMutex);
message(LOG_ERR, "freePair duplication. can't happen, ignore");
return;
}
pair->clock = 0;
pair->next = freePairs;
freePairs = pair;
nFreePairs++;
freeMutex(FPairMutex);
}
void insertPairs(Pair *p1) {
Pair *p2 = p1->pair;
Stone *stone = p1->stone;
p1->next = p2; /* link pair each other */
p2->prev = p1;
waitMutex(PairMutex);
p2->next = stone->pairs->next; /* insert pair */
if (stone->pairs->next != NULL) stone->pairs->next->prev = p2;
p1->prev = stone->pairs;
stone->pairs->next = p1;
freeMutex(PairMutex);
if (Debug > 4) {
message(LOG_DEBUG, "%d TCP %d: pair %d inserted",
stone->sd, p1->sd, p2->sd);
message_pair(LOG_DEBUG, p1);
}
}
void message_time_log(Pair *pair) {
TimeLog *log = pair->log;
if (log && log->clock) {
#ifdef THREAD_UNSAFE
struct tm *t = localtime(&log->clock);
#else
struct tm tm;
struct tm *t = localtime_r(&log->clock, &tm);
#endif
time_t now;
time(&now);
message(log->pri, "%02d:%02d:%02d %d %s",
t->tm_hour, t->tm_min, t->tm_sec,
(int)(now - log->clock), log->str);
log->clock = 0;
}
}
/* after connect(2) successfully completed */
void connected(Pair *pair) {
Pair *p = pair->pair;
if (Debug > 2)
message(LOG_DEBUG, "%d TCP %d: established to %d %08x %08x",
pair->stone->sd, p->sd, pair->sd, p->proto, pair->proto);
time(&lastEstablished);
/* now successfully connected */
#ifdef USE_SSL
if (pair->stone->proto & proto_ssl_d) {
if (doSSL_connect(pair) < 0) {
/* SSL_connect fails, shutdown pairs */
if (!(p->proto & proto_shutdown))
if (doshutdown(p, 2) >= 0)
p->proto |= (proto_shutdown | proto_dirty);
p->proto |= (proto_close | proto_dirty);
pair->proto |= (proto_close | proto_dirty);
return;
}
} else
#endif /* pair & dst is connected */
{
pair->proto |= (proto_connect | proto_dirty);
p->proto |= proto_dirty; /* src */
}
/*
SSL connection may not be established yet,
but we can prepare for read/write
*/
if (pair->t->len > 0) {
if (Debug > 8)
message(LOG_DEBUG, "%d TCP %d: waiting %d bytes to write",
pair->stone->sd, pair->sd, pair->t->len);
if (!(pair->proto & proto_shutdown))
pair->proto |= (proto_select_w | proto_dirty);
} else if (!(pair->proto & proto_ohttp_d)) {
if (Debug > 8)
message(LOG_DEBUG, "%d TCP %d: request to read 1st",
pair->stone->sd, p->sd);
if (!(p->proto & proto_eof))
p->proto |= (proto_select_r | proto_dirty);
}
if (!(p->proto & proto_ohttp_s)) {
if (p->t->len > 0) {
if (Debug > 8)
message(LOG_DEBUG, "%d TCP %d: waiting %d bytes to write",
pair->stone->sd, p->sd, p->t->len);
if (!(p->proto & proto_shutdown))
p->proto |= (proto_select_w | proto_dirty);
} else {
if (Debug > 8)
message(LOG_DEBUG, "%d TCP %d: request to read",
pair->stone->sd, pair->sd);
if (!(pair->proto & proto_eof))
pair->proto |= (proto_select_r | proto_dirty);
}
}
}
void message_conn(int pri, Conn *conn) {
SOCKET sd = INVALID_SOCKET;
Pair *p1, *p2;
int proto = 0;
int i = 0;
char str[LONGSTRMAX+1];
str[LONGSTRMAX] = '\0';
p1 = conn->pair;
if (p1) {
p2 = p1->pair;
strntime(str, LONGSTRMAX, &p1->clock, -1);
i = strlen(str);
proto = p1->proto;
if (p2) sd = p2->sd;
}
addrport2str(&conn->dst->addr, conn->dst->len, (proto & proto_pair_d),
str+i, LONGSTRMAX-i, 0);
i = strlen(str);
if (i > LONGSTRMAX) i = LONGSTRMAX;
str[i] = '\0';
message(pri, "Conn %d: %08x %s", sd, proto, str);
}
int doconnect(Pair *p1, struct sockaddr *sa, socklen_t salen) {
struct sockaddr_storage ss;
struct sockaddr *dst = (struct sockaddr*)&ss; /* destination */
socklen_t dstlen;
int ret;
Pair *p2;
time_t clock;
char addrport[STRMAX+1];
#ifdef WINDOWS
u_long param;
#endif
if (p1 == NULL) return -1;
p2 = p1->pair;
if (p2 == NULL) return -1;
if (!(p2->proto & proto_connect)) return 0;
bcopy(sa, dst, salen);
dstlen = salen;
time(&clock);
if (Debug > 8) message(LOG_DEBUG, "%d TCP %d: doconnect",
p1->stone->sd, p1->sd);
ret = modPairDest(p1, dst, sizeof(ss));
if (ret > 0) dstlen = ret; /* dest is modified */
/*
now destination is determined, engage
*/
if (!(p1->stone->proto & proto_block_d)) {
#ifdef WINDOWS
param = 1;
ioctlsocket(p1->sd, FIONBIO, ¶m);
#else
fcntl(p1->sd, F_SETFL, O_NONBLOCK);
#endif
}
addrport[0] = '\0';
if (Debug > 2) {
addrport2strOnce(dst, dstlen, (p1->proto & proto_pair_d),
addrport, STRMAX, 0);
message(LOG_DEBUG, "%d TCP %d: connecting to TCP %d %s",
p1->stone->sd, p2->sd, p1->sd, addrport);
}
if (p1->proto & proto_dgram) {
ret = 0; /* do nothing */
} else {
ret = connect(p1->sd, dst, dstlen);
}
if (ret < 0) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
if (errno == EINPROGRESS) {
p1->proto |= (proto_conninprog | proto_dirty);
if (Debug > 3)
message(LOG_DEBUG, "%d TCP %d: connection in progress",
p1->stone->sd, p1->sd);
return 1;
} else if (errno == EINTR) {
if (Debug > 4)
message(LOG_DEBUG, "%d TCP %d: connect interrupted",
p1->stone->sd, p1->sd);
if (clock - p1->clock < CONN_TIMEOUT) return 0;
addrport2strOnce(dst, dstlen, (p1->proto & proto_pair_d),
addrport, STRMAX, 0);
message(priority(p2), "%d TCP %d: connect timeout to %s",
p2->stone->sd, p2->sd, addrport);
} else if (errno == EISCONN || errno == EADDRINUSE
#ifdef EALREADY
|| errno == EALREADY
#endif
) {
if (Debug > 4) { /* SunOS's bug ? */
message(LOG_DEBUG, "%d TCP %d: connect bug err=%d",
p1->stone->sd, p1->sd, errno);
message_pair(LOG_DEBUG, p1);
}
} else {
addrport2strOnce(dst, dstlen, (p1->proto & proto_pair_d),
addrport, STRMAX, 0);
message(priority(p1),
"%d TCP %d: can't connect err=%d: to %s",
p1->stone->sd, p1->sd, errno, addrport);
}
}
if (ret < 0 /* fail to connect */
|| (p1->proto & proto_close)
|| (p2->proto & proto_close)) {
if (!(p2->proto & proto_shutdown))
if (doshutdown(p2, 2) >= 0)
p2->proto |= (proto_shutdown | proto_dirty);
p2->proto |= (proto_close | proto_dirty);
p1->proto |= (proto_close | proto_dirty);
return -1;
}
connected(p1);
return 1;
}
void freeConn(Conn *conn) {
if (conn->dst) free(conn->dst);
free(conn);
}
int reqconn(Pair *pair, /* request pair to connect to destination */
struct sockaddr *dst, socklen_t dstlen) { /* connect to */
int ret;
Conn *conn;
Pair *p = pair->pair;
if ((pair->proto & proto_command) == command_proxy
|| (pair->proto & proto_command) == command_health
|| (pair->proto & proto_command) == command_identd) {
pair->proto |= proto_noconnect;
if (p && !(p->proto & (proto_eof | proto_close))) {
/* must read request header */
p->proto |= (proto_select_r | proto_dirty);
}
return 0;
}
ret = doconnect(pair, dst, dstlen);
if (ret < 0) return -1; /* error */
if (ret > 0) return ret; /* connected or connection in progress */
conn = malloc(sizeof(Conn));
if (!conn) {
memerr:
message(LOG_CRIT, "%d TCP %d: out of memory",
(p ? p->stone->sd : -1), (p ? p->sd : -1));
return -1;
}
time(&pair->clock);
p->clock = pair->clock;
pair->count += REF_UNIT; /* request to connect */
conn->pair = pair;
conn->dst = saDup(dst, dstlen);
if (!conn->dst) {
free(conn);
goto memerr;
}
conn->lock = 0;
waitMutex(ConnMutex);
conn->next = conns.next;
conns.next = conn;
freeMutex(ConnMutex);
return 0;
}
void asyncConn(Conn *conn) {
Pair *p1, *p2;
ASYNC_BEGIN;
if (Debug > 8) message(LOG_DEBUG, "asyncConn");
p1 = conn->pair;
if (p1 == NULL ||
doconnect(p1, &conn->dst->addr, conn->dst->len) != 0) {
if (p1) p1->count -= REF_UNIT; /* no more request to connect */
conn->pair = NULL;
conn->lock = -1;
} else {
conn->lock = 0;
}
if (p1) {
#ifdef USE_EPOLL
if (!(p1->proto & (proto_noconnect | proto_close))) {
struct epoll_event ev;
ev.events = EPOLLONESHOT;
ev.data.ptr = p1;
if (Debug > 6)
message(LOG_DEBUG, "%d TCP %d: asyncConn1 "
"epoll_ctl %d ADD %x",
p1->stone->sd, p1->sd, ePollFd, (int)ev.data.ptr);
if (epoll_ctl(ePollFd, EPOLL_CTL_ADD, p1->sd, &ev) < 0) {
message(LOG_ERR, "%d TCP %d: asyncConn1 "
"epoll_ctl %d ADD err=%d",
p1->stone->sd, p1->sd, ePollFd, errno);
}
}
/* must be added to ePollFd before unset proto_thread */
#endif
p1->proto &= ~proto_thread;
p1->proto |= proto_dirty;
p2 = p1->pair;
} else {
p2 = NULL;
}
if (p2) {
#ifdef USE_EPOLL
if (!(p2->proto & proto_close)) {
struct epoll_event ev;
ev.events = EPOLLONESHOT;
ev.data.ptr = p2;
if (Debug > 6)
message(LOG_DEBUG, "%d TCP %d: asyncConn2 "
"epoll_ctl %d ADD %x",
p2->stone->sd, p2->sd, ePollFd, (int)ev.data.ptr);
if (epoll_ctl(ePollFd, EPOLL_CTL_ADD, p2->sd, &ev) < 0) {
message(LOG_ERR, "%d TCP %d: asyncConn2 "
"epoll_ctl %d ADD err=%d",
p2->stone->sd, p2->sd, ePollFd, errno);
}
}
/* must be added to ePollFd before unset proto_thread */
#endif
p2->proto &= ~proto_thread;
p2->proto |= proto_dirty;
}
ASYNC_END;
}
/* scan conn request */
int scanConns(void) {
Conn *conn, *pconn;
Pair *p1, *p2;
if (Debug > 8) message(LOG_DEBUG, "scanConns");
pconn = &conns;
for (conn=conns.next; conn != NULL; conn=conn->next) {
p1 = conn->pair;
if (p1) p2 = p1->pair;
if (p1 && !(p1->proto & proto_close) &&
p2 && !(p2->proto & proto_close)) {
if ((p2->proto & proto_connect) && conn->lock == 0 &&
!(p1->proto & proto_thread) &&
!(p2->proto & proto_thread)) {
conn->lock = 1; /* lock conn */
if (Debug > 4) message_conn(LOG_DEBUG, conn);
p1->proto |= (proto_thread | proto_dirty);
p2->proto |= (proto_thread | proto_dirty);
ASYNC(asyncConn, conn);
}
} else {
waitMutex(ConnMutex);
if (pconn->next == conn && conn->lock <= 0) {
pconn->next = conn->next; /* remove conn */
freeConn(conn);
conn = pconn;
}
freeMutex(ConnMutex);
}
pconn = conn;
}
return 1;
}
Pair *acceptPair(Stone *stone) {
struct sockaddr_storage ss;
struct sockaddr *from = (struct sockaddr*)&ss;
socklen_t fromlen = sizeof(ss);
Pair *pair;
SOCKET nsd = accept(stone->sd, from, &fromlen);
if (InvalidSocket(nsd)) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
if (errno == EINTR) {
if (Debug > 4)
message(LOG_DEBUG, "stone %d: accept interrupted", stone->sd);
return NULL;
} else if (errno == EAGAIN) {
if (Debug > 4)
message(LOG_DEBUG, "stone %d: accept no connection",
stone->sd);
return NULL;
}
#ifndef NO_FORK
else if (errno == EBADF && Debug < 5) {
return NULL;
}
#endif
message(LOG_ERR, "stone %d: accept error err=%d", stone->sd, errno);
return NULL;
}
pair = newPair();
if (!pair) {
message(LOG_CRIT, "stone %d: out of memory, closing TCP %d",
stone->sd, nsd);
closesocket(nsd);
freePair(pair);
return NULL;
}
bcopy(&fromlen, pair->t->buf, sizeof(fromlen)); /* save to ExBuf */
bcopy(from, pair->t->buf + sizeof(fromlen), fromlen);
pair->sd = nsd;
pair->stone = stone;
pair->proto = ((stone->proto & proto_pair_s & ~proto_command) |
proto_first_r | proto_first_w | command_source);
pair->timeout = stone->timeout;
return pair;
}
int getident(char *str, struct sockaddr *sa, socklen_t salen,
int cport, struct sockaddr *csa, socklen_t csalen) {
/* (size of str) >= STRMAX+1 */
SOCKET sd;
struct sockaddr_storage ss;
struct sockaddr *peer = (struct sockaddr*)&ss;
socklen_t peerlen = sizeof(ss);
int sport = getport(sa);
char buf[LONGSTRMAX+1];
char c;
int len;
int ret;
char addr[STRMAX+1];
#ifdef WINDOWS
u_long param;
#endif
time_t start, now;
#ifdef USE_EPOLL
int epfd = INVALID_SOCKET;
struct epoll_event ev;
struct epoll_event evs[1];
#endif
time(&start);
bcopy(sa, peer, salen);
peerlen = salen;
if (str) {
str[0] = '\0';
}
sd = socket(peer->sa_family, SOCK_STREAM, IPPROTO_TCP);
if (InvalidSocket(sd)) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
if (Debug > 0)
message(LOG_DEBUG, "ident: can't create socket err=%d", errno);
return 0;
}
saPort(csa, 0);
if (bind(sd, csa, csalen) < 0) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
if (Debug > 0)
message(LOG_DEBUG, "ident: can't bind socket err=%d", errno);
/* hope default source address is adequate */
}
saPort(peer, 113); /* ident protocol */
addr2str(peer, peerlen, addr, STRMAX, 0);
addr[STRMAX] = '\0';
#ifdef WINDOWS
param = 1;
ioctlsocket(sd, FIONBIO, ¶m);
#else
fcntl(sd, F_SETFL, O_NONBLOCK);
#endif
#ifdef USE_EPOLL
epfd = epoll_create(BACKLOG_MAX);
if (epfd < 0) {
message(LOG_ERR, "ident: can't create epoll err=%d", errno);
epfd = INVALID_SOCKET;
goto noconnect; /* I can't tell the master is healthy or not */
}
ev.events = (EPOLLOUT | EPOLLONESHOT);
if (epoll_ctl(epfd, EPOLL_CTL_ADD, sd, &ev) < 0) {
message(LOG_ERR, "ident: epoll_ctl ADD err=%d", errno);
goto noconnect;
}
#endif
ret = connect(sd, peer, peerlen);
if (ret < 0) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
if (errno == EINPROGRESS) {
#ifndef USE_EPOLL
fd_set wout;
struct timeval tv;
#endif
do {
time(&now);
if (now - start >= CONN_TIMEOUT) {
if (Debug > 0)
message(LOG_DEBUG, "ident: connect to %s, timeout",
addr);
goto noconnect;
}
#ifndef USE_EPOLL
tv.tv_sec = 1;
tv.tv_usec = 0;
FD_ZERO(&wout);
FdSet(sd, &wout);
#endif
} while (
#ifdef USE_EPOLL
epoll_wait(epfd, evs, 1, 1000) == 0
#else
select(FD_SETSIZE, NULL, &wout, NULL, &tv) == 0
#endif
);
} else {
if (Debug > 0)
message(LOG_DEBUG, "ident: can't connect to %s, err=%d",
addr, errno);
noconnect:
#ifdef USE_EPOLL
if (ValidSocket(epfd)) close(epfd);
#endif
closesocket(sd);
return 0;
}
}
#ifdef USE_EPOLL
ev.events = (EPOLLIN | EPOLLONESHOT);
if (epoll_ctl(epfd, EPOLL_CTL_MOD, sd, &ev) < 0) {
message(LOG_ERR, "ident: epoll_ctl MOD err=%d", errno);
goto noconnect;
}
#endif
snprintf(buf, LONGSTRMAX, "%d, %d%c%c", sport, cport, '\r', '\n');
len = strlen(buf);
ret = send(sd, buf, len, 0);
if (ret != len) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
if (Debug > 0)
message(LOG_DEBUG,
"ident: can't send to %s ret=%d err=%d buf=%s",
addr, ret, errno, buf);
error:
shutdown(sd, 2);
#ifdef USE_EPOLL
if (ValidSocket(epfd)) close(epfd);
#endif
closesocket(sd);
return 0;
} else {
#ifndef USE_EPOLL
fd_set rout;
struct timeval tv;
#endif
do {
time(&now);
if (now - start >= CONN_TIMEOUT) {
if (Debug > 0)
message(LOG_DEBUG, "ident: read from %s, timeout", addr);
goto error;
}
#ifndef USE_EPOLL
tv.tv_sec = 1;
tv.tv_usec = 0;
FD_ZERO(&rout);
FdSet(sd, &rout);
#endif
} while (
#ifdef USE_EPOLL
epoll_wait(epfd, evs, 1, 1000) == 0
#else
select(FD_SETSIZE, &rout, NULL, NULL, &tv) == 0
#endif
);
ret = recv(sd, buf, LONGSTRMAX, 0);
if (ret <= 0) {
if (Debug > 0)
message(LOG_DEBUG, "ident: can't read from %s, ret=%d",
addr, ret);
goto error;
}
shutdown(sd, 2);
#ifdef USE_EPOLL
if (ValidSocket(epfd)) close(epfd);
#endif
closesocket(sd);
}
do {
ret--;
c = buf[ret];
} while (ret > 0 && (c == '\r' || c == '\n'));
ret++;
buf[ret] = '\0';
if (Debug > 2)
message(LOG_DEBUG, "ident: sent %s:%d, %d got %s",
addr, sport, cport, buf);
if (str) {
char *p;
p = rindex(buf, ':');
if (p) {
int i;
do {
p++;
} while (*p == ' ');
for (i=0; i < STRMAX && *p; i++) str[i] = *p++;
str[i] = '\0';
}
}
return 1;
}
int acceptCheck(Pair *pair1) {
struct sockaddr_storage ss;
struct sockaddr *from = (struct sockaddr*)&ss;
socklen_t fromlen = sizeof(ss);
Stone *stone = pair1->stone;
Pair *pair2 = NULL;
int satype;
int saproto = 0;
#ifdef ENLARGE
int prevXferBufMax = XferBufMax;
#endif
XHosts *xhost;
char ident[STRMAX+1];
char fromstr[STRMAX*2+1];
int fslen;
fslen = 0;
ident[0] = '\0';
bcopy(pair1->t->buf, &fromlen, sizeof(fromlen)); /* restore */
if (0 < fromlen && fromlen <= sizeof(ss)) {
bcopy(pair1->t->buf + sizeof(fromlen), from, fromlen);
} else {
message(LOG_ERR, "%d TCP %d: acceptCheck Can't happen fromlen=%d",
stone->sd, pair1->sd, fromlen);
if (getpeername(pair1->sd, from, &fromlen) < 0) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
message(LOG_ERR,
"%d TCP %d: acceptCheck Can't get peer's name err=%d",
stone->sd, pair1->sd, errno);
return 0;
}
}
if (stone->proto & proto_ident) {
if (getident(ident, from, fromlen, stone->port,
&stone->listen->addr, stone->listen->len)) {
ExBuf *t = newExData(pair1, data_identuser);
strncpy(fromstr, ident, STRMAX); /* (size of ident) <= STRMAX */
fromstr[STRMAX] = '\0';
fslen = strlen(fromstr);
if (t) {
strcpy(t->buf + DATA_HEAD_LEN, fromstr);
t->len = DATA_HEAD_LEN + fslen;
}
/* omit size check, because fslen <= STRMAX */
fromstr[fslen++] = '@';
}
}
fromstr[fslen] = '\0';
xhost = checkXhost(stone->xhosts, from, fromlen);
if (!xhost) {
addrport2strOnce(from, fromlen, (stone->proto & proto_stone_s),
fromstr+fslen, STRMAX*2-fslen, 0);
message(LOG_WARNING, "stone %d: access denied: from %s",
stone->sd, fromstr);
shutdown(pair1->sd, 2);
return 0;
}
if (AccFp) {
char str[STRMAX+1];
char tstr[STRMAX+1];
short port = 0;
time_t clock;
time(&clock);
if (from->sa_family == AF_INET) {
port = ntohs(((struct sockaddr_in*)from)->sin_port);
}
#ifdef AF_INET6
else if (from->sa_family == AF_INET6) {
port = ntohs(((struct sockaddr_in6*)from)->sin6_port);
}
#endif
addr2str(from, fromlen, str, STRMAX, NI_NUMERICHOST);
str[STRMAX] = '\0';
strntime(tstr, STRMAX, &clock, -1);
tstr[STRMAX] = '\0';
addrport2strOnce(from, fromlen, (stone->proto & proto_stone_s),
fromstr+fslen, STRMAX*2-fslen, 0);
fprintf(AccFp, "%s%d[%d] %s[%s]%d\n",
tstr, stone->port, stone->sd, fromstr, str, port);
}
if ((xhost->mode & XHostsMode_Dump) > 0 || Debug > 1) {
addrport2strOnce(from, fromlen, (stone->proto & proto_stone_s),
fromstr+fslen, STRMAX*2-fslen, 0);
message(LOG_DEBUG, "stone %d: accepted TCP %d from %s mode=%d",
stone->sd, pair1->sd, fromstr, xhost->mode);
}
pair2 = newPair();
if (!pair2) {
message(LOG_CRIT, "stone %d: out of memory, closing TCP %d",
stone->sd, pair1->sd);
if (pair2) freePair(pair2);
return 0;
}
pair2->stone = stone;
pair1->xhost = pair2->xhost = xhost;
pair2->proto = ((stone->proto & proto_pair_d) |
proto_first_r | proto_first_w);
pair2->timeout = stone->timeout;
/* now successfully accepted */
if (!(stone->proto & proto_block_d)) {
#ifdef WINDOWS
u_long param;
param = 1;
ioctlsocket(pair1->sd, FIONBIO, ¶m);
#else
fcntl(pair1->sd, F_SETFL, O_NONBLOCK);
#endif
}
#ifdef USE_SSL
if (stone->proto & proto_ssl_s) {
if (doSSL_accept(pair1) < 0) goto error;
} else
#endif /* src & pair1 is connected */
pair1->proto |= (proto_connect | proto_dirty);
/*
SSL connection may not be established yet,
but we can prepare the pair for connecting to the destination
*/
if (stone->proto & proto_udp_d) {
pair2->proto |= proto_dgram;
satype = SOCK_DGRAM;
saproto = IPPROTO_UDP;
} else {
satype = SOCK_STREAM;
saproto = IPPROTO_TCP;
}
#ifdef AF_LOCAL
if (stone->proto & proto_unix_d) {
saproto = 0;
pair2->sd = socket(AF_LOCAL, satype, saproto);
} else
#endif
#ifdef AF_INET6
if (stone->proto & proto_v6_d)
pair2->sd = socket(AF_INET6, satype, saproto);
else
#endif
pair2->sd = socket(AF_INET, satype, saproto);
if (InvalidSocket(pair2->sd)) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
message(priority(pair1), "%d TCP %d: can't create socket err=%d",
stone->sd, pair1->sd, errno);
#ifdef USE_SSL
error:
#endif
freePair(pair2);
return 0;
}
if (stone->from) {
if (bind(pair2->sd, &stone->from->addr, stone->from->len) < 0) {
char str[STRMAX+1];
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
addrport2str(&stone->from->addr, stone->from->len, 0,
str, STRMAX, 0);
str[STRMAX] = '\0';
message(LOG_ERR, "stone %d: can't bind %s err=%d",
stone->sd, str, errno);
}
}
pair2->pair = pair1;
pair1->pair = pair2;
return 1;
}
int strnAddr(char *buf, int limit, SOCKET sd, int which, int isport) {
struct sockaddr_storage ss;
struct sockaddr *name = (struct sockaddr*)&ss;
socklen_t namelen = sizeof(ss);
int len;
char str[STRMAX+1];
int ret;
switch (which) {
#ifdef SO_ORIGINAL_DST
case 1: /* original destination */
ret = getsockopt(sd, SOL_IP, SO_ORIGINAL_DST, name, &namelen);
break;
#endif
default: /* peer */
ret = getpeername(sd, name, &namelen);
}
if (ret < 0) {
if (isport) {
strcpy(str, "0.0.0.0:0");
} else {
strcpy(str, "0.0.0.0");
}
} else {
if (isport) {
addrport2str(name, namelen, 0, str, STRMAX, 0);
} else {
addr2str(name, namelen, str, STRMAX, 0);
}
str[STRMAX] = '\0';
}
len = strlen(str);
if (len > limit) len = limit;
strncpy(buf, str, len);
return len;
}
#ifdef SO_PEERCRED
#include <pwd.h>
#endif
int strnUser(char *buf, int limit, Pair *pair, int which) {
#if defined(AF_LOCAL) && defined(SO_PEERCRED)
Stone *stone = pair->stone;
#endif
ExBuf *ex;
int len;
char str[STRMAX+1];
str[0] = '\0';
if (which == 2 && (ex = getExData(pair, data_identuser, 0))) {
len = ex->len - DATA_HEAD_LEN;
strncpy(str, ex->buf + DATA_HEAD_LEN, len);
str[len] = '\0';
} else
#if defined(AF_LOCAL) && defined(SO_PEERCRED)
if (stone->listen->addr.sa_family == AF_LOCAL) {
struct ucred *cred = NULL;
ex = getExData(pair, data_ucred, 0);
if (ex) {
cred = (struct ucred*)(ex->buf + DATA_HEAD_LEN);
} else {
socklen_t optlen = sizeof(*cred);
ex = newExData(pair, data_ucred);
if (ex) {
cred = (struct ucred*)(ex->buf + DATA_HEAD_LEN);
if (getsockopt(pair->sd, SOL_SOCKET, SO_PEERCRED,
cred, &optlen) < 0) {
message(LOG_ERR, "%d TCP %d: Can't get PEERCRED err=%d",
stone->sd, pair->sd, errno);
ungetExBuf(ex);
cred = NULL;
}
}
}
switch (which) {
case 1: /* gid */
snprintf(str, STRMAX, "%d", (cred ? cred->gid : -1));
break;
case 2: /* user name */
*str = '\0';
if (cred) {
#ifdef THREAD_UNSAFE
struct passwd *passwd = getpwuid(cred->uid);
if (passwd) snprintf(str, STRMAX, "%s", passwd->pw_name);
#else
struct passwd pwbuf;
char sbuf[STRMAX+1];
struct passwd *passwd;
int ret = getpwuid_r(cred->uid, &pwbuf, sbuf, STRMAX, &passwd);
if (ret == 0) snprintf(str, STRMAX, "%s", passwd->pw_name);
#endif
}
break;
case 3: /* group name */
*str = '\0';
if (cred) {
#ifdef THREAD_UNSAFE
struct group *group = getgrgid(cred->gid);
if (group) snprintf(str, STRMAX, "%s", group->gr_name);
#else
struct group gbuf;
char sbuf[STRMAX+1];
struct group *group;
int ret = getgrgid_r(cred->gid, &gbuf, sbuf, STRMAX, &group);
if (ret == 0) snprintf(str, STRMAX, "%s", group->gr_name);
#endif
}
break;
default: /* uid */
snprintf(str, STRMAX, "%d", (cred ? cred->uid : -1));
break;
}
}
#endif
len = strlen(str);
if (len > limit) len = limit;
strncpy(buf, str, len);
return len;
}
int strnparse(char *buf, int limit, char **pp, Pair *pair, char term) {
int i = 0;
char *p;
char c;
#ifdef USE_SSL
char **match = NULL;
SSL *ssl = pair->ssl;
SSL_SESSION *sess = NULL;
int cond;
#endif
p = *pp;
while (i < limit && (c = *p++)) {
if (c == '\\') {
c = *p++;
if (c == term) break;
#ifdef USE_SSL
cond = -1;
if (c == '?') {
cond = 0;
c = *p++;
}
if ('0' <= c && c <= '9') {
if (ssl && !match) {
sess = SSL_get1_session(ssl);
if (sess)
match = SSL_SESSION_get_ex_data(sess, MatchIndex);
if (!match) ssl = NULL;
/* now (match || ssl == NULL) holds */
}
if (match) {
int num = c - '0';
if (match[num]) {
if (cond >= 0) {
if (*match[num]) cond = 1;
} else {
int len = strlen(match[num]);
if (len >= limit - i) len = limit - i;
if (buf) {
strncpy(buf+i, match[num], len);
i += len;
}
}
}
}
if (cond > 0) {
if (buf) {
i += strnparse(buf+i, limit-i, &p, pair, ':');
strnparse(NULL, limit-i, &p, pair, '/');
}
} else if (cond == 0) {
if (buf) {
strnparse(NULL, limit-i, &p, pair, ':');
i += strnparse(buf+i, limit-i, &p, pair, '/');
}
}
continue;
}
#endif
switch(c) {
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case 'a': /* peer address */
if (buf) i += strnAddr(buf+i, limit-i, pair->sd, 0, 0);
continue;
case 'A': /* peer address:port */
if (buf) i += strnAddr(buf+i, limit-i, pair->sd, 0, 1);
continue;
#ifdef SO_ORIGINAL_DST
case 'd': /* dst address */
if (buf) i += strnAddr(buf+i, limit-i, pair->sd, 1, 0);
continue;
case 'D': /* dst address:port (transparent proxy) */
if (buf) i += strnAddr(buf+i, limit-i, pair->sd, 1, 1);
continue;
#endif
case 'u':
if (buf) i += strnUser(buf+i, limit-i, pair, 0);
continue;
case 'g':
if (buf) i += strnUser(buf+i, limit-i, pair, 1);
continue;
case 'U':
if (buf) i += strnUser(buf+i, limit-i, pair, 2);
continue;
case 'G':
if (buf) i += strnUser(buf+i, limit-i, pair, 3);
continue;
case '\0':
c = '\\';
p--;
}
}
if (buf) buf[i++] = c;
}
#ifdef USE_SSL
if (sess) SSL_SESSION_free(sess);
#endif
if (buf) buf[i] = '\0';
*pp = p;
return i;
}
int scanClose(Pair *pairs) { /* scan close request */
Pair *p1, *p2, *p;
int n = 0;
int m = 0;
int all;
if (pairs) {
all = 0;
} else {
pairs = PairTop;
all = 1;
}
p1 = trash.next;
while (p1 != NULL) {
SOCKET sd;
p2 = p1;
p1 = p1->next;
if (p2->proto & proto_thread) continue;
if (p2->count > 0) {
p2->count--;
n++;
continue;
}
sd = p2->sd;
if (p2->proto & (proto_select_r | proto_select_w)) {
p2->proto &= ~(proto_select_r | proto_select_w);
p2->proto |= proto_dirty;
p2->count = REF_UNIT;
}
#ifdef USE_SSL
if (p2->ssl_flag) {
p2->ssl_flag = 0;
p2->count = REF_UNIT;
}
#endif
p = p2->prev;
if (p) p->next = p1; /* remove `p2' from trash */
if (p1) p1->prev = p;
freePair(p2);
m++;
}
if (Debug > 8 && (n > 0 || m > 0))
message(LOG_DEBUG, "trash: queued=%d, removed=%d", n, m);
p1 = pairs->next;
while (p1 != NULL) {
if (p1->clock == -1) { /* top */
if (all) {
pairs = p1;
p1 = pairs->next;
continue;
} else {
break;
}
}
p2 = p1;
p1 = p1->next;
if (!(p2->proto & proto_close)) continue; /* skip */
if (p2->count > 0) {
p2->count--;
continue;
}
waitMutex(PairMutex);
p = p2->prev;
if (p) p->next = p1; /* remove `p2' from list */
if (p1) p1->prev = p;
p = p2->pair;
if (p) p->pair = NULL;
freeMutex(PairMutex);
if (trash.next) trash.next->prev = p2; /* push `p2' to trash */
p2->prev = &trash;
p2->pair = NULL;
p2->count = REF_UNIT;
p2->next = trash.next;
trash.next = p2;
}
return 1;
}
void message_pairs(int pri) { /* dump for debug */
Pair *pair;
for (pair=PairTop; pair != NULL; pair=pair->next) {
if (pair->clock != -1) { /* not top */
message_pair(pri, pair);
} else if (Debug > 2) {
message(LOG_DEBUG, "%d TCP %d: top", pair->stone->sd, pair->sd);
}
}
}
void message_origins(int pri) { /* dump for debug */
Origin *origin;
for (origin=OriginTop; origin != NULL; origin=origin->next) {
if (origin->from) {
message_origin(pri, origin);
} else if (Debug > 2) {
message(LOG_DEBUG, "%d UDP %d: top",
origin->stone->sd, origin->sd);
}
}
}
void message_conns(int pri) { /* dump for debug */
Conn *conn;
for (conn=conns.next; conn != NULL; conn=conn->next)
message_conn(pri, conn);
}
/* read write thread */
/* no Mutex are needed because in the single thread */
void setclose(Pair *pair, int flag) { /* set close flag */
SOCKET sd = pair->sd;
message_time_log(pair);
if (!(pair->proto & proto_close)) { /* request to close */
pair->proto |= (flag | proto_close);
if (Debug > 2 && ValidSocket(sd))
message(LOG_DEBUG, "%d TCP %d: close tx:%d rx:%d lp:%d",
pair->stone->sd, sd, pair->tx, pair->rx, pair->loop);
}
#ifdef USE_EPOLL
if (ValidSocket(sd)) {
pair->sd = INVALID_SOCKET;
closesocket(sd);
}
#endif
}
int dowrite(Pair *pair) { /* write from buf from pair->t->start */
SOCKET sd = pair->sd;
Pair *p;
int len;
ExBuf *ex;
ex = pair->t; /* top */
if (!ex) return 0;
while (ex->len <= 0 && ex->next) {
pair->t = ex->next;
ex->next = NULL;
pair->nbuf--;
if (Debug > 4) message(LOG_DEBUG, "%d TCP %d: before dowrite "
"unget ExBuf nbuf=%d nfex=%d",
pair->stone->sd, pair->sd,
pair->nbuf, nFreeExBuf);
ungetExBuf(ex);
}
if (ex->len <= 0) return 0; /* nothing to write */
if (Debug > 5) message(LOG_DEBUG, "%d TCP %d: write %d bytes",
pair->stone->sd, sd, ex->len);
if (InvalidSocket(sd)) return -1;
#ifdef USE_SSL
if (pair->ssl) {
len = SSL_write(pair->ssl, &ex->buf[ex->start], ex->len);
if (pair->proto & proto_close) return -1;
if (len <= 0) {
int err;
err = SSL_get_error(pair->ssl, len);
if (err == SSL_ERROR_NONE
|| err == SSL_ERROR_WANT_WRITE) {
if (Debug > 4)
message(LOG_DEBUG,
"%d TCP %d: SSL_write interrupted err=%d",
pair->stone->sd, sd, err);
return 0; /* EINTR */
} else if (err == SSL_ERROR_WANT_READ) {
if (Debug > 4)
message(LOG_DEBUG,
"%d TCP %d: SSL_write blocked on read err=%d",
pair->stone->sd, sd, err);
pair->ssl_flag |= sf_wb_on_r;
return 0; /* EINTR */
}
if (err == SSL_ERROR_SYSCALL) {
unsigned long e = ERR_get_error();
if (e == 0) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
if (errno == EINTR) {
if (Debug > 4)
message(LOG_DEBUG,
"%d TCP %d: SSL_write I/O interrupted",
pair->stone->sd, sd);
return 0;
}
message(priority(pair),
"%d TCP %d: SSL_write I/O error err=%d, closing",
pair->stone->sd, sd, errno);
message_pair(LOG_ERR, pair);
} else {
message(priority(pair),
"%d TCP %d: SSL_write I/O %s, closing",
pair->stone->sd, sd, ERR_error_string(e, NULL));
message_pair(LOG_ERR, pair);
}
return -1; /* error */
} else if (err != SSL_ERROR_ZERO_RETURN) {
message(priority(pair),
"%d TCP %d: SSL_write err=%d %s, closing",
pair->stone->sd, sd,
err, ERR_error_string(ERR_get_error(), NULL));
message_pair(LOG_ERR, pair);
return len; /* error */
}
}
} else {
#endif
len = send(sd, &ex->buf[ex->start], ex->len, 0);
if (pair->proto & proto_close) return -1;
if (len < 0) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
if (errno == EINTR) {
if (Debug > 4)
message(LOG_DEBUG, "%d TCP %d: write interrupted",
pair->stone->sd, sd);
return 0;
}
if (errno == ECONNABORTED) {
if (Debug > 3)
message(LOG_DEBUG, "%d TCP %d: write aborted",
pair->stone->sd, sd);
return -1;
}
message(priority(pair), "%d TCP %d: write error err=%d, closing",
pair->stone->sd, sd, errno);
message_pair(LOG_ERR, pair);
return len; /* error */
}
#ifdef USE_SSL
}
#endif
if (Debug > 4) message(LOG_DEBUG, "%d TCP %d: %d bytes written",
pair->stone->sd, sd, len);
if ((pair->xhost->mode & XHostsMode_Dump) > 0
|| ((pair->proto & proto_first_w) && Debug > 3))
message_buf(pair, len, "");
time(&pair->clock);
p = pair->pair;
if (p) p->clock = pair->clock;
if (ex->len <= len) {
ex->start = 0;
} else {
ex->start += len;
message(LOG_NOTICE,
"%d TCP %d: write %d bytes, but only %d bytes written",
pair->stone->sd, sd, ex->len, len);
message_pair(LOG_NOTICE, pair);
}
ex->len -= len;
if (ex->len <= 0 && ex->next) {
pair->t = ex->next;
ex->next = NULL;
pair->nbuf--;
if (Debug > 4) message(LOG_DEBUG, "%d TCP %d: after dowrite "
"unget ExBuf nbuf=%d nfex=%d",
pair->stone->sd, pair->sd,
pair->nbuf, nFreeExBuf);
ungetExBuf(ex);
}
pair->tx += len;
if ((p->proto & proto_command) != command_health)
lastReadWrite = pair->clock;
return len;
}
static unsigned char basis_64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
int baseEncode(unsigned char *buf, int len, int max) {
unsigned char *org = buf + max - len;
unsigned char c1;
unsigned char c2 = 0; /* dummy init to suppress warnings */
unsigned char c3 = 0;
int blen = 0;
int i;
bcopy(buf, org, len);
for (i=0; i < len; i += 3) {
switch (len - i) {
case 1:
c2 = '\0';
buf[blen+2] = '=';
case 2:
c3 = '\0';
buf[blen+3] = '=';
}
switch (len - i) {
default:
c3 = org[i+2];
buf[blen+3] = basis_64[c3 & 0x3F];
case 2:
c2 = org[i+1];
buf[blen+2] = basis_64[((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)];
case 1:
c1 = org[i];
buf[blen+1] = basis_64[((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)];
buf[blen] = basis_64[c1>>2];
}
blen += 4;
}
if (buf[blen-1] != '=') buf[blen++] = '=';
return blen;
}
#define XX 255 /* illegal base64 char */
#define EQ 254 /* padding */
static unsigned char index_64[256] = {
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,62, XX,XX,XX,63,
52,53,54,55, 56,57,58,59, 60,61,XX,XX, XX,EQ,XX,XX,
XX, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14,
15,16,17,18, 19,20,21,22, 23,24,25,XX, XX,XX,XX,XX,
XX,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
41,42,43,44, 45,46,47,48, 49,50,51,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
};
int baseDecode(unsigned char *buf, int len, char *rest) {
int blen = 0;
unsigned char c[4], o[4];
int i, j;
j = 0;
for (i=0; i < len; i++) {
c[j] = index_64[buf[i]];
if (c[j] == XX) continue;
if (j == 0 && c[j] == EQ) continue;
o[j++] = buf[i];
if (j == 4) {
j = 0;
buf[blen++] = (c[0] << 2) | ((c[1] & 0x30) >> 4);
if (c[2] == EQ) continue;
buf[blen++] = ((c[1] & 0x0F) << 4) | ((c[2] & 0x3C) >> 2);
if (c[3] == EQ) continue;
buf[blen++] = ((c[2] & 0x03) << 6) | c[3];
}
}
*rest = j;
for (i=0; i < j; i++) *(rest-1-i) = o[i];
return blen;
}
int doread(Pair *pair) { /* read into buf from pair->pair->b->start */
SOCKET sd = pair->sd;
Pair *p;
int len, i;
ExBuf *ex;
int bufmax, start;
if (InvalidSocket(sd)) return -1;
if (Debug > 5) message(LOG_DEBUG, "%d TCP %d: read", pair->stone->sd, sd);
p = pair->pair;
if (p == NULL) { /* no pair, no more read */
char _buf[BUFMAX];
#ifdef USE_SSL
if (pair->ssl) {
len = SSL_read(pair->ssl, _buf, BUFMAX);
} else
#endif
len = recv(sd, _buf, BUFMAX, 0);
if (pair->proto & proto_close) return -1;
if (Debug > 4) message(LOG_DEBUG, "%d TCP %d: read %d bytes",
pair->stone->sd, sd, len);
if (len == 0) return -1; /* EOF w/o pair */
if (len > 0) {
message(priority(pair), "%d TCP %d: no pair, closing",
pair->stone->sd, sd);
message_pair(LOG_ERR, pair);
len = -1;
}
return len;
}
ex = p->b; /* bottom */
if (ex->len > 0) { /* not emply */
ex = getExBuf();
if (!ex) return -1; /* out of memory */
p->b->next = ex;
p->b = ex;
p->nbuf++;
if (Debug > 4) message(LOG_DEBUG, "%d TCP %d: get ExBuf nbuf=%d",
pair->stone->sd, p->sd, p->nbuf);
}
bufmax = ex->bufmax - ex->start - ex->len;
start = ex->start + ex->len;
if (p->proto & proto_base) bufmax = (bufmax - 1) / 4 * 3;
else if (pair->proto & proto_base) {
if (!(pair->proto & proto_first_r)) {
len = *(ex->buf+ex->bufmax-1);
for (i=0; i < len; i++) {
ex->buf[start++] = ex->buf[ex->bufmax-2-i];
}
bufmax -= len;
}
*(ex->buf+ex->bufmax-1) = 0;
bufmax -= 5;
}
if (((p->proto & proto_command) == command_ihead) ||
((p->proto & proto_command) == command_iheads)) bufmax = bufmax / 2;
#ifdef USE_SSL
if (pair->ssl) {
len = SSL_read(pair->ssl, &ex->buf[start], bufmax);
if (pair->proto & proto_close) return -1;
if (len < 0) {
int err;
err = SSL_get_error(pair->ssl, len);
if (err == SSL_ERROR_NONE
|| err == SSL_ERROR_WANT_READ) {
if (Debug > 4)
message(LOG_DEBUG,
"%d TCP %d: SSL_read interrupted err=%d",
pair->stone->sd, sd, err);
return 0; /* EINTR */
} else if (err == SSL_ERROR_WANT_WRITE) {
if (Debug > 4)
message(LOG_DEBUG,
"%d TCP %d: SSL_read blocked on write err=%d",
pair->stone->sd, sd, err);
pair->ssl_flag |= sf_rb_on_w;
return 0; /* EINTR */
}
if (err == SSL_ERROR_SYSCALL) {
unsigned long e = ERR_get_error();
if (e == 0) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
if (errno == EINTR) {
if (Debug > 4)
message(LOG_DEBUG,
"%d TCP %d: SSL_read I/O interrupted",
pair->stone->sd, sd);
return 0;
}
message(priority(pair),
"%d TCP %d: SSL_read I/O error err=%d, closing",
pair->stone->sd, sd, errno);
message_pair(LOG_ERR, pair);
} else {
message(priority(pair),
"%d TCP %d: SSL_read I/O %s, closing",
pair->stone->sd, sd, ERR_error_string(e, NULL));
message_pair(LOG_ERR, pair);
}
return -1; /* error */
} else if (err != SSL_ERROR_ZERO_RETURN) {
message(priority(pair),
"%d TCP %d: SSL_read err=%d %s, closing",
pair->stone->sd, sd,
err, ERR_error_string(ERR_get_error(), NULL));
message_pair(LOG_ERR, pair);
return -1; /* error */
}
}
} else {
#endif
len = recv(sd, &ex->buf[start], bufmax, 0);
if (pair->proto & proto_close) return -1;
if (len < 0) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
if (errno == EINTR) {
if (Debug > 4)
message(LOG_DEBUG, "%d TCP %d: read interrupted",
pair->stone->sd, sd);
return 0; /* EINTR */
}
if (errno == ECONNRESET) {
if (Debug > 3)
message(LOG_DEBUG, "%d TCP %d: read but reset by peer",
pair->stone->sd, sd);
return -1;
}
message(priority(pair), "%d TCP %d: read error err=%d, closing",
pair->stone->sd, sd, errno);
message_pair(LOG_ERR, pair);
return len; /* error */
}
#ifdef USE_SSL
}
#endif
if (len > 0) {
pair->rx += len;
#ifdef ENLARGE
if (len > ex->bufmax - 10
&& XferBufMax < ex->bufmax * 2) {
XferBufMax = ex->bufmax * 2;
message(LOG_NOTICE, "%d TCP %d: XferBufMax becomes %d byte",
pair->stone->sd, sd, XferBufMax);
}
#endif
ex->len = start + len - ex->start;
if (Debug > 4) {
SOCKET psd = p->sd;
if (start > ex->start) {
message(LOG_DEBUG, "%d TCP %d: read %d+%d bytes to %d",
pair->stone->sd, sd, len, start - ex->start, psd);
} else {
message(LOG_DEBUG, "%d TCP %d: read %d bytes to %d",
pair->stone->sd, sd, ex->len, psd);
}
}
time(&pair->clock);
p->clock = pair->clock;
if (p->proto & proto_base) {
ex->len = baseEncode((unsigned char*)&ex->buf[ex->start], ex->len,
ex->bufmax - ex->start);
} else if (pair->proto & proto_base) {
ex->len = baseDecode((unsigned char*)&ex->buf[ex->start], ex->len,
ex->buf+ex->bufmax-1);
len = *(ex->buf+ex->bufmax-1);
if (Debug > 4 && len > 0) { /* len < 4 */
char str[STRMAX+1];
for (i=0; i < len; i++)
sprintf(&str[i*3], " %02x", ex->buf[ex->bufmax-2-i]);
str[0] = '(';
message(LOG_DEBUG, "%d TCP %d: save %d bytes \"%s\")",
pair->stone->sd, sd, len, str);
}
}
if ((p->proto & proto_command) != command_health)
lastReadWrite = pair->clock;
}
if (p->t->len <= 0) { /* top */
message_time_log(pair);
if (Debug > 2)
message(LOG_DEBUG, "%d TCP %d: EOF", pair->stone->sd, sd);
return -2; /* EOF w/ pair */
}
return p->t->len;
}
/* http */
#define METHOD_LEN_MAX 10
int commOutput(Pair *pair, char *fmt, ...) {
Pair *p = pair->pair;
ExBuf *ex;
SOCKET psd;
char *str;
va_list ap;
if (p == NULL) return -1;
psd = p->sd;
if ((p->proto & (proto_shutdown | proto_close)) || InvalidSocket(psd))
return -1;
ex = p->b; /* bottom */
if (ex->bufmax - (ex->start + ex->len) < STRMAX+1) {
ExBuf *new = getExBuf();
if (new) {
ex = new;
p->b->next = ex;
p->b = ex;
p->nbuf++;
if (Debug > 4) message(LOG_DEBUG, "%d TCP %d: get ExBuf nbuf=%d",
pair->stone->sd, p->sd, p->nbuf);
}
}
str = &ex->buf[ex->start + ex->len];
ex->buf[ex->bufmax-1] = '\0';
va_start(ap, fmt);
vsnprintf(str, ex->bufmax-1 - (ex->start + ex->len), fmt, ap);
va_end(ap);
if (p->proto & proto_base)
ex->len += baseEncode((unsigned char*)str, strlen(str),
ex->bufmax-1 - (ex->start + ex->len));
else ex->len += strlen(str);
p->proto |= (proto_select_w | proto_dirty); /* need to write */
return ex->len;
}
static char *comm_match(char *buf, char *str) {
while (*str) {
if (toupper(*buf++) != *str++) return NULL; /* unmatch */
}
if (*buf) {
if (!isspace(*buf)) return NULL;
/* while (isspace(*buf)) buf++; */
if (*buf == ' ') buf++;
}
return buf;
}
#ifdef ADDRCACHE
unsigned int str2hash(char *str) {
unsigned int hash = 0;
while (*str) {
hash = hash * 7 + *str;
str++;
}
return hash;
}
struct hashtable {
char *host;
char *serv;
time_t clock;
int len;
struct sockaddr_storage ss;
} *hashtable;
int addrcache(char *name, char *serv, struct sockaddr *sa, socklen_t *salenp) {
struct hashtable *t;
time_t now;
time(&now);
if (!hashtable) {
hashtable = malloc(AddrCacheSize * sizeof(struct hashtable));
if (!hashtable) {
message(LOG_ERR, "addrcache: out of memory");
return host2sa(name, serv, sa, salenp, NULL, NULL, 0);
}
bzero(hashtable, AddrCacheSize * sizeof(struct hashtable));
}
t = &hashtable[(str2hash(name) ^ str2hash(serv)) % AddrCacheSize];
waitMutex(HashMutex);
if (t->host && strcmp(t->host, name) == 0
&& t->serv && strcmp(t->serv, serv) == 0
&& t->len <= *salenp
&& now - t->clock < CACHE_TIMEOUT) {
bcopy(&t->ss, sa, t->len);
*salenp = t->len;
freeMutex(HashMutex);
if (Debug > 5) message(LOG_DEBUG, "addrcache hit: %s:%s %d",
name, serv, (int)(now - t->clock));
return 1;
}
freeMutex(HashMutex);
if (Debug > 9) message(LOG_DEBUG, "addrcache %s %s", name, serv);
if (!host2sa(name, serv, sa, salenp, NULL, NULL, 0)) {
return 0;
}
waitMutex(HashMutex);
if ((t->host && strcmp(t->host, name) != 0) ||
(t->serv && strcmp(t->serv, serv) != 0) ||
(t->len > *salenp)) {
free(t->host);
free(t->serv);
t->host = NULL;
t->serv = NULL;
t->len = sizeof(t->ss);
}
if (!t->host) t->host = strdup(name);
if (!t->serv) t->serv = strdup(serv);
bcopy(sa, &t->ss, *salenp);
t->len = *salenp;
t->clock = now;
freeMutex(HashMutex);
return 1;
}
#endif
int doproxy(Pair *pair, char *host, char *serv) {
SOCKET sd = pair->sd;
int reconnect = 0;
PortXHosts *pxh;
struct sockaddr_storage name_s;
struct sockaddr *name = (struct sockaddr*)&name_s;
struct sockaddr_storage ss;
struct sockaddr *sa = (struct sockaddr*)&ss;
socklen_t namelen = sizeof(name_s);
socklen_t salen = sizeof(ss);
if ((pair->stone->proto & proto_ip_only_d)) {
#ifdef AF_INET6
if ((pair->stone->proto & proto_v6_d))
sa->sa_family = AF_INET6;
else
#endif
sa->sa_family = AF_INET;
} else {
sa->sa_family = AF_UNSPEC;
}
#ifdef ADDRCACHE
if (AddrCacheSize > 0) {
if (!addrcache(host, serv, sa, &salen)) return -1;
} else
#endif
if (!host2sa(host, serv, sa, &salen, NULL, NULL, 0)) return -1;
if (islocalhost(sa)) {
TimeLog *log = pair->log;
pair->log = NULL;
if (log) free(log);
}
if ((pair->stone->proto & proto_nobackup) == 0) {
Backup *backup = findBackup(sa);
if (backup && backup->bn) { /* unhealthy */
sa = &backup->backup->addr;
salen = backup->backup->len;
}
}
pxh = (PortXHosts*)pair->stone->dsts[1];
if (pxh) {
for (; pxh; pxh=pxh->next) {
XPorts *ports;
XHosts *xhost;
int isok = 0;
int port = getport(sa);
for (ports=pxh->ports; ports; ports=ports->next) {
if (ports->from <= port && port <= ports->end) {
isok = 1;
}
}
if (!isok) continue;
xhost = checkXhost(pxh->xhosts, sa, salen);
if (xhost) {
if (xhost->mode) {
Pair *p = pair->pair;
pair->xhost = xhost;
if (p) p->xhost = xhost;
}
if (Debug > 7) {
message(LOG_DEBUG,
"stone %d: proxy can connect to %s:%s mode=%d",
pair->stone->sd, host, serv, xhost->mode);
}
break;
} else {
message(LOG_WARNING, "stone %d: proxy may not connect to %s",
pair->stone->sd, host);
return -1;
}
}
if (!pxh) {
message(LOG_WARNING, "stone %d: proxy may not connect to port %s",
pair->stone->sd, serv);
return -1;
}
}
if ((pair->proto & proto_connect) && !(pair->proto & proto_close)
&& getpeername(sd, name, &namelen) >= 0) { /* reconnect proxy */
Pair *p = pair->pair;
if (Debug > 7) {
char str[STRMAX+1];
message(LOG_DEBUG, "%d TCP %d: old proxy connection: %s",
pair->stone->sd, sd,
addrport2str(name, namelen, 0, str, STRMAX, 0));
}
if (p) p->proto |= (proto_first_w | proto_dirty);
if (saComp(sa, name)) return 0; /* same sa, so need not to connect */
reconnect = 1;
}
if (reconnect
|| ((pair->stone->proto & proto_v6_d) && sa->sa_family == AF_INET)
#ifdef AF_INET6
|| (!(pair->stone->proto & proto_v6_d) && sa->sa_family == AF_INET6)
#endif
) {
SOCKET nsd = socket(sa->sa_family, SOCK_STREAM, IPPROTO_TCP);
if (ValidSocket(nsd)) {
Pair *p = pair->pair;
#ifdef USE_EPOLL
struct epoll_event ev;
ev.events = EPOLLONESHOT;
ev.data.ptr = pair;
if (epoll_ctl(ePollFd, EPOLL_CTL_ADD, nsd, &ev) < 0) {
message(LOG_ERR, "%d TCP %d: reopen "
"epoll_ctl %d ADD err=%d",
pair->stone->sd, nsd, ePollFd, errno);
}
#endif
pair->sd = nsd;
message(LOG_INFO, "%d TCP %d: close %d %08x, "
"reopen %d as family=%d",
pair->stone->sd, (p ? p->sd : INVALID_SOCKET),
sd, pair->proto, nsd, sa->sa_family);
closesocket(sd);
}
}
pair->proto &= ~(proto_connect | proto_command);
if (reqconn(pair, sa, salen) < 0) return -1;
if ((pair->proto & state_mask) == 1) {
if (Debug > 7) message(LOG_DEBUG, "%d TCP %d: command_proxy again",
pair->stone->sd, pair->sd);
pair->proto |= command_proxy;
}
return 0;
}
int proxyCONNECT(Pair *pair, char *parm, int start) {
char *port = "443"; /* default: https */
char *r = parm;
char *q = NULL;
Pair *p;
message_time(pair, LOG_INFO, "CONNECT %s", parm);
while (*r) {
if (isspace(*r)) {
*r = '\0';
break;
}
if (*r == ':') q = r;
r++;
}
if (q) {
port = q + 1;
*q = '\0';
}
pair->b->len += pair->b->start;
pair->b->start = 0;
p = pair->pair;
if (p) p->proto |= proto_ohttp_s; /* remove request header */
return doproxy(pair, parm, port);
}
int proxyCommon(Pair *pair, char *parm, int start) {
char *port = "80"; /* default port of http:// */
char *host;
ExBuf *ex;
char *top;
char *p, *q;
int i;
ex = pair->b; /* bottom */
top = &ex->buf[start];
for (i=0; i < METHOD_LEN_MAX; i++) {
if (parm[i] == ':') break;
}
if (strncmp(parm, "http", i) != 0
|| parm[i+1] != '/' || parm[i+2] != '/') {
message(LOG_ERR, "Unknown URL format: %s", parm);
return -1;
}
host = &parm[i+3];
p = host;
while (*p) {
if (*p == ':') {
port = p + 1;
*p++ = '\0';
continue;
}
if (isspace(*p) || *p == '/') {
*p = '\0';
break;
}
p++;
}
i = p - parm; /* length of 'http://host' */
p = top;
while (!isspace(*p)) p++; /* skip 'GET http://host' */
while (isspace(*p)) p++; /* now p points url */
q = p + i; /* now q points path */
if (*q != '/') *--q = '/';
bcopy(q, p, ex->start + ex->len - (q - top));
ex->len = ex->start + ex->len - (q - p);
ex->start = 0;
if (Debug > 1) {
Pair *r = pair->pair;
message(LOG_DEBUG, "proxy %d -> http://%s:%s",
(r ? r->sd : INVALID_SOCKET), host, port);
}
#ifdef USE_EPOLL
if (pair->proto & proto_noconnect) {
struct epoll_event ev;
ev.events = EPOLLONESHOT;
ev.data.ptr = pair;
if (epoll_ctl(ePollFd, EPOLL_CTL_ADD, pair->sd, &ev) < 0) {
message(LOG_ERR, "%d TCP %d: proxyCommon "
"epoll_ctl %d ADD err=%d",
pair->stone->sd, pair->sd, ePollFd, errno);
}
}
#endif
pair->proto &= ~(proto_noconnect | state_mask);
pair->proto |= (proto_dirty | 1);
return doproxy(pair, host, port);
}
int proxyGET(Pair *pair, char *parm, int start) {
message_time(pair, LOG_INFO, "GET %s", parm);
return proxyCommon(pair, parm, start);
}
int proxyHEAD(Pair *pair, char *parm, int start) {
message_time(pair, LOG_INFO, "HEAD %s", parm);
return proxyCommon(pair, parm, start);
}
int proxyPOST(Pair *pair, char *parm, int start) {
message_time(pair, LOG_INFO, "POST %s", parm);
return proxyCommon(pair, parm, start);
}
int proxyErr(Pair *pair, char *parm, int start) {
message(LOG_ERR, "Unknown method: %s", parm);
return -1;
}
Comm proxyComm[] = {
{ "CONNECT", proxyCONNECT },
{ "POST", proxyPOST },
{ "GET", proxyGET },
{ "HEAD", proxyHEAD },
{ NULL, proxyErr },
};
#ifdef USE_POP
int popUSER(Pair *pair, char *parm, int start) {
int ulen, tlen;
char *data;
ExBuf *ex = getExData(pair, data_apop, 0);
if (!ex) {
message(LOG_ERR, "%d TCP %d: popUSER Can't happen no ExData",
pair->stone->sd, pair->sd);
return -1;
}
data = ex->buf + DATA_HEAD_LEN;
if (Debug) message(LOG_DEBUG, ": USER %s", parm);
ulen = strlen(parm);
tlen = strlen(data);
if (ulen + 1 + tlen + 1 >= BUFMAX-1) {
commOutput(pair, "+Err Too long user name\r\n");
return -1;
}
bcopy(data, data + ulen + 1, tlen + 1);
strcpy(data, parm);
commOutput(pair, "+OK Password required for %s\r\n", parm);
pair->proto &= ~state_mask;
pair->proto |= 1;
return -2; /* read more */
}
#define DIGEST_LEN 16
int popPASS(Pair *pair, char *parm, int start) {
MD5_CTX context;
unsigned char digest[DIGEST_LEN];
char *str;
int ulen, tlen, plen, i;
int state = (pair->proto & state_mask);
ExBuf *ex;
ExBuf *t;
char *data;
int max;
if (Debug > 5) message(LOG_DEBUG, ": PASS %s", parm);
if (state < 1) {
commOutput(pair, "-ERR USER first\r\n");
return -2; /* read more */
}
t = getExData(pair, data_apop, 1);
data = t->buf + DATA_HEAD_LEN;
max = t->bufmax - DATA_HEAD_LEN;
ulen = strlen(data);
str = data + ulen + 1;
tlen = strlen(str);
plen = strlen(parm);
if (ulen + 1 + tlen + plen + 1 >= max-1) {
commOutput(pair, "+Err Too long password\r\n");
return -1;
}
strcat(str, parm);
ex = pair->b; /* bottom */
sprintf(ex->buf, "APOP %s ", data);
ulen = strlen(ex->buf);
MD5Init(&context);
MD5Update(&context, str, tlen + plen);
MD5Final(digest, &context);
ungetExBuf(t);
for (i=0; i < DIGEST_LEN; i++) {
sprintf(ex->buf + ulen + i*2, "%02x", digest[i]);
}
message_time(pair, LOG_INFO, "POP -> %s", ex->buf);
strcat(ex->buf, "\r\n");
ex->start = 0;
ex->len = strlen(ex->buf);
return 0;
}
int popAUTH(Pair *pair, char *parm, int start) {
if (Debug) message(LOG_DEBUG, ": AUTH %s", parm);
commOutput(pair, "-ERR authorization first\r\n");
return -2; /* read more */
}
int popCAPA(Pair *pair, char *parm, int start) {
if (Debug) message(LOG_DEBUG, ": CAPA %s", parm);
commOutput(pair, "-ERR authorization first\r\n");
return -2; /* read more */
}
int popAPOP(Pair *pair, char *parm, int start) {
ExBuf *ex = pair->b; /* bottom */
message_time(pair, LOG_INFO, "APOP %s", parm);
ex->len += ex->start - start;
ex->start = start;
return 0;
}
int popErr(Pair *pair, char *parm, int start) {
message(LOG_ERR, "Unknown POP command: %s", parm);
return -1;
}
Comm popComm[] = {
{ "USER", popUSER },
{ "PASS", popPASS },
{ "APOP", popAPOP },
{ "AUTH", popAUTH },
{ "CAPA", popCAPA },
{ NULL, popErr },
};
#endif
Pair *identd(int cport, struct sockaddr *ssa, socklen_t ssalen) {
struct sockaddr_storage ss;
struct sockaddr *sa = (struct sockaddr*)&ss;
socklen_t salen;
Pair *pair;
for (pair=PairTop; pair != NULL; pair=pair->next) {
SOCKET sd;
if ((pair->proto & proto_command) == command_source) continue;
sd = pair->sd;
salen = sizeof(ss);
if (InvalidSocket(sd) || getsockname(sd, sa, &salen) < 0) {
continue;
}
if (getport(sa) != cport) continue;
salen = sizeof(ss);
if (getpeername(sd, sa, &salen) < 0) {
continue;
}
if (!saComp(sa, ssa)) continue;
return pair;
}
return NULL;
}
int identdQUERY(Pair *pair, char *parm, int start) {
int cport = 0;
int sport = 0;
char mesg[STRMAX+1];
struct sockaddr_storage ss;
struct sockaddr *sa = (struct sockaddr*)&ss;
socklen_t salen = sizeof(ss);
Pair *p = pair->pair;
strcpy(mesg, "ERROR : NO-USER");
if (p) {
SOCKET sd = p->sd;
if (sscanf(parm, "%d,%d", &cport, &sport) == 2
&& ValidSocket(sd) && getpeername(sd, sa, &salen) >= 0) {
if (Debug > 8) {
char addrport[STRMAX+1];
addrport2str(sa, salen, 0, addrport, STRMAX, 0);
message(LOG_DEBUG, "%d TCP %d: identd query %d,%d from %s",
pair->stone->sd, sd, cport, sport, addrport);
}
saPort(sa, sport);
p = identd(cport, sa, salen);
if (p) {
int port = -1;
Stone *stone = p->stone;
if (stone) port = stone->port;
snprintf(mesg, STRMAX, "USERID : STONE : %d", port);
}
if (Debug > 2) {
char addrport[STRMAX+1];
addrport2str(sa, salen, 0, addrport, STRMAX, 0);
message(LOG_DEBUG, "identd %d %s %s", cport, addrport, mesg);
}
} else {
return -1;
}
}
commOutput(pair, "%d , %d : %s\r\n", cport, sport, mesg);
return -2; /* read more */
}
int identdQUIT(Pair *pair, char *parm, int start) {
if (Debug) message(LOG_DEBUG, "identd QUIT %s", parm);
return -1;
}
Comm identdComm[] = {
{ "QUIT", identdQUIT },
{ "", identdQUERY },
{ NULL, identdQUERY },
};
int nStones(void) {
int n = 0;
Stone *stone;
for (stone=stones; stone != NULL; stone=stone->next) n++;
return n;
}
int nPairs(Pair *top) {
int n = 0;
Pair *pair;
for (pair=top; pair != NULL; pair=pair->next)
if (pair->clock != -1) n++; /* not top */
return n;
}
int nConns(void) {
int n = 0;
Conn *conn;
for (conn=conns.next; conn != NULL; conn=conn->next) n++;
return n;
}
int nOrigins(void) {
int n = 0;
Origin *origin;
for (origin=OriginTop; origin != NULL; origin=origin->next)
if (origin->from) n++;
return n;
}
int limitCommon(Pair *pair, int var, int limit, char *str) {
if (Debug) message(LOG_DEBUG, ": LIMIT %s %d: %d", str, limit, var);
if (var < limit) {
commOutput(pair, "200 %s=%d is less than %d\r\n",
str, var, limit);
} else {
commOutput(pair, "500 %s=%d is not less than %d\r\n", str, var, limit);
}
return -2; /* read more */
}
int limitPair(Pair *pair, char *parm, int start) {
return limitCommon(pair, nPairs(PairTop), atoi(parm), "pair");
}
int limitConn(Pair *pair, char *parm, int start) {
return limitCommon(pair, nConns(), atoi(parm), "conn");
}
int limitEstablished(Pair *pair, char *parm, int start) {
time_t now;
time(&now);
return limitCommon(pair, (int)(now - lastEstablished),
atoi(parm), "established");
}
int limitReadWrite(Pair *pair, char *parm, int start) {
time_t now;
time(&now);
return limitCommon(pair, (int)(now - lastReadWrite),
atoi(parm), "readwrite");
}
int limitAsync(Pair *pair, char *parm, int start) {
return limitCommon(pair, AsyncCount, atoi(parm), "async");
}
int limitErr(Pair *pair, char *parm, int start) {
if (Debug) message(LOG_ERR, ": Illegal LIMIT %s", parm);
commOutput(pair, "500 Illegal LIMIT\r\n");
return -2; /* read more */
}
Comm limitComm[] = {
{ "PAIR", limitPair },
{ "CONN", limitConn },
{ "ESTABLISHED", limitEstablished },
{ "READWRITE", limitReadWrite },
{ "ASYNC", limitAsync },
{ NULL, limitErr },
};
int healthHELO(Pair *pair, char *parm, int start) {
char str[LONGSTRMAX+1];
snprintf(str, LONGSTRMAX,
"stone=%d pair=%d trash=%d conn=%d origin=%d",
nStones(), nPairs(PairTop), nPairs(trash.next),
nConns(), nOrigins());
str[LONGSTRMAX] = '\0';
if (Debug) message(LOG_DEBUG, ": HELO %s: %s", parm, str);
commOutput(pair, "250 stone:%s debug=%d %s\r\n",
VERSION, Debug, str);
return -2; /* read more */
}
int healthSTAT(Pair *pair, char *parm, int start) {
char str[LONGSTRMAX+1];
int mc = MutexConflict;
MutexConflict = 0;
snprintf(str, LONGSTRMAX,
"async=%d mutex=%d",
AsyncCount, mc);
str[LONGSTRMAX] = '\0';
if (Debug) message(LOG_DEBUG, ": STAT %s: %s", parm, str);
commOutput(pair, "250 stone:%s debug=%d %s\r\n",
VERSION, Debug, str);
return -2; /* read more */
}
int healthFREE(Pair *pair, char *parm, int start) {
char str[LONGSTRMAX+1];
snprintf(str, LONGSTRMAX,
"fpair=%d nfexbuf=%d nfexbot=%d nfpktbuf=%d",
nFreePairs, nFreeExBuf, nFreeExBot, nFreePktBuf);
str[LONGSTRMAX] = '\0';
if (Debug) message(LOG_DEBUG, ": FREE %s: %s", parm, str);
commOutput(pair, "250 stone:%s debug=%d %s\r\n",
VERSION, Debug, str);
return -2; /* read more */
}
int healthCLOCK(Pair *pair, char *parm, int start) {
char str[LONGSTRMAX+1];
time_t now;
time(&now);
snprintf(str, LONGSTRMAX,
"now=%ld established=%d readwrite=%d", (long)now,
(int)(now - lastEstablished), (int)(now - lastReadWrite));
str[LONGSTRMAX] = '\0';
if (Debug) message(LOG_DEBUG, ": CLOCK %s: %s", parm, str);
commOutput(pair, "250 stone:%s debug=%d %s\r\n",
VERSION, Debug, str);
return -2; /* read more */
}
int healthCVS_ID(Pair *pair, char *parm, int start) {
commOutput(pair, "200 stone %s %s\r\n", VERSION, CVS_ID);
return -2; /* read more */
}
int healthCONFIG(Pair *pair, char *parm, int start) {
int i;
for (i=1; i < ConfigArgc; i++)
commOutput(pair, "200%c%s\n", (i < ConfigArgc-1 ? '-' : ' '),
ConfigArgv[i]);
return -2; /* read more */
}
int healthSTONE(Pair *pair, char *parm, int start) {
Stone *stone;
char str[STRMAX+1];
for (stone=stones; stone != NULL; stone=stone->next) {
Stone *child;
for (child=stone->children; child != NULL; child=child->children)
commOutput(pair, "200-%s\n", stone2str(child, str, STRMAX));
commOutput(pair, "200%c%s\n", (stone->next ? '-' : ' '),
stone2str(stone, str, STRMAX));
}
return -2; /* read more */
}
int healthLIMIT(Pair *pair, char *parm, int start) {
Comm *comm = limitComm;
char *q = NULL;
while (comm->str) {
if ((q=comm_match(parm, comm->str)) != NULL) break;
comm++;
}
if (!q) return limitErr(pair, parm, start);
return (*comm->func)(pair, q, start);
}
int healthQUIT(Pair *pair, char *parm, int start) {
if (Debug) message(LOG_DEBUG, ": QUIT %s", parm);
return -1;
}
int healthErr(Pair *pair, char *parm, int start) {
if (*parm) message(LOG_ERR, "Unknown health command: %s", parm);
return -1;
}
Comm healthComm[] = {
{ "HELO", healthHELO },
{ "STAT", healthSTAT },
{ "FREE", healthFREE },
{ "CLOCK", healthCLOCK },
{ "CVS_ID", healthCVS_ID },
{ "CONFIG", healthCONFIG },
{ "STONE", healthSTONE },
{ "LIMIT", healthLIMIT },
{ "QUIT", healthQUIT },
{ NULL, healthErr },
};
int memCheck(void) {
char *buf = malloc(BUFMAX * 10);
if (buf) {
free(buf);
return 1;
}
message(LOG_CRIT, "memCheck: out of memory");
return 0;
}
int docomm(Pair *pair, Comm *comm) {
ExBuf *ex = pair->b; /* bottom */
char buf[BUFMAX];
char *p;
char *q = &ex->buf[ex->start + ex->len];
int start, i;
for (p=&ex->buf[ex->start]; p < q; p++) {
if (*p == '\r' || *p == '\n') break;
}
if (p >= q && p < &ex->buf[ex->bufmax]) {
ex->start += ex->len;
ex->len = 0;
return -2; /* read more */
}
for (start=p-ex->buf-1; start >= 0; start--) {
if (ex->buf[start] == '\r' || ex->buf[start] == '\n') break;
}
start++;
while ((*p == '\r' || *p == '\n') && p < q) p++;
ex->start = p - ex->buf;
if (p < q) {
ex->len = q - p;
} else {
ex->len = 0;
}
while (comm->str) {
if ((q=comm_match(&ex->buf[start], comm->str)) != NULL) break;
comm++;
}
if (q == NULL) q = &ex->buf[start];
for (i=0; q < p && i < BUFMAX-1; i++) {
if (*q == '\r' || *q == '\n') break;
buf[i] = *q++;
}
buf[i] = '\0';
return (*comm->func)(pair, buf, start);
}
int insheader(Pair *pair) { /* insert header */
ExBuf *ex = pair->b; /* bottom */
char *p;
int bufmax = ex->bufmax;
int len, i;
len = ex->start + ex->len;
for (i=ex->start; i < len; i++) {
if (ex->buf[i] == '\n') break;
}
if (i >= len) {
if (Debug > 3)
message(LOG_DEBUG, "%d TCP %d: insheader needs more",
pair->stone->sd, pair->sd);
return -1;
}
i++;
len -= i;
if (len > 0) {
bufmax -= len; /* reserve */
/* save rest header */
bcopy(&ex->buf[i], &ex->buf[bufmax], len);
}
p = pair->stone->p;
i += strnparse(&ex->buf[i], bufmax - i, &p, pair->pair, 0xFF);
ex->buf[i++] = '\r';
ex->buf[i++] = '\n';
if (Debug > 5) {
message(LOG_DEBUG,
"%d TCP %d: insheader start=%d, ins=%d, rest=%d, max=%d",
pair->stone->sd, pair->sd, ex->start, i-ex->start, len, ex->bufmax);
}
if (len > 0) /* restore */
bcopy(&ex->buf[bufmax], &ex->buf[i], len);
ex->len = i - ex->start + len;
return ex->len;
}
int rmheader(Pair *pair) { /* remove header */
ExBuf *ex = pair->b; /* bottom */
char *p;
char *q = &ex->buf[ex->start+ex->len];
int state = (pair->proto & state_mask);
if (Debug > 3) message_buf(pair, ex->len, "rm");
for (p=&ex->buf[ex->start]; p < q; p++) {
if (*p == '\r') continue;
if (*p == '\n') {
state++;
if (state >= 3) {
p++;
break; /* end of header */
}
} else {
state = 1;
}
}
if (state < 3) {
ex->len = ex->start = 0;
pair->proto = ((pair->proto & ~state_mask) | state);
return -2; /* header will continue... */
}
ex->len = q - p; /* remove header */
ex->start = p - ex->buf;
pair->proto &= ~state_mask;
return ex->len;
}
int first_read(Pair *pair) {
SOCKET sd = pair->sd;
SOCKET psd;
Pair *p = pair->pair;
ExBuf *ex;
Stone *stone = pair->stone;
int len;
if (p == NULL || (p->proto & (proto_shutdown | proto_close))
|| InvalidSocket(sd)) return -1;
ex = p->b; /* bottom */
psd = p->sd;
len = ex->len;
pair->proto &= ~proto_first_r;
if (p->proto & proto_command) { /* proxy */
switch(p->proto & proto_command) {
case command_proxy:
len = docomm(p, proxyComm);
break;
#ifdef USE_POP
case command_pop:
if (getExData(p, data_apop, 0)) len = docomm(p, popComm);
break;
#endif
case command_health:
if (!memCheck()) len = -1;
else len = docomm(p, healthComm);
break;
case command_identd:
len = docomm(p, identdComm);
break;
default:
;
}
if (len == -2) { /* read more */
if (Debug > 3) {
message(LOG_DEBUG, "%d TCP %d: read more from %d",
stone->sd, psd, sd);
}
} else if (len < 0) {
int flag = 0;
if (!(pair->proto & proto_shutdown))
if (doshutdown(pair, 2) >= 0) flag = proto_shutdown;
setclose(pair, flag);
if (ValidSocket(psd)) {
flag = 0;
if (!(p->proto & proto_shutdown))
if (doshutdown(p, 2) >= 0) flag = proto_shutdown;
setclose(p, flag);
}
return -1;
} else {
len = ex->len;
}
}
if (pair->proto & proto_ohttp) { /* over http */
len = rmheader(p);
if (len >= 0) {
if (pair->proto & proto_ohttp_s) {
commOutput(p, "HTTP/1.0 200 OK\r\n\r\n");
pair->proto &= ~proto_ohttp_s;
} else if (pair->proto & proto_ohttp_d) {
if (Debug > 3)
message(LOG_DEBUG, "%d TCP %d: request to read, "
"because response header from %d finished",
stone->sd, psd, sd);
p->proto |= (proto_select_r | proto_dirty);
}
}
}
#ifdef USE_POP
if ((pair->proto & proto_command) == command_pop /* apop */
&& !getExData(pair, data_apop, 0)) {
int i;
char *q;
for (i=ex->start; i < ex->start + ex->len; i++) {
if (ex->buf[i] == '<') { /* time stamp of APOP banner */
ExBuf *t = newExData(pair, data_apop);
if (!t) break;
q = t->buf + DATA_HEAD_LEN;
for (; i < ex->start + ex->len; i++) {
*q++ = ex->buf[i];
if (ex->buf[i] == '>') break;
}
*q = '\0';
if (Debug > 6)
message(LOG_DEBUG, "%d TCP %d: APOP challenge: %s",
stone->sd, sd, t->buf + DATA_HEAD_LEN);
break;
}
}
}
#endif
if (len <= 0 && !(pair->proto & (proto_eof | proto_close))) {
if (Debug > 8) {
message(LOG_DEBUG, "%d TCP %d: read more", stone->sd, sd);
}
pair->proto |= (proto_select_r | proto_dirty); /* read more */
if (len < 0) pair->proto |= (proto_first_r | proto_dirty);
}
return len;
}
#ifndef USE_EPOLL
static void message_select(int pri, char *msg,
fd_set *rout, fd_set *wout, fd_set *eout) {
int i, r, w, e;
for (i=0; i < FD_SETSIZE; i++) {
r = FD_ISSET(i, rout);
w = FD_ISSET(i, wout);
e = FD_ISSET(i, eout);
if (r || w || e)
message(pri, "%s %d: %c%c%c", msg,
i, (r ? 'r' : ' '), (w ? 'w' : ' '), (e ? 'e' : ' '));
}
}
#endif
/* main event loop */
void proto2fdset(Pair *pair, int isthread,
#ifdef USE_EPOLL
int epfd
#else
fd_set *routp, fd_set *woutp, fd_set *eoutp
#endif
) {
SOCKET sd;
#ifdef USE_EPOLL
struct epoll_event ev;
ev.events = EPOLLONESHOT;
ev.data.ptr = pair;
#endif
if (!pair) return;
sd = pair->sd;
if (InvalidSocket(sd)) return;
if (!isthread && (pair->proto & proto_thread)) return;
#ifdef USE_SSL
if (pair->ssl_flag & (sf_sb_on_r | sf_sb_on_w)) {
#ifdef USE_EPOLL
if (pair->ssl_flag & sf_sb_on_r) ev.events |= EPOLLIN;
if (pair->ssl_flag & sf_sb_on_w) ev.events |= EPOLLOUT;
epoll_ctl(epfd, EPOLL_CTL_MOD, sd, &ev);
#else
FD_CLR(sd, routp);
FD_CLR(sd, woutp);
if (pair->ssl_flag & sf_sb_on_r) FdSet(sd, routp);
if (pair->ssl_flag & sf_sb_on_w) FdSet(sd, woutp);
#endif
} else
#endif
if (pair->proto & proto_close) {
if (ValidSocket(sd)) {
pair->sd = INVALID_SOCKET;
closesocket(sd);
}
return;
} else if (pair->proto & proto_conninprog) {
#ifdef USE_EPOLL
ev.events |= (EPOLLOUT | EPOLLPRI);
epoll_ctl(epfd, EPOLL_CTL_MOD, sd, &ev);
#else
FdSet(sd, woutp);
FdSet(sd, eoutp);
#endif
#ifdef USE_SSL
} else if (pair->ssl_flag & sf_wb_on_r) {
#ifdef USE_EPOLL
ev.events |= EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_MOD, sd, &ev);
#else
FD_CLR(sd, woutp);
FdSet(sd, routp);
#endif
} else if (pair->ssl_flag & sf_rb_on_w) {
#ifdef USE_EPOLL
ev.events |= EPOLLOUT;
epoll_ctl(epfd, EPOLL_CTL_MOD, sd, &ev);
#else
FD_CLR(sd, routp);
FdSet(sd, woutp);
#endif
} else if (pair->ssl_flag & (sf_cb_on_r | sf_cb_on_w)) {
Pair *p = pair->pair;
if (p) {
/*
suppress hasty read/write until established connection.
assumes p is located before pair in pairs list
*/
SOCKET psd = p->sd;
if (ValidSocket(psd)) {
#ifdef USE_EPOLL
struct epoll_event pev;
pev.events = EPOLLONESHOT;
pev.data.ptr = p;
epoll_ctl(epfd, EPOLL_CTL_MOD, psd, &pev);
if (Debug > 7)
message(LOG_DEBUG, "%d TCP %d: proto2fdset2 "
"epoll_ctl %d MOD %x events=%x",
p->stone->sd, psd, epfd,
(int)pev.data.ptr, pev.events);
#else
FD_CLR(psd, routp);
FD_CLR(psd, woutp);
#endif
}
}
#ifdef USE_EPOLL
if (pair->ssl_flag & (sf_cb_on_r)) ev.events |= EPOLLIN;
if (pair->ssl_flag & (sf_cb_on_w)) ev.events |= EPOLLOUT;
epoll_ctl(epfd, EPOLL_CTL_MOD, sd, &ev);
#else
FD_CLR(sd, routp);
FD_CLR(sd, woutp);
if (pair->ssl_flag & (sf_cb_on_r)) FdSet(sd, routp);
if (pair->ssl_flag & (sf_cb_on_w)) FdSet(sd, woutp);
#endif
} else if (pair->ssl_flag & (sf_ab_on_r | sf_ab_on_w)) {
#ifdef USE_EPOLL
if (pair->ssl_flag & (sf_ab_on_r)) ev.events |= EPOLLIN;
if (pair->ssl_flag & (sf_ab_on_w)) ev.events |= EPOLLOUT;
epoll_ctl(epfd, EPOLL_CTL_MOD, sd, &ev);
#else
FD_CLR(sd, routp);
FD_CLR(sd, woutp);
if (pair->ssl_flag & (sf_ab_on_r)) FdSet(sd, routp);
if (pair->ssl_flag & (sf_ab_on_w)) FdSet(sd, woutp);
#endif
#endif
} else if (pair->proto & proto_connect) {
int isset = 0;
if (!(pair->proto & proto_eof)
&& (pair->proto & proto_select_r)) {
#ifdef USE_EPOLL
ev.events |= EPOLLIN;
#else
FdSet(sd, routp);
#endif
isset = 1;
}
if (!(pair->proto & proto_shutdown)
&& (pair->proto & proto_select_w)) {
#ifdef USE_EPOLL
ev.events |= EPOLLOUT;
#else
FdSet(sd, woutp);
#endif
isset = 1;
}
if (isset)
#ifdef USE_EPOLL
ev.events |= EPOLLPRI;
epoll_ctl(epfd, EPOLL_CTL_MOD, sd, &ev);
#else
FdSet(sd, eoutp);
#endif
}
#ifdef USE_EPOLL
if (Debug > 7)
message(LOG_DEBUG, "%d TCP %d: proto2fdset "
"epoll_ctl %d MOD %x events=%x",
pair->stone->sd, sd, epfd, (int)ev.data.ptr, ev.events);
#endif
pair->proto &= ~proto_dirty;
}
enum {
RW_LEAVE = 0,
RW_CONTINUE,
RW_EINTR,
RW_ONCE,
};
int doReadWritePair(Pair *pair, Pair *opposite,
int ready_r, int ready_w, int ready_e,
int hangup, int error) {
Pair *rPair, *wPair;
Stone *stone;
SOCKET stsd, sd, rsd, wsd;
int len;
int ret = RW_CONTINUE; /* assume to continue */
sd = pair->sd;
if (InvalidSocket(sd)) return ret;
stone = pair->stone;
stsd = stone->sd;
pair->loop++;
if (hangup && (pair->proto & proto_connect)) ready_r = 1;
if ((pair->proto & proto_conninprog)
&& (ready_w || ready_e || hangup)) {
int optval;
socklen_t optlen = sizeof(optval);
pair->proto &= ~proto_conninprog;
pair->proto |= proto_dirty;
if (getsockopt(sd, SOL_SOCKET, SO_ERROR,
(char*)&optval, &optlen) < 0) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
message(LOG_ERR, "%d TCP %d: getsockopt err=%d", stsd, sd, errno);
pair->proto |= (proto_close | proto_dirty);
if (opposite) opposite->proto |= (proto_close | proto_dirty);
return RW_LEAVE; /* leave */
}
if (optval) {
message(LOG_ERR, "%d TCP %d: connect getsockopt err=%d",
stsd, sd, optval);
pair->proto |= (proto_close | proto_dirty);
if (opposite) opposite->proto |= (proto_close | proto_dirty);
return RW_LEAVE; /* leave */
} else { /* succeed in connecting */
if (Debug > 4)
message(LOG_DEBUG, "%d TCP %d: connecting completed",
stsd, sd);
connected(pair);
}
} else if (ready_e) { /* Out-of-Band Data */
char buf[1];
len = recv(sd, buf, 1, MSG_OOB);
if (len == 1) {
if (opposite) wsd = opposite->sd; else wsd = INVALID_SOCKET;
if (Debug > 3)
message(LOG_DEBUG, "%d TCP %d: MSG_OOB 0x%02x to %d",
stsd, sd, buf[0], wsd);
if (ValidSocket(wsd)) {
len = send(wsd, buf, 1, MSG_OOB);
if (len != 1) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
message(LOG_ERR,
"%d TCP %d: send MSG_OOB ret=%d, err=%d",
stsd, sd, len, errno);
}
}
} else {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
message(LOG_ERR, "%d TCP %d: recv MSG_OOB ret=%d, err=%d",
stsd, sd, len, errno);
}
#ifdef USE_SSL
} else if (((pair->ssl_flag & sf_sb_on_r) && ready_r)
|| ((pair->ssl_flag & sf_sb_on_w) && ready_w)
) {
pair->ssl_flag &= ~(sf_sb_on_r | sf_sb_on_w);
pair->proto |= proto_dirty;
doSSL_shutdown(pair, -1);
} else if (((pair->ssl_flag & sf_cb_on_r) && ready_r)
|| ((pair->ssl_flag & sf_cb_on_w) && ready_w)) {
pair->ssl_flag &= ~(sf_cb_on_r | sf_cb_on_w);
pair->proto |= proto_dirty;
if (doSSL_connect(pair) < 0) {
/* SSL_connect fails, shutdown pairs */
if (opposite) {
if (!(opposite->proto & proto_shutdown))
if (doshutdown(opposite, 2) >= 0)
opposite->proto |= (proto_shutdown | proto_dirty);
opposite->proto |= (proto_close | proto_dirty);
}
pair->proto |= (proto_close | proto_dirty);
}
} else if (((pair->ssl_flag & sf_ab_on_r) && ready_r)
|| ((pair->ssl_flag & sf_ab_on_w) && ready_w)) {
pair->ssl_flag &= ~(sf_ab_on_r | sf_ab_on_w);
pair->proto |= proto_dirty;
if (doSSL_accept(pair) < 0) {
/* SSL_accept fails */
pair->proto |= (proto_close | proto_dirty);
if (opposite) opposite->proto |= (proto_close | proto_dirty);
return RW_LEAVE; /* leave */
}
if (pair->proto & proto_connect)
if (opposite) reqconn(opposite, &stone->dsts[0]->addr,
stone->dsts[0]->len);
#endif
} else if (((pair->proto & proto_select_r) && ready_r /* read */
#ifdef USE_SSL
&& !(pair->ssl_flag & sf_wb_on_r))
|| ((pair->ssl_flag & sf_rb_on_w)
&& ready_w /* WANT_WRITE */
#endif
)) {
#ifdef USE_SSL
pair->ssl_flag &= ~sf_rb_on_w;
pair->proto |= proto_dirty;
#endif
rPair = pair;
wPair = opposite;
rsd = sd;
if (wPair) wsd = wPair->sd; else wsd = INVALID_SOCKET;
#ifdef USE_SSL
read_pending:
#endif
rPair->proto &= ~proto_select_r;
rPair->proto |= proto_dirty;
if (rPair->proto & proto_dgram) { /* TCP <= UDP */
len = recvPairUDP(rPair);
} else {
rPair->count += REF_UNIT;
len = doread(rPair);
rPair->count -= REF_UNIT;
}
if (len < 0 || (rPair->proto & proto_close) || wPair == NULL) {
if (len == -2 /* if EOF w/ pair, */
&& !(rPair->proto & proto_shutdown)
/* and not yet shutdowned, */
&& wPair
&& !(wPair->proto & (proto_eof | proto_shutdown
| proto_close))
/* and not bi-directional EOF
and peer is not yet shutdowned, */
&& (wPair->proto & proto_connect)
&& ValidSocket(wsd)) { /* and pair is valid, */
/*
recevied EOF from rPair,
so reply SSL notify to rPair
and send SSL notify and FIN to wPair...
*/
/* no more to read */
rPair->proto |= (proto_eof | proto_dirty);
/*
Don't send notify, or further SSL_write will fail
if (rPair->ssl) doSSL_shutdown(rPair, 0);
*/
if (!(wPair->proto & proto_shutdown))
if (doshutdown(wPair, 1) >= 0) /* send FIN */
wPair->proto |= (proto_shutdown | proto_dirty);
wPair->proto &= ~proto_select_w;
wPair->proto |= proto_dirty;
} else {
/*
error, already shutdowned, or bi-directional EOF,
so reply SSL notify to rPair,
send SSL notify to wPair and shutdown wPair,
set close flag
*/
int flag = 0;
if (!(rPair->proto & proto_shutdown))
if (doshutdown(rPair, 2) >= 0)
flag = proto_shutdown;
rPair->proto &= ~proto_select_w;
rPair->proto |= proto_dirty;
setclose(rPair, (proto_eof | flag));
flag = 0;
if (wPair) {
if (!(wPair->proto & proto_shutdown))
if (doshutdown(wPair, 2) >= 0)
flag = proto_shutdown;
wPair->proto &= ~proto_select_w;
wPair->proto |= proto_dirty;
setclose(wPair, flag);
}
}
} else {
if (len > 0) {
int first_flag;
first_flag = (rPair->proto & proto_first_r);
if (first_flag) len = first_read(rPair);
if (wPair->proto & proto_dgram) {
rPair->proto |= (proto_select_r | proto_dirty);
if (sendPairUDP(wPair) < 0) {
int flag = 0;
if (!(rPair->proto & proto_shutdown))
if (doshutdown(rPair, 2) >= 0)
flag = proto_shutdown;
rPair->proto &= ~proto_select_w;
rPair->proto |= proto_dirty;
setclose(rPair, (proto_eof | flag));
}
} else if (len > 0 && ValidSocket(wsd)
&& (wPair->proto & proto_connect)
&& !(wPair->proto & (proto_shutdown | proto_close))
&& !(rPair->proto & proto_close)) {
/* (wPair->proto & proto_eof) may be true */
wPair->proto |= (proto_select_w | proto_dirty);
#ifdef ALWAYS_BUFFERING
rPair->proto |= (proto_select_r | proto_dirty);
#endif
} else {
return RW_LEAVE; /* leave */
}
} else { /* EINTR */
rPair->proto |= (proto_select_r | proto_dirty);
ret = RW_EINTR;
}
}
} else if (((pair->proto & proto_select_w) && ready_w) /* write */
#ifdef USE_SSL
|| ((pair->ssl_flag & sf_wb_on_r)
&& ready_r) /* WANT_READ */
#endif
) {
#ifdef USE_SSL
pair->ssl_flag &= ~sf_wb_on_r;
pair->proto |= proto_dirty;
#endif
wPair = pair;
rPair = opposite;
wsd = sd;
if (rPair) rsd = rPair->sd; else rsd = INVALID_SOCKET;
wPair->proto &= ~proto_select_w;
wPair->proto |= proto_dirty;
if (((wPair->proto & proto_command) == command_ihead) ||
((wPair->proto & proto_command) == command_iheads)) {
int state = (wPair->proto & state_mask);
if (state == 0) {
if (insheader(wPair) >= 0) /* insert header */
wPair->proto |= ++state;
}
}
wPair->count += REF_UNIT;
len = dowrite(wPair);
wPair->count -= REF_UNIT;
if (len < 0 || (wPair->proto & proto_close) || rPair == NULL) {
int flag = 0;
if (rPair) {
if (ValidSocket(rsd)
&& !(rPair->proto & proto_shutdown))
if (doshutdown(rPair, 2) >= 0) flag = proto_shutdown;
rPair->proto &= ~proto_select_w;
rPair->proto |= proto_dirty;
setclose(rPair, flag);
}
flag = 0;
if (!(wPair->proto & proto_shutdown))
if (doshutdown(wPair, 2) >= 0) flag = proto_shutdown;
setclose(wPair, flag);
} else {
ExBuf *ex;
ex = wPair->t; /* top */
/* (wPair->proto & proto_eof) may be true */
if (ex->len <= 0) { /* all written */
if (wPair->proto & proto_first_w) {
wPair->proto &= ~proto_first_w;
wPair->proto |= proto_dirty;
if (rPair && ValidSocket(rsd)
&& ((rPair->proto & proto_command)
== command_proxy)
&& ((rPair->proto & state_mask) == 1)) {
message_time_log(rPair);
if (Debug > 7)
message(LOG_DEBUG,
"%d TCP %d: reconnect proxy",
stsd, wPair->sd);
wPair->proto |= (proto_first_r | proto_dirty);
}
}
if (rPair && ValidSocket(rsd)
&& ((rPair->proto & proto_command)
== command_iheads)) {
if (Debug > 7)
message(LOG_DEBUG,
"%d TCP %d: insheader again",
stsd, wPair->sd);
rPair->proto &= ~state_mask;
}
if (rPair && ValidSocket(rsd)
&& (rPair->proto & proto_connect)
&& !(rPair->proto & (proto_eof | proto_close))
&& !(wPair->proto & (proto_shutdown | proto_close))
) {
#ifdef USE_SSL
if (rPair->ssl && SSL_pending(rPair->ssl)) {
if (Debug > 4)
message(LOG_DEBUG,
"%d TCP %d: SSL_pending, read again",
stsd, rPair->sd);
ret = RW_ONCE; /* read once */
goto read_pending;
}
#endif
rPair->proto |= (proto_select_r | proto_dirty);
} else {
return RW_LEAVE; /* leave */
}
} else { /* EINTR */
wPair->proto |= (proto_select_w | proto_dirty);
ret = RW_EINTR;
}
}
} else if (error) {
if (Debug > 3) message(LOG_DEBUG, "%d TCP %d: error", stsd, sd);
pair->proto |= (proto_close | proto_dirty);
if (opposite) opposite->proto |= (proto_close | proto_dirty);
return RW_LEAVE; /* leave */
}
return ret;
}
#ifndef USE_EPOLL
void doReadWrite(Pair *pair) { /* pair must be source side */
int npairs = 1;
Pair *p[2];
SOCKET stsd;
int loop;
int rx[2];
int tx[2];
int i;
fd_set ri, wi, ei;
fd_set ro, wo, eo;
struct timeval tv;
p[0] = pair;
p[1] = pair->pair;
stsd = pair->stone->sd;
if (Debug > 8) message(LOG_DEBUG, "%d TCP %d, %d: doReadWrite", stsd,
(p[0] ? p[0]->sd : INVALID_SOCKET),
(p[1] ? p[1]->sd : INVALID_SOCKET));
if (p[1]) npairs++;
FD_ZERO(&ri);
FD_ZERO(&wi);
FD_ZERO(&ei);
rx[0] = p[0]->rx;
tx[0] = p[0]->tx;
if (p[1]) {
rx[1] = p[1]->rx;
tx[1] = p[1]->tx;
} else {
rx[1] = -1;
tx[1] = -1;
}
loop = 0;
for (;;) { /* loop until timeout or EOF/error */
tv.tv_sec = 0;
tv.tv_usec = TICK_SELECT;
ro = ri;
wo = wi;
eo = ei;
for (i=0; i < npairs; i++) proto2fdset(p[i], 1, &ro, &wo, &eo);
if (Debug > 10)
message_select(LOG_DEBUG, "selectReadWrite1", &ro, &wo, &eo);
if (select(FD_SETSIZE, &ro, &wo, &eo, &tv) <= 0) goto exit_loop;
if (Debug > 10)
message_select(LOG_DEBUG, "selectReadWrite2", &ro, &wo, &eo);
for (i=0; i < npairs; i++) {
SOCKET sd;
int ret;
if (!p[i]) continue;
sd = p[i]->sd;
if (InvalidSocket(sd)) continue;
ret = doReadWritePair(p[i], p[1-i], FD_ISSET(sd, &ro),
FD_ISSET(sd, &wo), FD_ISSET(sd, &eo), 0, 0);
if (ret == RW_LEAVE) goto exit_loop;
if (ret == RW_ONCE) break; /* read once */
if (ret == RW_EINTR) loop = 0; /* EINTR */
}
if (++loop > 10) { /* check if spin occured */
if (rx[0] == p[0]->rx && tx[0] == p[0]->tx /* no update => spin */
&& (!p[1] || (rx[1] == p[1]->rx && tx[1] == p[1]->tx))) {
message(LOG_ERR, "%d TCP %d, %d: doReadWrite Can't happen "
"spin occured tx/rx: %d/%d, %d/%d", stsd,
(p[0] ? p[0]->sd : INVALID_SOCKET),
(p[1] ? p[1]->sd : INVALID_SOCKET),
tx[0], rx[0], tx[1], rx[1]);
goto exit_loop;
}
rx[0] = p[0]->rx;
tx[0] = p[0]->tx;
if (p[1]) {
rx[1] = p[1]->rx;
tx[1] = p[1]->tx;
}
loop = 0;
}
}
exit_loop:
for (i=0; i < npairs; i++) {
p[i]->proto &= ~proto_thread;
p[i]->proto |= proto_dirty;
p[i]->count -= REF_UNIT;
}
if (Debug > 8) message(LOG_DEBUG, "%d TCP %d, %d: doReadWrite end", stsd,
(p[0] ? p[0]->sd : INVALID_SOCKET),
(p[1] ? p[1]->sd : INVALID_SOCKET));
}
void asyncReadWrite(Pair *pair) { /* pair must be source side */
ASYNC_BEGIN;
doReadWrite(pair);
ASYNC_END;
}
int doPair(Pair *pair) {
SOCKET psd;
Pair *p = pair->pair;
if (!p || (pair->proto & proto_thread)) return 0;
psd = p->sd;
if (InvalidSocket(psd)) return 0;
pair->count += REF_UNIT;
p->count += REF_UNIT;
pair->proto |= (proto_thread | proto_dirty);
p->proto |= (proto_thread | proto_dirty);
if ((pair->proto & proto_command) == command_source) {
ASYNC(asyncReadWrite, pair);
} else {
ASYNC(asyncReadWrite, p);
}
return 1;
}
#endif
int doAcceptConnect(Pair *p1) {
Stone *stone = p1->stone;
Pair *p2;
int ret;
if (Debug > 8) message(LOG_DEBUG, "%d TCP %d: doAcceptConnect",
stone->sd, p1->sd);
if (!acceptCheck(p1)) {
freePair(p1);
return 0; /* pair is disposed */
}
p2 = p1->pair;
if (p2->proto & proto_ohttp_d) {
int i;
char *p = stone->p;
ExBuf *ex = p2->b; /* bottom */
i = strnparse(ex->buf, ex->bufmax - 5, &p, p1, 0xFF);
ex->buf[i++] = '\r';
ex->buf[i++] = '\n';
ex->buf[i++] = '\r';
ex->buf[i++] = '\n';
ex->len = i;
}
ret = -1;
if ((p1->proto & proto_connect) || (p1->proto & proto_dgram)) {
ret = reqconn(p2, &stone->dsts[0]->addr, /* 0 is default */
stone->dsts[0]->len);
if (ret < 0) {
freePair(p2);
freePair(p1);
return 0; /* pair is disposed */
}
}
#ifdef USE_EPOLL
if (!(p1->proto & proto_close)) {
struct epoll_event ev;
ev.events = EPOLLONESHOT;
if (!(p1->proto & proto_dgram)) {
ev.data.ptr = p1;
if (Debug > 6)
message(LOG_DEBUG, "%d TCP %d: doAcceptConnect1 "
"epoll_ctl %d ADD %x",
stone->sd, p1->sd, ePollFd, (int)ev.data.ptr);
if (epoll_ctl(ePollFd, EPOLL_CTL_ADD, p1->sd, &ev) < 0) {
message(LOG_ERR, "%d TCP %d: doAcceptConnect1 "
"epoll_ctl %d ADD err=%d",
stone->sd, p1->sd, ePollFd, errno);
}
}
if (!(p2->proto & (proto_noconnect | proto_close))) {
ev.data.ptr = p2;
if (Debug > 6)
message(LOG_DEBUG, "%d TCP %d: doAcceptConnect2 "
"epoll_ctl %d ADD %x",
stone->sd, p2->sd, ePollFd, (int)ev.data.ptr);
if (epoll_ctl(ePollFd, EPOLL_CTL_ADD, p2->sd, &ev) < 0) {
message(LOG_ERR, "%d TCP %d: doAcceptConnect2 "
"epoll_ctl %d ADD err=%d",
stone->sd, p2->sd, ePollFd, errno);
}
}
}
/* must be added to ePollFd before unset proto_thread */
#else
if (ret > 0) {
p1->proto |= (proto_thread | proto_dirty);
p2->proto |= (proto_thread | proto_dirty);
doReadWrite(p1);
}
#endif
if (!(p1->proto & proto_close)) {
p1->proto |= proto_dirty;
p2->proto |= proto_dirty;
insertPairs(p1);
return 1; /* pair is inserted */
} else {
freePair(p2);
freePair(p1);
return 0; /* pair is disposed */
}
}
void asyncAcceptConnect(Pair *pair) {
ASYNC_BEGIN;
doAcceptConnect(pair);
ASYNC_END;
}
Pair *getPairUDP(struct sockaddr *from, socklen_t fromlen, Stone *stone) {
Pair *pair;
ExBuf *t;
SockAddr *peer;
for (pair=stone->pairs->next; pair && pair->clock != -1; pair=pair->next) {
Pair *p = pair->pair;
if ((pair->proto & proto_dgram) && p && (p->proto & proto_connect)) {
ExBuf *t = getExData(pair, data_peeraddr, 0);
SockAddr *dst;
dst = (SockAddr*)(t->buf + DATA_HEAD_LEN);
if (saComp(&dst->addr, from)) {
time(&pair->clock);
return pair;
}
}
}
/* can't find pair, so create */
pair = newPair();
if (!pair) return NULL;
/* save `from' to ExBuf to check in doAcceptConnect */
bcopy(&fromlen, pair->t->buf, DATA_HEAD_LEN);
bcopy(from, pair->t->buf + DATA_HEAD_LEN, fromlen);
pair->stone = stone;
pair->proto = (proto_dgram | command_source);
pair->timeout = stone->timeout;
t = newExData(pair, data_peeraddr);
peer = (SockAddr*)(t->buf + DATA_HEAD_LEN);
peer->len = fromlen;
bcopy(from, &peer->addr, fromlen);
if (doAcceptConnect(pair)) return pair;
return NULL; /* pair is disposed */
}
void recvStoneUDP(Stone *stone) {
if (stone->proto & proto_udp_d) { /* UDP => UDP */
PktBuf *pb = recvUDP(stone);
if (pb) {
sendUDP(pb);
ungetPktBuf(pb);
}
} else { /* UDP => TCP */
struct sockaddr_storage ss;
struct sockaddr *from = (struct sockaddr*)&ss;
socklen_t fromlen = sizeof(ss);
int flags = 0;
int len;
Pair *rPair;
Pair *wPair;
ExBuf *ex;
char addrport[STRMAX+1];
ex = getExBuf();
if (!ex) {
message(LOG_CRIT, "%d UDP: out of memory", stone->sd);
return;
}
ex->start = 0;
#ifdef MSG_DONTWAIT
if (!(stone->proto & proto_block_s)) flags = MSG_DONTWAIT;
#endif
#ifdef MSG_TRUNC
flags |= MSG_TRUNC;
#endif
len = recvfrom(stone->sd, ex->buf + UDP_HEAD_LEN,
ex->bufmax - UDP_HEAD_LEN,
flags, from, &fromlen);
addrport[0] = '\0';
if (len < 0) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
addrport2strOnce(from, fromlen, proto_udp, addrport, STRMAX, 0);
message(LOG_ERR, "%d UDP: recvfrom err=%d %s",
stone->sd, errno, addrport);
ungetExBuf(ex);
return;
}
ex->buf[0] = ((unsigned)len >> 8);
ex->buf[1] = ((unsigned)len % 256);
ex->len += UDP_HEAD_LEN + len;
if (Debug > 8) {
addrport2strOnce(from, fromlen, proto_udp, addrport, STRMAX, 0);
message(LOG_DEBUG, "%d UDP: recvfrom len=%d %s",
stone->sd, len, addrport);
}
rPair = getPairUDP(from, fromlen, stone);
if (!rPair) {
message(LOG_ERR, "%d UDP: fail to get pair", stone->sd);
ungetExBuf(ex);
return;
}
rPair->rx += len;
wPair = rPair->pair;
if (wPair) {
wPair->clock = rPair->clock;
wPair->b->next = ex;
wPair->b = ex;
if (wPair->t->len <= 0) {
ExBuf *t = wPair->t;
wPair->t = wPair->t->next; /* drop top */
ungetExBuf(t);
}
wPair->proto |= (proto_select_w | proto_dirty);
}
}
}
#ifdef USE_EPOLL
void dispatch(int epfd, struct epoll_event *evs, int nevs) {
int i;
for (i=0; i < nevs; i++) {
int common;
int other;
struct epoll_event ev = evs[i];
union {
Stone stone;
Pair pair;
Origin origin;
} *p;
if (Debug > 8) message(LOG_DEBUG, "epoll %d: evs[%d].data=%x",
epfd, i, (int)ev.data.ptr);
common = *(int*)ev.data.ptr;
other = (ev.events & ~(EPOLLIN | EPOLLPRI | EPOLLOUT));
p = ev.data.ptr;
switch(common & type_mask) {
case type_stone:
if (Debug > 10 || (other && Debug > 2))
message(LOG_DEBUG, "stone %d: epoll %d events=%x type=%d",
p->stone.sd, epfd, ev.events, common);
if (p->stone.proto & proto_udp_s) {
recvStoneUDP(&p->stone);
} else {
Pair *pair = acceptPair(&p->stone);
if (pair) {
if (p->stone.proto & proto_ident) {
ASYNC(asyncAcceptConnect, pair);
} else {
doAcceptConnect(pair);
}
}
}
break;
case type_pair:
if (Debug > 10 || (other && Debug > 2))
message(LOG_DEBUG, "TCP %d: epoll %d events=%x type=%d",
p->pair.sd, epfd, ev.events, common);
doReadWritePair(&p->pair, p->pair.pair,
(ev.events & EPOLLIN) != 0,
(ev.events & EPOLLOUT) != 0,
(ev.events & EPOLLPRI) != 0,
(ev.events & EPOLLHUP) != 0,
(ev.events & EPOLLERR) != 0);
break;
case type_origin:
{
Origin *origin = &p->origin;
PktBuf *pb;
if (Debug > 10 || (other && Debug > 2))
message(LOG_DEBUG, "%d UDP %d: epoll %d events=%x type=%d",
origin->stone->sd, origin->sd,
epfd, ev.events, common);
pb = recvUDP((Stone*)origin);
if (pb) {
sendUDP(pb);
ungetPktBuf(pb);
}
}
break;
default:
message(LOG_ERR, "Irregular event events=%x type=%d",
ev.events, common);
}
}
}
#endif
int scanPairs(
#ifndef USE_EPOLL
fd_set *rop, fd_set *wop, fd_set *eop,
#endif
Pair *pairs
) {
Pair *pair;
int ret = 1;
int all;
if (Debug > 8) message(LOG_DEBUG, "scanPairs");
if (pairs) {
all = 0;
} else {
pairs = PairTop;
all = 1;
}
for (pair=pairs->next;
pair != NULL && (all || pair->clock != -1); /* until top */
pair=pair->next) {
SOCKET sd = pair->sd;
if (all && pair->clock == -1) { /* skip top */
pairs = pair;
continue;
}
if (!(pair->proto & (proto_thread | proto_dgram))
&& ValidSocket(sd)) {
time_t clock;
int idle = 1; /* assume no events happen on sd */
#ifndef USE_EPOLL
if (FD_ISSET(sd, rop) || FD_ISSET(sd, wop) || FD_ISSET(sd, eop)) {
Pair *p = pair->pair;
if (p && (p->proto & proto_dgram)) {
doReadWritePair(pair, p,
FD_ISSET(sd, rop),
FD_ISSET(sd, wop),
FD_ISSET(sd, eop), 0, 0);
idle = 0;
} else if (doPair(pair)) idle = 0;
}
#endif
if (idle && pair->timeout > 0
&& (time(&clock), clock - pair->clock > pair->timeout)) {
Pair *p = pair->pair;
if (Debug > 2) {
message(LOG_DEBUG, "%d TCP %d: idle time exceeds",
pair->stone->sd, sd);
message_pair(LOG_DEBUG, pair);
}
setclose(pair, proto_shutdown);
if (p) setclose(p, proto_shutdown);
}
}
}
if (Debug > 8) message(LOG_DEBUG, "scanPairs done");
return ret;
}
#ifndef USE_EPOLL
int scanStones(fd_set *rop, fd_set *wop, fd_set *eop) {
Stone *stone;
for (stone=stones; stone != NULL; stone=stone->next) {
int isset;
waitMutex(FdEinMutex);
isset = (FD_ISSET(stone->sd, eop) && FD_ISSET(stone->sd, &ein));
if (isset) FD_CLR(stone->sd, &ein);
freeMutex(FdEinMutex);
if (isset) {
message(LOG_ERR, "stone %d: exception", stone->sd);
} else {
if (FD_ISSET(stone->sd, rop) && FD_ISSET(stone->sd, &rin)) {
if (stone->proto & proto_udp_s) {
recvStoneUDP(stone);
} else {
Pair *pair = acceptPair(stone);
if (pair) ASYNC(asyncAcceptConnect, pair);
}
}
}
if ((stone->proto & proto_udp_s) && (stone->proto & proto_udp_d)) {
scanUDP(rop, eop, (Origin *)stone->p);
} else {
scanPairs(rop, wop, eop, stone->pairs);
}
}
return 1;
}
#endif
/* stone */
#ifdef USE_SSL
static int newMatch(void *parent, void *ptr, CRYPTO_EX_DATA *ad,
int idx, long argl, void *argp) {
char **match = malloc(sizeof(char*) * (NMATCH_MAX+1));
if (match) {
int i;
for (i=0; i <= NMATCH_MAX; i++) match[i] = NULL;
if (Debug > 4) message(LOG_DEBUG, "newMatch %d: %x",
NewMatchCount++, (int)match);
return CRYPTO_set_ex_data(ad, idx, match);
}
return 0;
}
static void freeMatch(void *parent, void *ptr, CRYPTO_EX_DATA *ad,
int idx, long argl, void *argp) {
char **match = ptr;
int i;
for (i=0; i <= NMATCH_MAX; i++) {
if (match[i]) free(match[i]);
}
if (Debug > 4) message(LOG_DEBUG, "freeMatch %d: %x",
--NewMatchCount, (int)match);
free(match);
}
static int hostcmp(char *pat, char *host) {
char a, b;
while (*pat) {
if (*pat == '*') {
pat++;
while (*host) {
if (*host == *pat) break;
host++;
}
}
a = toupper(*pat);
b = toupper(*host);
if (a != b) return a - b;
pat++;
host++;
}
return *host;
}
static int hostcheck(Pair *pair, X509 *cert, char *host) {
X509_EXTENSION *ext;
GENERAL_NAMES *ialt;
char name[LONGSTRMAX+1];
int i = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1);
if (i >= 0
&& (ext=X509_get_ext(cert, i))
&& (ialt=X509V3_EXT_d2i(ext))) {
int done = 0;
for (i=0; !done && i < sk_GENERAL_NAME_num(ialt); i++) {
GENERAL_NAME *gen = sk_GENERAL_NAME_value(ialt, i);
if (gen->type == GEN_DNS && gen->d.ia5) {
int len = gen->d.ia5->length;
if (len > LONGSTRMAX) len = LONGSTRMAX;
strncpy(name, (char*)gen->d.ia5->data, len);
name[len] = '\0';
if (hostcmp(name, host) == 0) {
if (Debug > 4)
message(LOG_DEBUG, "match %s dNSName=%s",
host, name);
done = 1; /* match */
} else if (Debug > 5) message(LOG_DEBUG, "dNSName: %s", name);
}
GENERAL_NAME_free(gen);
}
sk_GENERAL_NAME_free(ialt);
if (done) return 1;
}
if (X509_NAME_get_text_by_NID(X509_get_subject_name(cert), NID_commonName,
name, sizeof(name)) >= 0) {
if (hostcmp(name, host) == 0) {
if (Debug > 4) message(LOG_DEBUG, "match %s CN=%s", host, name);
return 1; /* match */
}
message(LOG_ERR, "%d TCP %d: connect to %s, but CN=%s",
pair->stone->sd, pair->sd, host, name);
return 0;
}
message(LOG_ERR, "%d TCP %d: no dNSName nor CN",
pair->stone->sd, pair->sd);
return 0;
}
static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
X509 *err_cert;
int err, depth, depthmax;
regex_t *re;
long serial = -1;
SSL *ssl;
Pair *pair;
StoneSSL *ss;
char buf[BUFMAX];
char *p;
err_cert = X509_STORE_CTX_get_current_cert(ctx);
err = X509_STORE_CTX_get_error(ctx);
depth = X509_STORE_CTX_get_error_depth(ctx);
ssl = X509_STORE_CTX_get_ex_data
(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
if (!ssl) {
message(LOG_ERR, "SSL callback can't get SSL object");
return 0; /* always fail */
}
pair = SSL_get_ex_data(ssl, PairIndex);
if (!pair) {
message(LOG_ERR, "SSL callback don't have ex_data, verify fails");
return 0; /* always fail */
}
if ((pair->proto & proto_command) == command_source) {
ss = pair->stone->ssl_server;
} else {
ss = pair->stone->ssl_client;
}
depthmax = ((pair->ssl_flag & sf_depth) >> sf_depth_bit);
if (depth >= depthmax) {
depthmax = depth + 1;
pair->ssl_flag = ((pair->ssl_flag & ~sf_depth)
| (depthmax << sf_depth_bit));
}
if (depth == 0) {
ASN1_INTEGER *n = X509_get_serialNumber(err_cert);
if (n) serial = ASN1_INTEGER_get(n);
if (ss->serial == -1 && serial >= 0) {
ss->serial = serial;
} else if (ss->serial >= 0 && serial != ss->serial) {
message(LOG_ERR, "%d TCP %d: SSL callback serial number mismatch "
"%lx != %lx", pair->stone->sd, pair->sd,
serial, ss->serial);
return 0; /* fail */
}
if (ss->name
&& !ss->re[depth]
&& !hostcheck(pair, err_cert, ss->name)) return 0;
}
if (Debug > 3)
message(LOG_DEBUG,
"%d TCP %d: callback: err=%d, depth=%d/%d, preverify=%d",
pair->stone->sd, pair->sd, err, depth, depth - depthmax,
preverify_ok);
p = X509_NAME_oneline(X509_get_subject_name(err_cert), buf, BUFMAX-1);
if (!p) return 0;
if (ss->verbose) message(LOG_DEBUG, "%d TCP %d: [depth%d=%s]",
pair->stone->sd, pair->sd, depth, p);
if (depth > ss->depth) {
preverify_ok = 0;
X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_CHAIN_TOO_LONG);
}
if (!preverify_ok) {
#ifdef CRYPTOAPI
if (ss->sslparm & sslparm_storeca) {
int ret = CryptoAPI_verify_certificate(err_cert);
if (ret < 0) {
if (ss->verbose)
message(LOG_DEBUG, "%d TCP %d: verify error err=%d %s, "
"CryptoAPI verify %ld",
pair->stone->sd, pair->sd,
err, X509_verify_cert_error_string(err),
ERR_get_error());
return 0;
} else if (ret == 0) {
if (ss->verbose)
message(LOG_DEBUG, "%d TCP %d: verify error err=%d %s, "
"CryptoAPI certificate is not trusted",
pair->stone->sd, pair->sd,
err, X509_verify_cert_error_string(err));
return 0;
}
} else {
#endif
if (ss->verbose)
message(LOG_DEBUG, "%d TCP %d: verify error err=%d %s",
pair->stone->sd, pair->sd,
err, X509_verify_cert_error_string(err));
if (!(ss->sslparm & sslparm_ignore)) return 0;
#ifdef CRYPTOAPI
}
#endif
}
re = ss->re[DEPTH_MAX - depthmax + depth];
if (!re) re = ss->re[depth];
if (depth < DEPTH_MAX && re) {
SSL_SESSION *sess = NULL;
regmatch_t pmatch[NMATCH_MAX];
char **match;
err = regexec(re, p, (size_t)NMATCH_MAX, pmatch, 0);
if (Debug > 3) message(LOG_DEBUG, "%d TCP %d: regexec%d=%d",
pair->stone->sd, pair->sd, depth, err);
if (err) return 0; /* not match */
sess = SSL_get1_session(ssl);
if (sess && (match = SSL_SESSION_get_ex_data(sess, MatchIndex))) {
int i;
int j = 1;
if (serial >= 0) {
char str[STRMAX+1];
int len;
snprintf(str, STRMAX, "%lx", serial);
len = strlen(str);
if (match[0]) free(match[0]);
match[0] = malloc(len+1);
if (match[0]) {
strncpy(match[0], str, len);
match[0][len] = '\0';
}
}
for (i=1; i <= NMATCH_MAX; i++) {
if (match[i]) continue;
if (pmatch[j].rm_so >= 0) {
int len = pmatch[j].rm_eo - pmatch[j].rm_so;
match[i] = malloc(len+1);
if (match[i]) {
strncpy(match[i], p + pmatch[j].rm_so, len);
match[i][len] = '\0';
if (Debug > 4) message(LOG_DEBUG, "%d TCP %d: \\%d=%s",
pair->stone->sd, pair->sd,
i, match[i]);
}
j++;
}
}
} else {
message(LOG_ERR,
"%d TCP %d: SSL callback can't get session's ex_data",
pair->stone->sd, pair->sd);
}
if (sess) SSL_SESSION_free(sess);
} else {
if (Debug > 3) message(LOG_DEBUG, "%d TCP %d: re%d=NULL",
pair->stone->sd, pair->sd, depth);
}
return 1; /* if re is null, always succeed */
}
static int passwd_callback(char *buf, int size, int rwflag, void *passwd) {
strncpy(buf, (char *)(passwd), size);
buf[size-1] = '\0';
return(strlen(buf));
}
#ifndef OPENSSL_NO_TLSEXT
static int ssl_servername_callback(SSL *ssl, int *ad, void *arg) {
Pair *pair = SSL_get_ex_data(ssl, PairIndex);
StoneSSL *ss = pair->stone->ssl_server;
Stone *stone;
const char *name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
if (!ss || !ss->name) return SSL_TLSEXT_ERR_NOACK;
if (!name) {
if (ss && ss->verbose)
message(LOG_DEBUG, "%d TCP %d: No servername, expects: %s",
pair->stone->sd, pair->sd, ss->name);
return SSL_TLSEXT_ERR_OK;
}
if (strcmp(name, ss->name) == 0) return SSL_TLSEXT_ERR_OK;
for (stone=pair->stone->children; stone; stone=stone->children) {
StoneSSL *sn = stone->ssl_server;
if (!sn || !sn->name) return SSL_TLSEXT_ERR_NOACK;
if (strcmp(name, sn->name) == 0) {
if (sn->verbose)
message(LOG_DEBUG, "%d TCP %d: Switching server context: %s",
stone->sd, pair->sd, sn->name);
SSL_set_SSL_CTX(ssl, sn->ctx);
pair->stone = stone;
return SSL_TLSEXT_ERR_OK;
}
}
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
#endif
StoneSSL *mkStoneSSL(SSLOpts *opts, int isserver) {
StoneSSL *ss;
int err;
int i;
ss = malloc(sizeof(StoneSSL));
if (!ss) {
memerr:
message(LOG_CRIT, "Out of memory");
exit(1);
}
ss->verbose = opts->verbose;
ss->shutdown_mode = opts->shutdown_mode;
ss->name = opts->servername;
ss->ctx = SSL_CTX_new(opts->meth);
if (!ss->ctx) {
message(LOG_ERR, "SSL_CTX_new error");
goto error;
}
SSL_CTX_set_options(ss->ctx, opts->off);
SSL_CTX_set_mode(ss->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
SSL_CTX_set_verify(ss->ctx, opts->mode, opts->callback);
SSL_CTX_set_verify_depth(ss->ctx, opts->depth + 1);
ss->depth = opts->depth;
ss->serial = opts->serial;
ss->lbmod = opts->lbmod;
ss->lbparm = opts->lbparm;
if (opts->caFile || opts->caPath) {
if (!SSL_CTX_load_verify_locations(ss->ctx,
opts->caFile, opts->caPath)) {
message(LOG_ERR, "SSL_CTX_load_verify_locations(%s,%s) error",
opts->caFile, opts->caPath);
goto error;
}
if (opts->vflags)
X509_STORE_set_flags(SSL_CTX_get_cert_store(ss->ctx),
opts->vflags);
}
if (opts->pfxFile) {
FILE *fp = fopen(opts->pfxFile, "r");
PKCS12 *p12;
EVP_PKEY *key;
X509 *cert;
if (!fp) {
message(LOG_ERR, "Can't open pfx file: %s", opts->pfxFile);
goto error;
}
p12 = d2i_PKCS12_fp(fp, NULL);
if (!p12) {
message(LOG_ERR, "Can't read pfx file: %s", opts->pfxFile);
fclose(fp);
goto error;
}
fclose(fp);
key = NULL;
cert = NULL;
if (!PKCS12_parse(p12, opts->passwd, &key, &cert, NULL)) {
message(LOG_ERR, "Can't parse PKCS12(%s) %s",
opts->pfxFile, ERR_error_string(ERR_get_error(), NULL));
goto error;
}
if (cert) {
if (!SSL_CTX_use_certificate(ss->ctx, cert)) {
message(LOG_ERR, "SSL_CTX_use_certificate(%s) %s",
opts->pfxFile,
ERR_error_string(ERR_get_error(), NULL));
X509_free(cert);
goto error;
}
X509_free(cert);
}
if (key) {
if (!SSL_CTX_use_PrivateKey(ss->ctx, key)) {
message(LOG_ERR, "SSL_CTX_use_PrivateKey(%s) %s",
opts->pfxFile,
ERR_error_string(ERR_get_error(), NULL));
EVP_PKEY_free(key);
goto error;
}
EVP_PKEY_free(key);
}
PKCS12_free(p12);
} else {
if (opts->passwd) {
SSL_CTX_set_default_passwd_cb(ss->ctx, passwd_callback);
SSL_CTX_set_default_passwd_cb_userdata(ss->ctx, opts->passwd);
}
if (opts->keyFile
&& !SSL_CTX_use_PrivateKey_file
(ss->ctx, opts->keyFile, X509_FILETYPE_PEM)) {
message(LOG_ERR, "SSL_CTX_use_PrivateKey_file(%s) %s",
opts->keyFile, ERR_error_string(ERR_get_error(), NULL));
goto error;
}
if (opts->certFile
&& !SSL_CTX_use_certificate_file(ss->ctx, opts->certFile,
X509_FILETYPE_PEM)) {
message(LOG_ERR, "SSL_CTX_use_certificate_file(%s) error",
opts->certFile);
goto error;
}
}
ss->sslparm = 0;
if (opts->useSNI) ss->sslparm |= sslparm_sni;
if (opts->certIgnore) ss->sslparm |= sslparm_ignore;
#ifdef CRYPTOAPI
if (opts->certStoreCA) ss->sslparm |= sslparm_storeca;
if (opts->certStore) {
if (!SSL_CTX_use_CryptoAPI_certificate(ss->ctx, opts->certStore)) {
message(LOG_ERR, "Can't load certificate \"%s\" "
"from Microsoft Certificate Store, %s",
opts->certStore, ERR_error_string(ERR_get_error(), NULL));
goto error;
}
}
#endif
if (opts->cipherList
&& !SSL_CTX_set_cipher_list(ss->ctx, opts->cipherList)) {
message(LOG_ERR, "SSL_CTX_set_cipher_list(%s) error",
opts->cipherList);
goto error;
}
for (i=0; i < DEPTH_MAX; i++) {
if (opts->regexp[i]) {
ss->re[i] = malloc(sizeof(regex_t));
if (!ss->re) goto memerr;
err = regcomp(ss->re[i], opts->regexp[i], REG_EXTENDED|REG_ICASE);
if (err) {
message(LOG_ERR, "RegEx compiling error %d", err);
goto error;
}
if (Debug > 5) {
message(LOG_DEBUG, "regexp[%d]=%s", i, opts->regexp[i]);
}
} else {
ss->re[i] = NULL;
}
}
if (isserver) {
#ifndef OPENSSL_NO_TLSEXT
if (ss->sslparm & sslparm_sni) {
SSL_CTX_set_tlsext_servername_callback
(ss->ctx, ssl_servername_callback);
}
#endif
if (opts->sid_ctx) {
int ret;
int len = strlen((char*)opts->sid_ctx);
ret = SSL_CTX_set_session_id_context(ss->ctx, opts->sid_ctx, len);
if (!ret) {
len = SSL_MAX_SSL_SESSION_ID_LENGTH;
opts->sid_ctx[len] = '\0';
message(LOG_ERR, "Too long sid_ctx, truncated to '%s'",
opts->sid_ctx);
ret = SSL_CTX_set_session_id_context(ss->ctx,
opts->sid_ctx, len);
if (!ret) {
message(LOG_ERR, "SSL_CTX_set_session_id_context error");
}
}
}
SSL_CTX_set_session_cache_mode
(ss->ctx, (SSL_SESS_CACHE_SERVER | SSL_SESS_CACHE_NO_AUTO_CLEAR));
}
return ss;
error:
if (opts->verbose)
message(LOG_INFO, "%s", ERR_error_string(ERR_get_error(), NULL));
exit(1);
}
void rmStoneSSL(StoneSSL *ss) {
int i;
SSL_CTX_free(ss->ctx);
for (i=0; i < DEPTH_MAX; i++) {
if (ss->re[i]) {
regfree(ss->re[i]);
free(ss->re[i]);
}
}
free(ss);
}
char *exPatFile(char *pat, char *name, char *src, char *dst) {
char str[STRMAX+1];
char *p;
int pos, len, nlen, slen, dlen;
int l;
if (!name) name = "";
if (!src) src = "";
if (!dst) dst = "";
nlen = strlen(name);
slen = strlen(src);
dlen = strlen(dst);
len = 0;
for (pos=0; pos < STRMAX; pos++) {
if (pat[pos] == '\0') {
str[len] = '\0';
break;
} else if (pat[pos] == '%') {
switch (pat[++pos]) {
case 'n': l = nlen; p = name; break;
case 's': l = slen; p = src; break;
case 't': l = dlen; p = dst; break;
default:
l = 1;
p = &pat[pos];
}
if (len + l >= STRMAX) l = STRMAX - len;
strncpy(str+len, p, l);
len += l;
} else {
str[len++] = pat[pos];
}
}
str[STRMAX] = '\0';
return strdup(str);
}
void exPatOpts(SSLOpts *opts, char *src, char *dst) {
if (opts->pfxFilePat) {
opts->pfxFile = exPatFile(opts->pfxFilePat,
opts->servername, src, dst);
if (Debug > 3) message(LOG_DEBUG, "exPatPfx: %s => %s",
opts->pfxFilePat, opts->pfxFile);
} else {
if (opts->certFilePat) {
opts->certFile = exPatFile(opts->certFilePat,
opts->servername, src, dst);
if (Debug > 3) message(LOG_DEBUG, "exPatCert: %s => %s",
opts->certFilePat, opts->certFile);
}
if (opts->keyFilePat) {
opts->keyFile = exPatFile(opts->keyFilePat,
opts->servername, src, dst);
if (Debug > 3) message(LOG_DEBUG, "exPatKey: %s => %s",
opts->keyFilePat, opts->keyFile);
}
}
if (opts->passFilePat) {
opts->passFile = exPatFile(opts->passFilePat,
opts->servername, src, dst);
if (Debug > 3) message(LOG_DEBUG, "exPatPass: %s => %s",
opts->passFilePat, opts->passFile);
}
if (opts->passFile) {
FILE *fp = fopen(opts->passFile, "r");
char str[STRMAX+1];
int i;
if (!fp) {
message(LOG_ERR, "Can't open passwd file: %s", opts->passFile);
exit(1);
}
for (i=0; i < STRMAX; i++) {
int c = getc(fp);
if (c == '\r' || c == '\n' || c == EOF) break;
str[i] = c;
}
str[i] = '\0';
fclose(fp);
opts->passwd = strdup(str);
}
}
#endif
void rmoldstone(void) {
Stone *stone, *next;
stone = oldstones;
oldstones = NULL;
for ( ; stone != NULL; stone=next) {
next = stone->next;
if (stone->port) {
#ifdef USE_EPOLL
epoll_ctl(ePollFd, EPOLL_CTL_DEL, stone->sd, NULL);
#else
waitMutex(FdRinMutex);
waitMutex(FdEinMutex);
FD_CLR(stone->sd, &rin);
FD_CLR(stone->sd, &ein);
freeMutex(FdEinMutex);
freeMutex(FdRinMutex);
#endif
closesocket(stone->sd);
}
#ifdef USE_SSL
if (stone->ssl_server) rmStoneSSL(stone->ssl_server);
if (stone->ssl_client) rmStoneSSL(stone->ssl_client);
#endif
free(stone);
}
}
void rmoldconfig(void) {
int i;
for (i=0; i < OldConfigArgc; i++) {
free(OldConfigArgv[i]);
}
OldConfigArgc = 0;
free(OldConfigArgv);
OldConfigArgv = NULL;
}
void repeater(void) {
int ret;
static int spin = 0;
static int nerrs = 0;
static time_t scantime = 0;
time_t now;
Pair *pair;
#ifdef USE_EPOLL
int timeout;
struct epoll_event evs[EVSMAX];
for (pair=PairTop; pair != NULL; pair=pair->next)
if (pair->clock != -1 && /* not top */
!(pair->proto & proto_thread) && (pair->proto & proto_dirty))
proto2fdset(pair, 0, ePollFd);
if (conns.next || trash.next || spin > 0 || AsyncCount > 0) {
if (AsyncCount == 0 && spin > 0) spin--;
timeout = TICK_SELECT / 1000;
} else if (MinInterval > 0) {
timeout = MinInterval * 1000;
} else {
timeout = -1;
}
ret = epoll_wait(ePollFd, evs, EVSMAX, timeout);
if (Debug > 10) {
message(LOG_DEBUG, "epoll %d: %d", ePollFd, ret);
}
#else
struct timeval tv, *timeout;
fd_set rout, wout, eout;
rout = rin;
wout = win;
eout = ein;
for (pair=PairTop; pair != NULL; pair=pair->next)
if (pair->clock != -1 && /* not top */
!(pair->proto & proto_thread))
proto2fdset(pair, 0, &rout, &wout, &eout);
if (conns.next || trash.next || spin > 0 || AsyncCount > 0) {
if (AsyncCount == 0 && spin > 0) spin--;
timeout = &tv;
timeout->tv_sec = 0;
timeout->tv_usec = TICK_SELECT;
} else if (MinInterval > 0) {
timeout = &tv;
timeout->tv_sec = MinInterval;
timeout->tv_usec = 0;
} else {
timeout = NULL; /* block indefinitely */
}
if (Debug > 10) {
message(LOG_DEBUG, "select main(%ld)",
(timeout ? timeout->tv_usec : 0));
message_select(LOG_DEBUG, "select main IN ", &rout, &wout, &eout);
}
ret = select(FD_SETSIZE, &rout, &wout, &eout, timeout);
if (Debug > 10) {
message(LOG_DEBUG, "select main: %d", ret);
message_select(LOG_DEBUG, "select main OUT", &rout, &wout, &eout);
}
#endif
if (ret > 0) {
nerrs = 0;
spin = SPIN_MAX;
#ifdef USE_EPOLL
dispatch(ePollFd, evs, ret);
#else
(void)(scanStones(&rout, &wout, &eout) > 0);
#endif
} else if (ret < 0) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
if (errno != EINTR) {
#ifdef USE_EPOLL
message(LOG_ERR, "epoll %d error err=%d", ePollFd, errno);
#else
message(LOG_ERR, "select error err=%d", errno);
#endif
if (++nerrs >= NERRS_MAX) {
#ifdef USE_EPOLL
message(LOG_ERR, "epoll %d error %d times, exiting",
ePollFd, nerrs);
#else
message(LOG_ERR, "select error %d times, exiting", nerrs);
message_select(LOG_INFO, "IN", &rin, &win, &ein);
#endif
message_pairs(LOG_INFO);
message_origins(LOG_INFO);
message_conns(LOG_INFO);
exit(1);
}
}
usleep(TICK_SELECT);
}
if (conns.next) scanConns();
time(&now);
if (now == scantime) return;
scantime = now;
if (backups && scantime - lastScanBackups >= MinInterval) {
lastScanBackups = scantime;
scanBackups();
}
#ifdef USE_EPOLL
if (PairTop) scanPairs(NULL);
if (OriginTop) scanUDP(NULL);
#endif
if (PairTop) scanClose(NULL);
if (oldstones) rmoldstone();
if (OldConfigArgc) rmoldconfig();
#ifdef USE_SSL
ERR_remove_state(0);
#endif
}
int reusestone(Stone *stone) {
Stone *s;
if (!oldstones) return 0;
for (s=oldstones; s != NULL; s=s->next) {
if (s->port == stone->port && s->proto == stone->proto) {
if (Debug > 5)
message(LOG_DEBUG, "stone %d: reused port %d", s->sd, s->port);
stone->sd = s->sd;
s->port = 0;
return 1;
}
}
return 0;
}
#ifdef NO_FAMILY_T
typedef int sa_family_t;
#endif
void mkXhostsExt(char *host, char *str, XHosts *ext) {
int kind = 0;
char *top = NULL; /* dummy init to suppress warnings */
u_long num = 0;
int i = 0;
do {
switch(kind) {
case -3: /* pass if digit or '.' until ',' */
if (str[i] == '.') break;
case -2: /* pass if digit until ',' */
if (isdigit(str[i])) break;
case -1: /* pass ',' */
if (str[i] == ',' || str[i] == '\0') {
kind = 0; /* found next ext */
break;
}
error:
message(LOG_ERR, "Unknown extension: \"%s\" in %s/%s",
&str[i], host, str);
exit(1);
case 0:
top = &str[i];
if (isdigit(*top)) {
num = *top - '0';
kind = 1;
break;
}
if (*top == 'v') {
i++;
if (top[1] == '4') {
ext->xhost.addr.sa_family = AF_INET;
#ifdef AF_INET6
} else if (top[1] == '6') {
ext->xhost.addr.sa_family = AF_INET6;
#endif
} else {
goto error;
}
kind = -1; /* expect ',' or end of string */
break;
}
if (*top == 'p') {
if (isdigit(top[1])) {
ext->mode = atoi(top+1);
} else {
ext->mode = 1;
}
kind = -2; /* skip to the next ext */
break;
}
goto error;
case 1: /* net mask */
if (str[i] == ',' || str[i] == '\0') {
ext->mbits = num;
if (ext->mbits > 32) {
#ifdef AF_INET6
/* force to set IPv6 */
ext->xhost.addr.sa_family = AF_INET6;
}
if (ext->mbits > 128) {
#endif
goto error;
}
kind = 0; /* found next ext */
break;
}
case 2: /* nnn.<nnn>.nnn.nnn */
case 3: /* nnn.nnn.<nnn>.nnn */
if (str[i] == '.') {
i++;
num <<= 8;
kind++;
}
case 4: /* nnn.nnn.nnn.<nnn> */
if (isdigit(str[i])) {
num = ((num & 0xFFFFFF00)
| ((num & 0xFF) * 10 + (str[i] - '0')));
break;
}
ext->xhost.addr.sa_family = AF_INET; /* force to set IPv4 */
for (ext->mbits=0; ext->mbits < 32 && num; ext->mbits++) {
if (!(num & 0x80000000)) {
message(LOG_ERR, "netmask by bits pattern "
"is deprecated: %s/%s", host, top);
exit(1);
}
num <<= 1;
}
i--; /* unget */
kind = -1; /* expect ',' or end of string */
break;
default:
message(LOG_ERR, "Can't happen: kind=%d in mkXhostsExt", kind);
exit(1);
}
} while (str[i++]);
if (Debug > 9) message(LOG_DEBUG, "mkXhostsExt: host=%s ext=%s "
"family=%d mbits=%d mode=%d",
host, str, ext->xhost.addr.sa_family,
ext->mbits, ext->mode);
}
XHosts *mkXhosts(int nhosts, char *hosts[], sa_family_t family, char *mesg) {
XHosts *top = NULL;
XHosts *bot = NULL;
char xhost[STRMAX+1];
int allow = 1;
int i;
char *p;
for (i=0; i < nhosts; i++) {
XHosts *new;
if (Debug > 10) message(LOG_DEBUG, "xhost[%d]=\"%s\"", i, hosts[i]);
if (!strcmp(hosts[i], "!")) {
new = malloc(XHostsBaseSize);
if (!new) goto memerr;
new->mbits = -1;
allow = !allow;
} else {
short mbits = -1;
short mode = 0;
struct sockaddr_storage ss;
struct sockaddr *sa = (struct sockaddr*)&ss;
socklen_t salen = sizeof(ss);
strcpy(xhost, hosts[i]);
p = strchr(xhost, '/');
if (p) {
XHosts ext;
*p++ = '\0';
ext.mbits = mbits;
ext.mode = mode;
ext.xhost.addr.sa_family = family;
mkXhostsExt(xhost, p, &ext);
mbits = ext.mbits;
mode = ext.mode;
family = ext.xhost.addr.sa_family;
}
sa->sa_family = family;
if (!host2sa(xhost, NULL, sa, &salen, NULL, NULL, 0)) exit(1);
new = malloc(XHostsBaseSize+salen);
if (!new) goto memerr;
new->xhost.len = salen;
bcopy(sa, &new->xhost.addr, salen);
if (mbits < 0) {
if (sa->sa_family == AF_INET) {
mbits = 32;
#ifdef AF_INET6
} else if (sa->sa_family == AF_INET6) {
mbits = 128;
#endif
} else {
message(LOG_ERR, "mkXhosts: unknown family=%d",
sa->sa_family);
exit(1);
}
}
new->mbits = mbits;
new->mode = mode;
if (mesg) {
char str[STRMAX+1];
int pos = 0;
addr2str(&new->xhost.addr, new->xhost.len,
str, STRMAX, NI_NUMERICHOST);
pos = strlen(str);
snprintf(str+pos, STRMAX-pos, "/%d", new->mbits);
pos += strlen(str+pos);
message(LOG_DEBUG, "%s%s is %s", mesg, str,
(allow ? "permitted" : "denied"));
}
}
new->next = NULL;
if (!top) top = new;
if (bot) bot->next = new;
bot = new;
}
return top;
memerr:
message(LOG_CRIT, "Out of memory");
exit(1);
}
int mkPortXhosts(int argc, int i, char *argv[]) {
PortXHosts *pxh;
XPorts *top = NULL;
XPorts *bot = NULL;
char **hosts;
char *p, *q;
char str[STRMAX+1];
int isnum;
int from;
int j;
i++;
if (!strcmp(argv[i], "--")) {
portXHosts = NULL;
return i;
}
p = argv[i];
q = str;
isnum = 1;
from = -1;
for (;;) {
if (*p == ',' || *p == '-' || *p == '\0') {
int port;
*q = '\0';
if (str[0]) {
if (isnum) port = atoi(str);
else {
struct sockaddr_storage ss;
struct sockaddr *sa = (struct sockaddr*)&ss;
socklen_t salen = sizeof(ss);
if (!host2sa(NULL, str, sa, &salen, NULL, NULL, 0)) {
goto opterr;
}
port = getport(sa);
}
} else {
opterr:
message(LOG_ERR, "Illegal option: -x requires port list: %s",
argv[i]);
exit(1);
}
if (*p == '-') {
from = port;
} else {
XPorts *new = malloc(sizeof(XPorts));
if (!new) goto memerr;
new->next = NULL;
if (from >= 0) new->from = from;
else new->from = port;
new->end = port;
from = -1;
if (bot) bot->next = new;
bot = new;
if (!top) top = new;
if (*p == '\0') break;
}
p++;
q = str;
isnum = 1;
continue;
} else if (!isdigit(*p)) {
isnum = 0;
}
*q++ = *p++;
}
if (Debug > 5) {
char buf[BUFMAX];
XPorts *cur;
j = 0;
for (cur=top; j < BUFMAX && cur; cur=cur->next) {
if (j > 0) buf[j++] = ',';
snprintf(buf+j, BUFMAX-1-j, "%d-%d", cur->from, cur->end);
j += strlen(buf+j);
}
buf[j] = '\0';
message(LOG_DEBUG, "XPorts: %s", buf);
}
i++;
hosts = &argv[i];
j = 0;
for (; i < argc; i++, j++) if (!strcmp(argv[i], "--")) break;
pxh = malloc(sizeof(PortXHosts));
if (!pxh) goto memerr;
pxh->ports = top;
if (Debug > 5) p = "XHosts: "; else p = NULL;
pxh->xhosts = mkXhosts(j, hosts, AF_UNSPEC, p);
pxh->next = portXHosts;
portXHosts = pxh;
return i;
memerr:
message(LOG_CRIT, "Out of memory");
exit(1);
}
Stone *getStone(struct sockaddr *sa, socklen_t salen, int proto) {
Stone *stone;
proto &= proto_udp_s;
for (stone=stones; stone != NULL; stone=stone->next) {
if ((stone->proto & proto_udp_s) == proto
&& saComp(&stone->listen->addr, sa)) {
return stone;
}
}
return NULL;
}
/* make stone */
Stone *mkstone(
char *dhost, /* destination hostname */
char *dserv, /* destination port */
char *host, /* listening host */
char *serv, /* listening port */
int nhosts, /* # of hosts to permit */
char *hosts[], /* hosts to permit */
int proto) { /* UDP/TCP/SSL */
Stone *stone;
Stone *st;
struct sockaddr_storage ss;
struct sockaddr *sa = (struct sockaddr*)&ss;
socklen_t salen = sizeof(ss);
int satype;
int saproto = 0;
sa_family_t family;
char *mesg;
char str[STRMAX+1];
stone = calloc(1, sizeof(Stone));
if (!stone) {
message(LOG_CRIT, "Out of memory");
exit(1);
}
stone->next = NULL;
stone->children = NULL;
stone->parent = NULL;
stone->common = type_stone;
stone->p = NULL;
stone->timeout = PairTimeOut;
if (proto & proto_udp_s) {
satype = SOCK_DGRAM;
saproto = IPPROTO_UDP;
} else {
satype = SOCK_STREAM;
saproto = IPPROTO_TCP;
}
#ifdef AF_LOCAL
if (proto & proto_unix_s) {
struct sockaddr_un *sun = (struct sockaddr_un*)sa;
salen = sizeof(struct sockaddr_un);
bzero(sa, salen);
sun->sun_family = AF_LOCAL;
snprintf(sun->sun_path, sizeof(sun->sun_path)-1, "%s", host);
saproto = 0;
} else
#endif
#ifdef AF_INET6
if (proto & proto_v6_s) {
struct sockaddr_in6 *sin6p = (struct sockaddr_in6*)sa;
sa->sa_family = AF_INET6;
if (!host2sa(host, serv, sa, &salen, &satype, &saproto, AI_PASSIVE))
exit(1);
stone->port = ntohs(sin6p->sin6_port);
} else
#endif
{
struct sockaddr_in *sinp = (struct sockaddr_in*)sa;
sa->sa_family = AF_INET;
if (!host2sa(host, serv, sa, &salen, &satype, &saproto, AI_PASSIVE))
exit(1);
stone->port = ntohs(sinp->sin_port);
}
if ((proto & proto_command) == command_proxy
|| (proto & proto_command) == command_health
|| (proto & proto_command) == command_identd) {
stone->ndsts = 1;
if ((proto & proto_command) == command_proxy) {
stone->dsts = malloc(sizeof(SockAddr*) + sizeof(PortXHosts*));
if (stone->dsts) ((PortXHosts**)stone->dsts)[1] = portXHosts;
/* only proxy stone needs portXHosts,
so we divert dsts into holding current portXHosts */
} else {
stone->dsts = malloc(sizeof(SockAddr*)); /* dummy */
}
if (!stone->dsts) goto memerr;
stone->dsts[0] = saDup(sa, salen); /* dummy */
#ifdef AF_LOCAL
} else if (proto & proto_unix_d) {
struct sockaddr_storage dss;
struct sockaddr_un *sun = (struct sockaddr_un*)&dss;
stone->ndsts = 1;
stone->dsts = malloc(sizeof(SockAddr*));
if (!stone->dsts) goto memerr;
bzero(sun, sizeof(dss));
sun->sun_family = AF_LOCAL;
snprintf(sun->sun_path, sizeof(sun->sun_path)-1, "%s", dhost);
stone->dsts[0] = saDup((struct sockaddr*)sun,
sizeof(struct sockaddr_un));
if (!stone->dsts[0]) goto memerr;
#endif
} else {
struct sockaddr_storage dss;
struct sockaddr *dsa = (struct sockaddr*)&dss;
socklen_t dsalen = sizeof(dss);
int dsatype;
int dsaproto;
LBSet *lbset;
#ifdef AF_INET6
if (proto & proto_v6_d) dsa->sa_family = AF_INET6;
else
#endif
dsa->sa_family = AF_INET;
if (proto & proto_udp_d) {
dsatype = SOCK_DGRAM;
dsaproto = IPPROTO_UDP;
} else {
dsatype = SOCK_STREAM;
dsaproto = IPPROTO_TCP;
}
if (!host2sa(dhost, dserv, dsa, &dsalen, &dsatype, &dsaproto, 0)) {
exit(1);
}
lbset = findLBSet(dsa);
if (lbset) {
stone->ndsts = lbset->ndsts;
stone->dsts = lbset->dsts;
} else {
stone->ndsts = 1;
stone->dsts = malloc(sizeof(SockAddr*));
if (!stone->dsts) {
memerr:
message(LOG_CRIT, "Out of memory");
exit(1);
}
stone->dsts[0] = saDup(dsa, dsalen);
if (!stone->dsts[0]) goto memerr;
}
}
stone->proto = proto;
stone->from = ConnectFrom;
if (!reusestone(stone)) { /* recycle stone */
stone->sd = socket(sa->sa_family, satype, saproto);
if (InvalidSocket(stone->sd)) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
message(LOG_ERR, "stone %d: Can't get socket "
"family=%d type=%d proto=%d err=%d",
stone->sd, sa->sa_family, satype, saproto, errno);
exit(1);
}
#ifdef IPV6_V6ONLY
if ((proto & proto_v6_s) && (proto & proto_ip_only_s)) {
int i = 1;
setsockopt(stone->sd, IPPROTO_IPV6, IPV6_V6ONLY,
(char*)&i, sizeof(i));
}
#endif
if (!(proto & proto_udp_s) && ReuseAddr) {
int i = 1;
setsockopt(stone->sd, SOL_SOCKET, SO_REUSEADDR,
(char*)&i, sizeof(i));
}
if ((st=getStone(sa, salen, proto))) {
closesocket(stone->sd);
stone->parent = st;
stone->children = st->children;
st->children = stone;
stone->sd = st->sd;
} else if (!DryRun) {
if (bind(stone->sd, sa, salen) < 0) {
char str[STRMAX+1];
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
addrport2str(sa, salen, 0, str, STRMAX, 0);
str[STRMAX] = '\0';
message(LOG_ERR, "stone %d: Can't bind %s err=%d",
stone->sd, str, errno);
exit(1);
}
if (!(stone->proto & proto_block_s)) {
#ifdef WINDOWS
u_long param;
param = 1;
ioctlsocket(stone->sd, FIONBIO, ¶m);
#else
fcntl(stone->sd, F_SETFL, O_NONBLOCK);
#endif
}
if (stone->port == 0) {
salen = sizeof(ss);
if (getsockname(stone->sd, sa, &salen) >= 0) {
stone->port = getport(sa);
}
}
if (!(proto & proto_udp_s)) { /* TCP */
if (listen(stone->sd, BacklogMax) < 0) {
#ifdef WINDOWS
errno = WSAGetLastError();
#endif
message(LOG_ERR, "stone %d: Can't listen err=%d",
stone->sd, errno);
exit(1);
}
}
} /* !DryRun */
}
stone->listen = saDup(sa, salen);
#ifdef USE_SSL
if (proto & proto_ssl_s) { /* server side SSL */
exPatOpts(&ServerOpts, host, dhost);
stone->ssl_server = mkStoneSSL(&ServerOpts, 1);
if (stone->ssl_server->lbmod) {
if (stone->ssl_server->lbmod > stone->ndsts) {
message(LOG_WARNING, "LB set (%d) < lbmod (%d)",
stone->ndsts, stone->ssl_server->lbmod);
stone->ssl_server->lbmod = stone->ndsts;
}
}
} else {
stone->ssl_server = NULL;
}
if (proto & proto_ssl_d) { /* client side SSL */
exPatOpts(&ClientOpts, host, dhost);
stone->ssl_client = mkStoneSSL(&ClientOpts, 0);
if (!(stone->ssl_client->name && *stone->ssl_client->name))
stone->ssl_client->name = dhost;
} else {
stone->ssl_client = NULL;
}
#endif
mesg = NULL;
if (Debug > 1) {
mesg = str;
if ((proto & proto_command) == command_proxy) {
snprintf(mesg, STRMAX, "stone %d: using proxy by ",
stone->sd);
} else if ((proto & proto_command) == command_health) {
snprintf(mesg, STRMAX, "stone %d: health check by ", stone->sd);
} else if ((proto & proto_command) == command_identd) {
snprintf(mesg, STRMAX, "stone %d: ident query by ", stone->sd);
} else {
char addrport[STRMAX+1];
addrport2str(&stone->dsts[0]->addr, stone->dsts[0]->len,
(stone->proto & proto_stone_d),
addrport, STRMAX, 0);
addrport[STRMAX] = '\0';
snprintf(mesg, STRMAX, "stone %d: connecting to %s by ",
stone->sd, addrport);
}
}
family = AF_INET;
#ifdef AF_INET6
if (stone->proto & proto_v6_s) {
if (host == NULL && !(stone->proto & proto_ip_only_s)) {
family = AF_UNSPEC;
} else {
family = AF_INET6;
}
}
#endif
stone->xhosts = mkXhosts(nhosts, hosts, family, mesg);
message(LOG_INFO, "%s", stone2str(stone, str, STRMAX));
stone->backups = NULL;
if ((proto & proto_command) != command_proxy
&& (proto & proto_command) != command_health
&& (proto & proto_command) != command_identd
&& (proto & proto_nobackup) == 0) {
Backup *bs[LB_MAX];
int found = 0;
int i;
for (i=0; i < stone->ndsts; i++) {
bs[i] = findBackup(&stone->dsts[i]->addr);
if (bs[i]) {
found = 1;
bs[i]->used = 1;
}
}
if (found) {
stone->backups = malloc(sizeof(Backup*) * stone->ndsts);
if (stone->backups) {
for (i=0; i < stone->ndsts; i++) stone->backups[i] = bs[i];
}
}
}
return stone;
}
/* main */
void help(char *com, char *sub) {
message(LOG_INFO, "stone %s http://www.gcd.org/sengoku/stone/", VERSION);
message(LOG_INFO, "%s",
"Copyright(C)2007 by Hiroaki Sengoku <sengoku@gcd.org>");
#ifdef USE_SSL
message(LOG_INFO, "%s",
"using " OPENSSL_VERSION_TEXT " http://www.openssl.org/");
#ifdef CRYPTOAPI
message(LOG_INFO, "%s",
"using cryptoapi.c by Peter 'Luna' Runestig <peter@runestig.com>");
#endif
#endif
if (!sub) {
help:
fprintf(stderr,
"Usage: %s <opt>... <stone> [-- <stone>]...\n"
"opt: -h opt ; help for <opt> more\n"
" -h stone ; help for <stone>\n"
#ifdef USE_SSL
" -h ssl ; help for <SSL>, see -q/-z opt\n"
#endif
, com);
return;
}
if (!strcmp(sub, "opt")) {
fprintf(stderr, "Usage: %s <opt>... <stone> [-- <stone>]...\n"
"opt: -C <file> ; configuration file\n"
#ifdef CPP
" -P <command> ; preprocessor for config. file\n"
" -Q <options> ; options for preprocessor\n"
#endif
" -N ; configuration check only\n"
" -d ; increase debug level\n"
" -p ; packet dump\n"
" -n ; numerical address\n"
" -u <max> ; # of UDP sessions\n"
#ifndef NO_FORK
" -f <n> ; # of child processes\n"
#endif
#ifndef NO_SYSLOG
" -l ; use syslog\n"
" -ll ; run under daemontools\n"
#endif
" -L <file> ; write log to <file>\n"
" -a <file> ; write accounting to <file>\n"
" -i <file> ; write process ID to <file>\n"
" -X <n> ; size [byte] of Xfer buffer\n"
" -T <n> ; timeout [sec] of TCP sessions\n"
" -A <n> ; length of backlog\n"
" -r ; reuse socket\n"
" -x <port>[,<port>][-<port>]... <xhost> --\n"
" ; permit connecting to <xhost>:<port>\n"
" -s <send> <expect>... --\n"
" ; health check script\n"
" -b <n> <master>:<port> <backup>:<port>\n"
" ; check <master>:<port> every <n> sec\n"
" ; use <backup>:<port>, if check failed\n"
" -B <host>:<port>... --\n"
" ; load balancing hosts\n"
#ifdef ADDRCACHE
" -H <size> ; cache addresses used in proxy\n"
#endif
" -I <host> ; local end of its connections to\n"
#ifndef NO_SETUID
" -o <n> ; set uid to <n>\n"
" -g <n> ; set gid to <n>\n"
#endif
#ifndef NO_CHROOT
" -t <dir> ; chroot to <dir>\n"
#endif
#ifdef UNIX_DAEMON
" -D ; become UNIX Daemon\n"
#endif
" -c <dir> ; core dump to <dir>\n"
#ifdef USE_SSL
" -q <SSL> ; SSL client option\n"
" -z <SSL> ; SSL server option\n"
" ; `-h ssl' for <SSL>\n"
#endif
#ifdef NT_SERVICE
" -M install <name> ; install service as <name>\n"
" -M remove <name> ; remove service <name>\n"
#endif
, com);
} else if (!strcmp(sub, "stone")) {
fprintf(stderr, "Usage: %s <opt>... <stone> [-- <stone>]...\n"
"stone: <host>:<port> <sport> [<xhost>...]\n"
" proxy"
#ifdef AF_INET6
"[/[v4only | v6only]]"
#endif
" <sport> [<xhost>...]\n"
" health <sport> [<xhost>...]\n"
" identd <sport> [<xhost>...]\n"
" <host>:<port#>/http <sport> "
"<Request-Line> [<xhost>...]\n"
" <host>:<port#>/proxy <sport> <header> [<xhost>...]\n"
" <host>:<port#>/mproxy <sport> <header> [<xhost>...]\n"
"port: <port#>[/<ext>[,<ext>]...]\n"
"ext: tcp | udp"
#ifdef USE_SSL
" | ssl"
#endif
#ifdef AF_INET6
" | v6"
#endif
#ifdef USE_POP
" | apop"
#endif
" | base | block | nobackup\n"
"sport: [<host>:]<port#>[/<exts>[,<exts>]...]\n"
"exts: tcp | udp"
#ifdef USE_SSL
" | ssl"
#endif
#ifdef AF_INET6
" | v6 | v6only"
#endif
" | http | base | block | ident\n"
"xhost: <host>[/<ex>[,<ex>]...]\n"
"ex: <#bits> | p<mode#>"
#ifdef AF_INET6
" | v6"
#endif
"\n"
, com);
#ifdef USE_SSL
} else if (!strcmp(sub, "ssl")) {
fprintf(stderr,
"opt: -q <SSL> ; SSL client option\n"
" -z <SSL> ; SSL server option\n"
"SSL: default ; reset to default\n"
" verbose ; verbose mode\n"
" verify ; require peer's certificate\n"
" verify,once ; verify client's certificate only once\n"
" verify,ifany ; verify client's certificate if any\n"
" verify,none ; don't require peer's certificate\n"
" crl_check ; lookup CRLs\n"
" crl_check_all ; lookup CRLs for whole chain\n"
" uniq ; check serial # of peer's certificate\n"
" re<n>=<regex> ; verify depth <n> with <regex>\n"
" depth=<n> ; set verification depth to <n>\n"
#ifndef OPENSSL_NO_TLS1
" tls1 ; just use TLSv1\n"
#endif
#ifndef OPENSSL_NO_SSL3
" ssl3 ; just use SSLv3\n"
#endif
#ifndef OPENSSL_NO_SSL2
" ssl2 ; just use SSLv2\n"
#endif
" no_tls1 ; turn off TLSv1\n"
" no_ssl3 ; turn off SSLv3\n"
" no_ssl2 ; turn off SSLv2\n"
#ifndef OPENSSL_NO_TLSEXT
" sni ; Server Name Indication\n"
" servername=<str> ; Server Name\n"
#endif
" bugs ; SSL implementation bug workarounds\n"
#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
" serverpref ; use server's cipher preferences (SSLv2)\n"
#endif
" shutdown=<mode> ; accurate, nowait, unclean\n"
" sid_ctx=<str> ; set session ID context\n"
" passfile=<file> ; password file\n"
" passfilepat=<file> ; password file pattern\n"
" key=<file> ; key file\n"
" keypat=<file> ; key file pattern\n"
" cert=<file> ; certificate file\n"
" certpat=<file> ; certificate file pattern\n"
" certkey=<file> ; certificate & key file\n"
" certkeypat=<file> ; certificate & key file pattern\n"
" CAfile=<file> ; certificate file of CA\n"
" CApath=<dir> ; dir of CAs\n"
" pfx=<file> ; PKCS#12 file\n"
" pfxpat=<file> ; PKCS#12 file pattern\n"
#ifdef CRYPTOAPI
" store=<prop> ; \"SUBJ:<substr>\" or \"THUMB:<hex>\"\n"
" storeCA ; use CA cert in Windows cert store\n"
#endif
" cipher=<ciphers> ; list of ciphers\n"
" lb<n>=<m> ; load balancing based on CN\n"
);
#endif
} else {
goto help;
}
}
static void skipcomment(FILE *fp) {
int c;
while ((c=getc(fp)) != EOF && c != '\r' && c != '\n') ;
while ((c=getc(fp)) != EOF && (c == '\r' || c == '\n')) ;
if (c != EOF) ungetc(c, fp);
}
static int getvar(FILE *fp, char *buf, int bufmax) {
char var[STRMAX+1];
char *val;
int i = 0;
int paren = 0;
int c = getc(fp);
if (c == EOF) {
return 0;
} else if (c == '{') {
paren = 1;
} else {
ungetc(c, fp);
}
while ((c=getc(fp)) != EOF && i < STRMAX) {
if (paren && c == '}') {
break;
} else if (isalnum(c) || c == '_') {
var[i++] = c;
} else {
ungetc(c, fp);
break;
}
}
var[i] = '\0';
if (*var == '\0') return 0;
val = getenv(var);
if (val == NULL) return 0;
i = strlen(val);
if (i > bufmax) i = bufmax;
strncpy(buf, val, i);
return i;
}
static int gettoken(FILE *fp, char *buf) {
int i = 0;
int quote = 0;
int c;
for (;;) {
c = getc(fp);
if (c == EOF) return -1;
if (c == '#') {
skipcomment(fp);
continue;
}
if (!isspace(c)) {
ungetc(c, fp);
break;
}
}
while (i < BUFMAX-1) {
c = getc(fp);
if (c == EOF) {
if (i > 0) break;
return -1;
}
if (quote != '\'') {
if (c == '$') {
i += getvar(fp, &buf[i], BUFMAX-1-i);
continue;
}
if (c == '\\') { /* escape a char */
c = getc(fp);
if (c == EOF) break;
switch(c) {
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
}
}
}
if (quote) {
if (c == quote) {
quote = 0;
continue;
}
} else if (c == '\'' || c == '\"') {
quote = c;
continue;
} else if (isspace(c)) {
c = getc(fp);
if (c != ':' && c != '=') {
ungetc(c, fp);
break;
}
} else if (c == '#') {
skipcomment(fp);
continue;
}
buf[i++] = c;
}
buf[i] = '\0';
return i;
}
FILE *openconfig(void) {
#ifdef CPP
int pfd[2];
char host[MAXHOSTNAMELEN];
if (CppCommand != NULL && *CppCommand != '\0') {
if (gethostname(host, MAXHOSTNAMELEN-1) < 0) {
message(LOG_ERR, "gethostname err=%d", errno);
exit(1);
}
if (pipe(pfd) < 0) {
message(LOG_ERR, "Can't get pipe err=%d", errno);
exit(1);
}
if (!fork()) {
char *argv[BUFMAX/2];
int i = 0;
char buf[BUFMAX];
int len = 0;
char *p;
if (CppOptions) {
snprintf(buf, BUFMAX-1, "%s %s", CppCommand, CppOptions);
} else {
strncpy(buf, CppCommand, BUFMAX-1);
}
argv[i] = "cpp";
while (buf[len]) {
if (isspace(buf[len])) {
buf[len++] = '\0';
while (buf[len] && isspace(buf[len])) len++;
if (buf[len]) argv[++i] = &buf[len];
else break;
}
len++;
}
len++;
argv[++i] = buf + len;
snprintf(argv[i], BUFMAX-len, "-DHOST=%s", host);
len += strlen(argv[i]) + 1;
argv[++i] = buf + len;
for (p=host; *p; p++) if (*p == '.') *p = '_';
snprintf(argv[i], BUFMAX-len, "-DHOST_%s", host);
len += strlen(argv[i]) + 1;
if (getenv("HOME")) {
argv[++i] = buf + len;
snprintf(argv[i], BUFMAX-len, "-DHOME=%s", getenv("HOME"));
len += strlen(argv[i]) + 1;
}
argv[++i] = ConfigFile;
argv[++i] = NULL;
close(pfd[0]);
close(1);
dup(pfd[1]);
close(pfd[1]);
if (Debug > 9) {
char str[BUFMAX];
snprintf(str, BUFMAX, "%s: ", buf);
for (i=0; argv[i]; i++) {
len = strlen(str);
snprintf(&str[len], BUFMAX-len, " %s", argv[i]);
}
message(LOG_DEBUG, "%s", str);
}
execv(buf, argv);
}
close(pfd[1]);
return fdopen(pfd[0], "r");
} else
#endif
return fopen(ConfigFile, "r");
}
void getconfig(void) {
FILE *fp;
int nptr = 0;
char **new;
char buf[BUFMAX];
int len;
if (ConfigFile == NULL) return;
ConfigArgc = 0;
ConfigArgv = NULL;
fp = openconfig();
if (fp == NULL) {
message(LOG_ERR, "Can't open config file: %s err=%d",
ConfigFile, errno);
exit(1);
}
strcpy(buf, ConfigFile);
len = strlen(buf);
do {
if (Debug > 9) message(LOG_DEBUG, "token: \"%s\"", buf);
if (ConfigArgc >= nptr) { /* allocate new ptrs */
new = malloc((nptr+BUFMAX)*sizeof(*ConfigArgv));
if (new == NULL) {
message(LOG_CRIT, "Out of memory");
exit(1);
}
if (ConfigArgv) {
bcopy(ConfigArgv, new, nptr*sizeof(*ConfigArgv));
free(ConfigArgv);
}
ConfigArgv = new;
nptr += BUFMAX;
}
ConfigArgv[ConfigArgc] = malloc(len+1);
bcopy(buf, ConfigArgv[ConfigArgc], len+1);
ConfigArgc++;
} while ((len=gettoken(fp, buf)) >= 0);
fclose(fp);
#ifdef CPP
if (CppCommand != NULL && *CppCommand != '\0') {
wait(NULL);
}
#endif
}
int getdist( /* return pos where serv begins */
char *p,
int *protop) {
char *port_str, *proto_str, *top;
top = p;
port_str = proto_str = NULL;
*protop = 0; /* default */
#ifdef AF_LOCAL
if (p[0] == '.' || p[0] == '/') {
struct stat st;
p++;
while (*p) {
if (*p == '/') proto_str = ++p;
else p++;
}
if (proto_str) {
*(proto_str-1) = '\0';
if (stat(top, &st) >=0 && S_ISDIR(st.st_mode)) {
*(proto_str-1) = '/'; /* restore */
proto_str = NULL;
}
}
*protop |= proto_unix;
} else
#endif
while (*p) {
if (*p == ':') port_str = ++p;
else if (*p == '/') proto_str = ++p;
else p++;
}
if (proto_str) {
*(proto_str-1) = '\0';
p = proto_str;
do {
if (!strncmp(p, "tcp", 3)) {
p += 3;
*protop &= ~proto_udp;
} else if (!strncmp(p, "udp", 3)) {
p += 3;
*protop |= proto_udp;
} else if (!strncmp(p, "http", 4)) {
p += 4;
*protop |= proto_ohttp;
} else if (!strncmp(p, "base", 4)) {
p += 4;
*protop |= proto_base;
} else if (!strncmp(p, "ident", 5)) {
p += 5;
*protop |= proto_ident;
} else if (!strncmp(p, "proxy", 5)) {
p += 5;
*protop &= ~proto_command;
*protop |= command_ihead;
} else if (!strncmp(p, "mproxy", 6)) {
p += 6;
*protop &= ~proto_command;
*protop |= command_iheads;
} else if (!strncmp(p, "nobackup", 8)) {
p += 8;
*protop |= proto_nobackup;
#ifdef USE_SSL
} else if (!strncmp(p, "ssl", 3)) {
p += 3;
*protop |= proto_ssl;
#endif
#ifdef AF_INET6
} else if (!strncmp(p, "v6", 2)) {
p += 2;
*protop |= proto_v6;
if (!strncmp(p, "only", 4)) {
p += 4;
*protop |= proto_ip_only;
}
#endif
} else if (!strncmp(p, "v4only", 6)) {
p += 6;
*protop |= proto_ip_only;
} else if (!strncmp(p, "block", 5)) {
p += 5;
*protop |= proto_block;
#ifdef USE_POP
} else if (!strncmp(p, "apop", 4)) {
p += 4;
*protop &= ~proto_command;
*protop |= command_pop;
#endif
} else return -1; /* error */
} while ((*p == ',' || *p == '/') && p++);
}
if (port_str) {
*(port_str-1) = '\0';
return port_str - top; /* host & serv */
} else {
#ifdef AF_LOCAL
if (*protop & proto_unix) {
return 1;
}
#endif
if (!strcmp(top, "proxy")) {
*protop &= ~proto_command;
*protop |= command_proxy;
return 1; /* host only */
}
if (!strcmp(top, "health")) {
*protop &= ~proto_command;
*protop |= command_health;
return 1; /* host only */
}
if (!strcmp(top, "identd")) {
*protop &= ~proto_command;
*protop |= command_identd;
return 1; /* host only */
}
return 0; /* serv only */
}
}
#ifdef USE_SSL
void sslopts_default(SSLOpts *opts, int isserver) {
int i;
opts->verbose = 0;
opts->shutdown_mode = 0;
opts->mode = SSL_VERIFY_NONE;
opts->depth = DEPTH_MAX - 1;
opts->vflags = 0;
opts->off = 0;
opts->serial = -2;
opts->callback = verify_callback;
opts->sid_ctx = NULL;
opts->useSNI = 0;
if (isserver) {
char path[BUFMAX];
snprintf(path, BUFMAX-1, "%s/stone.pem", X509_get_default_cert_dir());
opts->keyFile = opts->certFile = strdup(path);
opts->keyFilePat = opts->certFilePat = NULL;
#if !defined(OPENSSL_NO_SSL2) && !defined(OPENSSL_NO_SSL3)
opts->meth = SSLv23_server_method();
#elif !defined(OPENSSL_NO_SSL3)
opts->meth = SSLv3_server_method();
#elif !defined(OPENSSL_NO_SSL2)
opts->meth = SSLv2_server_method();
#endif
} else {
opts->keyFile = opts->certFile = NULL;
opts->keyFilePat = opts->certFilePat = NULL;
#if !defined(OPENSSL_NO_SSL2) && !defined(OPENSSL_NO_SSL3)
opts->meth = SSLv23_client_method();
#elif !defined(OPENSSL_NO_SSL3)
opts->meth = SSLv3_client_method();
#elif !defined(OPENSSL_NO_SSL2)
opts->meth = SSLv2_client_method();
#endif
}
opts->caFile = opts->caPath = NULL;
opts->pfxFile = NULL;
opts->pfxFilePat = NULL;
opts->passFile = NULL;
opts->passFilePat = NULL;
opts->passwd = NULL;
opts->servername = NULL;
opts->certIgnore = 0;
#ifdef CRYPTOAPI
opts->certStoreCA = 0;
opts->certStore = NULL;
#endif
opts->cipherList = getenv("SSL_CIPHER");
for (i=0; i < DEPTH_MAX; i++) opts->regexp[i] = NULL;
opts->lbmod = 0;
opts->lbparm = 0xFF;
opts->shutdown_mode = 0;
}
int sslopts(int argc, int argi, char *argv[], SSLOpts *opts, int isserver) {
if (!strcmp(argv[argi], "default")) {
sslopts_default(opts, isserver);
} else if (!strcmp(argv[argi], "verbose")) {
opts->verbose++;
} else if (!strncmp(argv[argi], "shutdown=", 9)) {
if (!strcmp(argv[argi]+9, "nowait")) {
opts->shutdown_mode = SSL_RECEIVED_SHUTDOWN;
} else if (!strcmp(argv[argi]+9, "accurate")) {
opts->shutdown_mode = 0;
} else if (!strcmp(argv[argi]+9, "unclean")) {
opts->shutdown_mode = (SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
}
} else if (!strncmp(argv[argi], "verify", 6)
&& (argv[argi][6] == '\0' || argv[argi][6] == ',')) {
if (!strcmp(argv[argi]+6, ",none")) {
opts->mode = SSL_VERIFY_NONE;
} else if (isserver) {
opts->mode = (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT);
if (argv[argi][6] == ',') {
if (!strcmp(argv[argi]+7, "ifany")) {
opts->mode = (SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE);
} else if (!strcmp(argv[argi]+7, "once")) {
opts->mode |= SSL_VERIFY_CLIENT_ONCE;
}
}
} else if (argv[argi][6] == '\0') {
opts->mode = SSL_VERIFY_PEER;
} else {
goto error;
}
} else if (!strncmp(argv[argi], "crl_check", 9)) {
opts->vflags |= X509_V_FLAG_CRL_CHECK;
} else if (!strncmp(argv[argi], "crl_check_all", 13)) {
opts->vflags |= (X509_V_FLAG_CRL_CHECK
| X509_V_FLAG_CRL_CHECK_ALL);
} else if (!strncmp(argv[argi], "re", 2) && isdigit(argv[argi][2])
&& argv[argi][3] == '=') {
int depth = atoi(argv[argi]+2);
if (0 <= depth && depth < DEPTH_MAX) {
opts->regexp[depth] = strdup(argv[argi]+4);
} else {
goto error;
}
} else if (!strncmp(argv[argi], "re-", 3) && isdigit(argv[argi][3])
&& argv[argi][4] == '=') {
int depth = atoi(argv[argi]+3);
if (0 < depth && depth <= DEPTH_MAX) {
opts->regexp[DEPTH_MAX-depth] = strdup(argv[argi]+5);
} else {
goto error;
}
} else if (!strncmp(argv[argi], "depth=", 6)) {
opts->depth = atoi(argv[argi]+6);
if (opts->depth >= DEPTH_MAX) opts->depth = DEPTH_MAX - 1;
else if (opts->depth < 0) opts->depth = 0;
} else if (!strcmp(argv[argi], "bugs")) {
opts->off |= SSL_OP_ALL;
#ifndef OPENSSL_NO_TLS1
} else if (!strcmp(argv[argi], "tls1")) {
if (isserver) opts->meth = TLSv1_server_method();
else opts->meth = TLSv1_client_method();
#endif
#ifndef OPENSSL_NO_SSL3
} else if (!strcmp(argv[argi], "ssl3")) {
if (isserver) opts->meth = SSLv3_server_method();
else opts->meth = SSLv3_client_method();
#endif
#ifndef OPENSSL_NO_SSL2
} else if (!strcmp(argv[argi], "ssl2")) {
if (isserver) opts->meth = SSLv2_server_method();
else opts->meth = SSLv2_client_method();
#endif
} else if (!strcmp(argv[argi], "no_tls1")) {
opts->off |= SSL_OP_NO_TLSv1;
} else if (!strcmp(argv[argi], "no_ssl3")) {
opts->off |= SSL_OP_NO_SSLv3;
} else if (!strcmp(argv[argi], "no_ssl2")) {
opts->off |= SSL_OP_NO_SSLv2;
#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
} else if (!strcmp(argv[argi], "serverpref")) {
opts->off |= SSL_OP_CIPHER_SERVER_PREFERENCE;
#endif
} else if (!strcmp(argv[argi], "uniq")) {
opts->serial = -1;
} else if (!strncmp(argv[argi], "sid_ctx=", 8)) {
opts->sid_ctx = (unsigned char*)strdup(argv[argi]+8);
} else if (!strcmp(argv[argi], "sni")) {
opts->useSNI = 1;
} else if (!strncmp(argv[argi], "servername=", 11)) {
opts->servername = strdup(argv[argi]+11);
} else if (!strncmp(argv[argi], "key=", 4)) {
opts->keyFile = strdup(argv[argi]+4);
opts->keyFilePat = NULL;
opts->pfxFile = NULL;
} else if (!strncmp(argv[argi], "keypat=", 7)) {
opts->keyFilePat = strdup(argv[argi]+7);
opts->pfxFile = NULL;
} else if (!strncmp(argv[argi], "cert=", 5)) {
opts->certFile = strdup(argv[argi]+5);
opts->certFilePat = NULL;
opts->pfxFile = NULL;
} else if (!strncmp(argv[argi], "certpat=", 8)) {
opts->certFilePat = strdup(argv[argi]+8);
opts->pfxFile = NULL;
} else if (!strncmp(argv[argi], "certkey=", 8)) {
opts->keyFile = opts->certFile = strdup(argv[argi]+8);
opts->keyFilePat = opts->certFilePat = NULL;
opts->pfxFile = NULL;
} else if (!strncmp(argv[argi], "certkeypat=", 11)) {
opts->keyFilePat = opts->certFilePat = strdup(argv[argi]+11);
opts->pfxFile = NULL;
} else if (!strncmp(argv[argi], "CAfile=", 7)) {
opts->caFile = strdup(argv[argi]+7);
} else if (!strncmp(argv[argi], "CApath=", 7)) {
opts->caPath = strdup(argv[argi]+7);
} else if (!strncmp(argv[argi], "pfx=", 4)) {
opts->pfxFile = strdup(argv[argi]+4);
opts->pfxFilePat = NULL;
opts->keyFile = opts->certFile = NULL;
opts->keyFilePat = opts->certFilePat = NULL;
} else if (!strncmp(argv[argi], "pfxpat=", 7)) {
opts->pfxFilePat = strdup(argv[argi]+7);
opts->keyFile = opts->certFile = NULL;
opts->keyFilePat = opts->certFilePat = NULL;
} else if (!strncmp(argv[argi], "passfile=", 9)) {
opts->passFile = strdup(argv[argi]+9);
opts->passFilePat = NULL;
} else if (!strncmp(argv[argi], "passfilepat=", 12)) {
opts->passFilePat = strdup(argv[argi]+12);
opts->passFile = NULL;
} else if (!strncmp(argv[argi], "ignore", 6)) {
opts->certIgnore = 1;
#ifdef CRYPTOAPI
} else if (!strncmp(argv[argi], "storeCA", 7)) {
opts->certStoreCA = 1;
} else if (!strncmp(argv[argi], "store=", 6)) {
opts->certStore = strdup(argv[argi]+6);
#endif
} else if (!strncmp(argv[argi], "cipher=", 7)) {
opts->cipherList = strdup(argv[argi]+7);
} else if (!strncmp(argv[argi], "lb", 2) && isdigit(argv[argi][2])
&& argv[argi][3] == '=') {
opts->lbparm = argv[argi][2] - '0';
opts->lbmod = atoi(argv[argi]+4);
} else {
error:
message(LOG_ERR, "Invalid SSL Option: %s", argv[argi]);
help(argv[0], "ssl");
exit(1);
}
return argi;
}
#ifndef NO_THREAD
/* SSL callback */
unsigned long sslthread_id_callback(void) {
unsigned long ret;
#ifdef WINDOWS
ret = (unsigned long)GetCurrentThreadId();
#else
#ifdef PTHREAD
ret = (unsigned long)pthread_self();
#endif
#endif
if (Debug > 19) message(LOG_DEBUG, "SSL_thread id=%ld", ret);
return ret;
}
void sslthread_lock_callback(int mode, int n, const char *file, int line) {
if (mode & CRYPTO_LOCK) {
if (Debug > 19)
message(LOG_DEBUG, "SSL_lock mode=%x n=%d file=%s line=%d",
mode, n, file, line);
#ifdef WINDOWS
WaitForSingleObject(SSLMutex[n], 500);
#else
#ifdef PTHREAD
pthread_mutex_lock(&SSLMutex[n]);
#endif
#endif
} else {
if (Debug > 19)
message(LOG_DEBUG, "SSL_unlock mode=%x n=%d file=%s line=%d",
mode, n, file, line);
#ifdef WINDOWS
ReleaseMutex(SSLMutex[n]);
#else
#ifdef PTHREAD
pthread_mutex_unlock(&SSLMutex[n]);
#endif
#endif
}
}
int sslthread_initialize(void) {
int i;
NSSLMutexs = CRYPTO_num_locks();
SSLMutex = malloc(NSSLMutexs * sizeof(*SSLMutex));
if (!SSLMutex) return -1;
if (Debug > 1) message(LOG_DEBUG, "SSL thread nlocks=%d", NSSLMutexs);
for (i=0; i < NSSLMutexs; i++) {
#ifdef WINDOWS
SSLMutex[i] = CreateMutex(NULL, FALSE, NULL);
if (!SSLMutex[i]) return -1;
#else
#ifdef PTHREAD
pthread_mutex_init(&SSLMutex[i], NULL);
#endif
#endif
}
#if defined(WINDOWS) || defined(PTHREAD)
CRYPTO_set_id_callback(sslthread_id_callback);
CRYPTO_set_locking_callback(sslthread_lock_callback);
return 1;
#else
return 0;
#endif
}
#endif
#endif
int dohyphen(char opt, int argc, char *argv[], int argi) {
switch(opt) {
case 'd':
Debug++;
break;
case 'p':
XHostsTrue->mode = ((XHostsTrue->mode & ~XHostsMode_Dump)
| (((XHostsTrue->mode & XHostsMode_Dump) + 1)
& XHostsMode_Dump));
break;
#ifndef NO_SYSLOG
case 'l':
Syslog++;
break;
#endif
case 'L':
if (++argi >= argc) {
message(LOG_ERR, "option -%c requires log <file>", opt);
exit(1);
}
if (DryRun) break;
if (!strcmp(argv[argi], "-")) {
LogFp = stdout;
} else {
if (LogFp && LogFp != stderr) fclose(LogFp);
LogFp = fopen(argv[argi], "a");
if (LogFp == NULL) {
LogFp = stderr;
message(LOG_ERR, "Can't create log file: %s err=%d",
argv[argi], errno);
exit(1);
}
LogFileName = strdup(argv[argi]);
}
setbuf(LogFp, NULL);
break;
case 'a':
if (++argi >= argc) {
message(LOG_ERR, "option -%c requires accounting <file>", opt);
exit(1);
}
if (DryRun) break;
if (!strcmp(argv[argi], "-")) {
AccFp = stdout;
} else {
if (AccFp && AccFp != stdout) fclose(AccFp);
AccFp = fopen(argv[argi], "a");
if (AccFp == NULL) {
message(LOG_ERR,
"Can't create account log file: %s err=%d",
argv[argi], errno);
exit(1);
}
AccFileName = strdup(argv[argi]);
}
setbuf(AccFp, NULL);
break;
case 'i':
if (++argi >= argc) {
message(LOG_ERR, "option -%c requires pid <file>", opt);
exit(1);
}
PidFile = strdup(argv[argi]);
break;
#ifndef NO_CHROOT
case 't':
if (++argi >= argc) {
message(LOG_ERR, "option -%c requires <dir>", opt);
exit(1);
}
RootDir = strdup(argv[argi]);
break;
#endif
case 'n':
AddrFlag = 1;
break;
case 'u':
if (++argi >= argc) {
message(LOG_ERR, "option -%c requires # of <max> UDP sessions",
opt);
exit(1);
}
OriginMax = atoi(argv[argi]);
break;
case 'X':
if (++argi >= argc) {
message(LOG_ERR, "option -%c requires size of Xfer buffer <n>",
opt);
exit(1);
}
XferBufMax = atoi(argv[argi]);
break;
case 'T':
if (++argi >= argc) {
message(LOG_ERR, "option -%c requires timeout <n>", opt);
exit(1);
}
PairTimeOut = atoi(argv[argi]);
break;
case 'A':
if (++argi >= argc) {
message(LOG_ERR, "option -%c requires length of backlog <n>", opt);
exit(1);
}
BacklogMax = atoi(argv[argi]);
break;
#ifndef NO_SETUID
case 'o':
if (++argi >= argc) {
message(LOG_ERR, "option -%c requires <uid>", opt);
exit(1);
}
SetUID = atoi(argv[argi]);
break;
case 'g':
if (++argi >= argc) {
message(LOG_ERR, "option -%c requires <gid>", opt);
exit(1);
}
SetGID = atoi(argv[argi]);
break;
#endif
case 'c':
if (++argi >= argc) {
message(LOG_ERR, "option -%c requires <dir> for core dump", opt);
exit(1);
}
CoreDumpDir = strdup(argv[argi]);
break;
#ifndef NO_FORK
case 'f':
if (++argi >= argc) {
message(LOG_ERR, "option -%c requires # of child processes <n>",
opt);
exit(1);
}
NForks = atoi(argv[argi]);
break;
#endif
#ifdef UNIX_DAEMON
case 'D':
DaemonMode = 1;
break;
#endif
case 'r':
ReuseAddr = 1;
break;
case 'x':
argi = mkPortXhosts(argc, argi, argv);
break;
case 's':
argi = mkChat(argc, argi, argv);
break;
case 'b':
argi = mkBackup(argc, argi, argv);
break;
case 'B':
argi = lbsopts(argc, argi, argv);
break;
#ifdef ADDRCACHE
case 'H':
if (++argi >= argc) {
message(LOG_ERR, "option -%c requires addr cache size <n>", opt);
exit(1);
}
AddrCacheSize = atoi(argv[argi]);
break;
#endif
case 'I':
if (++argi >= argc) {
message(LOG_ERR, "option -%c requires local interface <host>",
opt);
exit(1);
}
if (!argv[argi] || argv[argi][0] == '\0') {
ConnectFrom = NULL;
} else {
char host[STRMAX+1];
char port[STRMAX+1];
struct sockaddr_storage ss;
struct sockaddr *sa = (struct sockaddr*)&ss;
socklen_t salen = sizeof(ss);
int pos = hostPortExt(argv[argi], host, port);
if (pos < 0) {
sa->sa_family = AF_UNSPEC;
if (!host2sa(argv[argi], NULL, sa, &salen, NULL, NULL, 0)) {
return -1;
}
} else {
sa->sa_family = AF_UNSPEC;
#ifdef AF_INET6
if (pos && !strcmp(argv[argi]+pos, "v6"))
sa->sa_family = AF_INET6;
#endif
if (!host2sa(host, port, sa, &salen, NULL, NULL, 0)) {
return -1;
}
}
ConnectFrom = saDup(sa, salen);
if (!ConnectFrom) {
message(LOG_CRIT, "Out of memory");
exit(1);
}
}
break;
#ifdef USE_SSL
case 'q':
if (++argi >= argc) {
message(LOG_ERR, "Illegal Option: -q without <SSL>");
exit(1);
}
argi = sslopts(argc, argi, argv, &ClientOpts, 0);
break;
case 'z':
if (++argi >= argc) {
message(LOG_ERR, "Illegal Option: -z without <SSL>");
exit(1);
}
argi = sslopts(argc, argi, argv, &ServerOpts, 1);
break;
#endif
#ifdef CPP
case 'P':
if (++argi >= argc) {
message(LOG_ERR, "option -%c requires preprocessor <command>",
opt);
exit(1);
}
CppCommand = strdup(argv[argi]);
break;
case 'Q':
if (++argi >= argc) {
message(LOG_ERR, "option -%c requires <options> for preprocessor",
opt);
exit(1);
}
CppOptions = strdup(argv[argi]);
break;
#endif
default:
return -1;
}
return argi;
}
#ifdef NT_SERVICE
int quoteToken(char *dst, char *src) {
char buf[STRMAX+1];
int len;
if (strchr(src, ' ')) {
snprintf(buf, STRMAX, "\"%s\"", src);
len = strlen(buf);
if (dst) strncpy(dst, buf, len);
} else {
len = strlen(src);
if (dst) strncpy(dst, src, len);
}
return len;
}
void installService(int argc, char *argv[]) {
SC_HANDLE scManager;
SC_HANDLE scService;
char exeName[STRMAX+1];
char *command;
int commax, len;
int i;
int state;
char *p;
if (!GetModuleFileName(0, exeName, sizeof(exeName))) {
message(LOG_ERR, "Can't determine exe name err=%d",
(int)GetLastError());
exit(1);
}
scManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (!scManager) {
message(LOG_ERR, "Can't open service control manager err=%d",
(int)GetLastError());
exit(1);
}
len = strlen(exeName);
for (i=1; i < argc; i++) {
len += 1 + quoteToken(NULL, argv[i]);
}
commax = len;
len++; /* for '\0' */
command = malloc(len);
if (!command) {
message(LOG_CRIT, "Out of memory");
exit(1);
}
strcpy(command, exeName);
len = strlen(command);
state = 0;
for (i=1; i < argc; i++) {
p = argv[i];
switch(state) {
case 0:
if (!strcmp(p, "-M")) state++;
break;
case 1:
if (!strcmp(p, "install"))
p = "run_svc"; /* assume same length */
break;
}
command[len++] = ' ';
len += quoteToken(command+len, p);
}
command[len] = '\0';
if (Debug > 1) message(LOG_DEBUG, "install: %s", command);
scService
= CreateService(scManager, NTServiceName,
NTServiceDisplayName,
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
command, NULL, NULL, "TcpIp\0\0",
NULL, NULL);
if (!scService) {
message(LOG_ERR, "Can't install service: %s err=%d",
NTServiceName, (int)GetLastError());
CloseServiceHandle(scManager);
exit(1);
}
message(LOG_INFO, "service installed: %s", NTServiceName);
CloseServiceHandle(scService);
CloseServiceHandle(scManager);
}
void removeService(void) {
SC_HANDLE scManager;
SC_HANDLE scService;
scManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (!scManager) {
message(LOG_ERR, "Can't open service control manager err=%d",
(int)GetLastError());
exit(1);
}
scService = OpenService(scManager, NTServiceName,
SERVICE_ALL_ACCESS);
if (!scService) {
message(LOG_ERR, "Can't open service: %s err=%d",
NTServiceName, (int)GetLastError());
CloseServiceHandle(scManager);
exit(1);
}
if (ControlService(scService, SERVICE_CONTROL_STOP, &NTServiceStatus)) {
do {
usleep(1000);
} while (QueryServiceStatus(scService, &NTServiceStatus),
NTServiceStatus.dwCurrentState == SERVICE_STOP_PENDING);
if (NTServiceStatus.dwCurrentState == SERVICE_STOPPED) {
message(LOG_INFO, "%s stopped", NTServiceName);
} else {
message(LOG_ERR, "failed to stop %s", NTServiceName);
}
}
if (!DeleteService(scService)) {
message(LOG_ERR, "failed to remove service: %s err=%d",
NTServiceName, (int)GetLastError());
CloseServiceHandle(scService);
CloseServiceHandle(scManager);
exit(1);
}
CloseServiceHandle(scService);
CloseServiceHandle(scManager);
message(LOG_INFO, "service removed: %s", NTServiceName);
}
void addEventSource(char *name) {
HKEY hk;
char key[LONGSTRMAX+1];
char exeName[STRMAX+1];
DWORD data;
snprintf(key, LONGSTRMAX, "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\%s", name);
if (RegCreateKey(HKEY_LOCAL_MACHINE, key, &hk)) return;
if (!GetModuleFileName(0, exeName, sizeof(exeName))) return;
if (RegSetValueEx(hk, "EventMessageFile", 0, REG_EXPAND_SZ,
(BYTE*)exeName, strlen(exeName)+1)) return;
data = (EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE |
EVENTLOG_INFORMATION_TYPE);
if (RegSetValueEx(hk, "TypesSupported", 0, REG_DWORD,
(LPBYTE)&data, sizeof(DWORD))) return;
RegCloseKey(hk);
}
#endif
int doopts(int argc, char *argv[]) {
int i;
char *p;
for (i=1; i < argc; i++) {
p = argv[i];
if (*p == '-') {
p++;
while(*p) {
int ret = dohyphen(*p, argc, argv, i);
if (ret >= 0) {
i = ret;
} else switch(*p) {
case '-': /* end of global options */
return i+1;
case 'h':
help(argv[0], argv[i+1]);
exit(1);
break;
case 'N':
DryRun = 1;
break;
#ifdef NT_SERVICE
case 'M':
i++;
if (i+1 >= argc) {
message(LOG_ERR, "Illegal Option: -M without args");
exit(1);
}
NTServiceName = strdup(argv[i+1]);
NTServiceDisplayName
= malloc(strlen(NTServiceName)
+ strlen(NTServiceDisplayPrefix) + 1);
if (!NTServiceDisplayName) {
message(LOG_CRIT, "Out of memory");
exit(1);
}
strcpy(NTServiceDisplayName, NTServiceDisplayPrefix);
strcat(NTServiceDisplayName, NTServiceName);
if (!strcmp(argv[i], "install")) {
installService(argc, argv);
exit(0);
} else if (!strcmp(argv[i], "remove")) {
removeService();
exit(0);
} else if (!strcmp(argv[i], "run_svc")) {
addEventSource(NTServiceName);
NTServiceLog
= RegisterEventSource(NULL, NTServiceName);
} else {
message(LOG_ERR, "Illegal Option: -M %s %s",
argv[i], argv[i+1]);
exit(1);
}
i++;
break;
#endif
case 'C':
if (!ConfigFile) {
i++;
ConfigFile = malloc(strlen(argv[i]) + 1);
if (ConfigFile == NULL) {
message(LOG_CRIT, "Out of memory");
exit(1);
}
strcpy(ConfigFile, argv[i]);
break;
} /* drop through */
default:
message(LOG_ERR, "Invalid Option: %s", argv[i]);
help(argv[0], "opt");
exit(1);
}
p++;
}
} else break;
}
return i;
}
void doargs(int argc, int i, char *argv[]) {
Stone *stone;
char *host, *shost;
char *serv, *sserv;
int proto, sproto, dproto;
char *p;
int j, k;
proto = sproto = dproto = 0; /* default: TCP */
if (argc - i < 1) {
help(argv[0], NULL);
exit(1);
}
for (; i < argc; i++) {
p = argv[i];
if (*p == '-') {
p++;
while(*p) {
int ret = dohyphen(*p, argc, argv, i);
if (ret >= 0) {
i = ret;
} else {
message(LOG_ERR, "Invalid Option: %s", argv[i]);
help(argv[0], "opt");
exit(1);
}
p++;
}
continue;
}
host = strdup(argv[i]);
j = getdist(host, &dproto);
if (j > 0) { /* with hostname */
i++;
if (j > 1) serv = host + j; else serv = NULL;
if (argc <= i) {
help(argv[0], NULL);
exit(1);
}
shost = strdup(argv[i]);
j = getdist(shost, &sproto);
if (j > 0) {
if (j > 1) sserv = shost + j; else sserv = NULL;
} else if (j == 0) {
sserv = shost;
shost = NULL;
} else {
message(LOG_ERR, "Invalid <sport>: %s", argv[i]);
exit(1);
}
} else {
message(LOG_ERR, "Invalid <host>:<port>: %s", argv[i]);
exit(1);
}
i++;
j = 0;
k = i;
for (; i < argc; i++, j++) if (!strcmp(argv[i], "--")) break;
if ((sproto & proto_udp)) {
proto |= proto_udp_s;
if (sproto & proto_v6) proto |= proto_v6_s;
if (sproto & proto_ip_only) proto |= proto_ip_only_s;
} else {
if (sproto & proto_ohttp) proto |= proto_ohttp_s;
if (sproto & proto_ssl) proto |= proto_ssl_s;
if (sproto & proto_v6) proto |= proto_v6_s;
if (sproto & proto_ip_only) proto |= proto_ip_only_s;
if (sproto & proto_unix) proto |= proto_unix_s;
if (sproto & proto_block) proto |= proto_block_s;
if (sproto & proto_base) proto |= proto_base_s;
if (sproto & proto_ident) proto |= proto_ident;
}
if ((dproto & proto_udp)) {
proto |= proto_udp_d;
if (dproto & proto_v6) proto |= proto_v6_d;
if (dproto & proto_ip_only) proto |= proto_ip_only_d;
} else {
if ((dproto & proto_command) == command_proxy) {
proto &= ~proto_command;
proto |= command_proxy;
#ifdef USE_POP
} else if ((dproto & proto_command) == command_pop) {
proto &= ~proto_command;
proto |= command_pop;
#endif
} else if (dproto & proto_ohttp) {
proto |= proto_ohttp_d;
goto extra_arg;
} else if ((dproto & proto_command) == command_ihead) {
proto &= ~proto_command;
proto |= command_ihead;
extra_arg:
p = argv[k++];
j--;
if (k > argc || j < 0) {
help(argv[0], NULL);
exit(1);
}
} else if ((dproto & proto_command) == command_iheads) {
proto &= ~proto_command;
proto |= command_iheads;
goto extra_arg;
} else if ((dproto & proto_command) == command_health) {
proto &= ~proto_command;
proto |= command_health;
} else if ((dproto & proto_command) == command_identd) {
proto &= ~proto_command;
proto |= command_identd;
}
if (dproto & proto_ssl) proto |= proto_ssl_d;
if (dproto & proto_v6) proto |= proto_v6_d;
if (dproto & proto_ip_only) proto |= proto_ip_only_d;
if (dproto & proto_unix) proto |= proto_unix_d;
if (dproto & proto_block) proto |= proto_block_d;
if (dproto & proto_base) proto |= proto_base_d;
if (dproto & proto_nobackup) proto |= proto_nobackup;
}
stone = mkstone(host, serv, shost, sserv, j, &argv[k], proto);
if ((proto & proto_udp_s) && (proto & proto_udp_d)) { /* UDP => UDP */
Origin *origin = (Origin*)malloc(sizeof(Origin));
if (origin == NULL) {
memerr:
message(LOG_CRIT, "Out of memory");
exit(1);
}
bzero(origin, sizeof(Origin));
origin->stone = stone;
origin->common = type_origin;
origin->sd = INVALID_SOCKET;
origin->from = NULL;
origin->next = OriginTop;
OriginTop = origin;
stone->p = (char*)origin;
} else if (proto & proto_ohttp_d) {
stone->p = strdup(p);
} else if (((proto & proto_command) == command_ihead) ||
((proto & proto_command) == command_iheads)) {
stone->p = strdup(p);
}
if (!(proto & proto_udp_s) || !(proto & proto_udp_d)) {
stone->pairs = newPair();
if (!stone->pairs) goto memerr;
stone->pairs->clock = -1; /* top */
stone->pairs->stone = stone;
stone->pairs->next = PairTop;
if (PairTop) PairTop->prev = stone->pairs;
PairTop = stone->pairs;
}
if (!stone->parent) { /* stone is parent */
stone->next = stones;
stones = stone;
}
proto = sproto = dproto = 0; /* default: TCP */
}
#ifndef USE_EPOLL
for (stone=stones; stone != NULL; stone=stone->next) {
FdSet(stone->sd, &rin);
FdSet(stone->sd, &ein);
}
#endif
}
#ifdef FD_SET_BUG
void checkFdSetBug(void) {
fd_set set;
FD_ZERO(&set);
FD_SET(0, &set);
FD_SET(0, &set);
FD_CLR(0, &set);
if (FD_ISSET(0, &set)) {
if (Debug > 0)
message(LOG_DEBUG, "FD_SET bug detected");
FdSetBug = 1;
}
}
#endif
#ifndef WINDOWS
static void handler(int sig) {
int i;
switch(sig) {
case SIGHUP:
if (Debug > 4) message(LOG_DEBUG, "SIGHUP");
#ifndef NO_FORK
if (NForks) { /* mother process */
if (ConfigFile && !oldstones) {
oldstones = stones;
stones = NULL;
OldConfigArgc = ConfigArgc;
OldConfigArgv = ConfigArgv;
Debug = 0;
getconfig(); /* reconfigure */
i = doopts(ConfigArgc, ConfigArgv);
doargs(ConfigArgc, i, ConfigArgv);
for (i=0; i < NForks; i++) {
kill(Pid[i], SIGHUP);
kill(Pid[i], SIGINT);
}
}
} else { /* child process */
#endif
message_pairs(LOG_INFO);
message_origins(LOG_INFO);
message_conns(LOG_INFO);
#ifndef NO_FORK
}
#endif
if (LogFileName) {
fclose(LogFp);
LogFp = fopen(LogFileName, "a");
if (LogFp == NULL) {
LogFp = stderr;
message(LOG_ERR, "Can't re-create log file: %s err=%d",
LogFileName, errno);
exit(1);
}
setbuf(LogFp, NULL);
}
if (AccFileName) {
fclose(AccFp);
AccFp = fopen(AccFileName, "a");
if (AccFp == NULL) {
message(LOG_ERR, "Can't re-create account log file: %s err=%d",
AccFileName, errno);
exit(1);
}
setbuf(AccFp, NULL);
}
signal(SIGHUP, handler);
break;
case SIGTERM:
#ifdef IGN_SIGTERM
Debug = 0;
message(LOG_INFO, "SIGTERM. clear Debug level");
signal(SIGTERM, handler);
break;
#endif
case SIGINT:
#ifndef NO_FORK
if (NForks) { /* mother process */
message(LOG_INFO, "SIGTERM/INT. killing children and exiting");
for (i=0; i < NForks; i++) kill(Pid[i], sig);
} else
#endif
message(LOG_INFO, "SIGTERM/INT. exiting"); /* child process */
exit(1);
case SIGUSR1:
Debug++;
message(LOG_INFO, "SIGUSR1. increase Debug level to %d", Debug);
#ifndef NO_FORK
if (NForks) { /* mother process */
for (i=0; i < NForks; i++) kill(Pid[i], sig);
} else {
#endif
message_pairs(LOG_INFO);
message_origins(LOG_INFO);
message_conns(LOG_INFO);
#ifndef NO_FORK
}
#endif
signal(SIGUSR1, handler);
break;
case SIGUSR2:
if (Debug > 0) Debug--;
message(LOG_INFO, "SIGUSR2. decrease Debug level to %d", Debug);
#ifndef NO_FORK
if (NForks) { /* mother process */
for (i=0; i < NForks; i++) kill(Pid[i], sig);
}
#endif
signal(SIGUSR2, handler);
break;
case SIGPIPE:
if (Debug > 0) message(LOG_DEBUG, "SIGPIPE");
signal(SIGPIPE, handler);
break;
case SIGSEGV:
case SIGBUS:
case SIGILL:
case SIGFPE:
if (CoreDumpDir) {
message(LOG_ERR, "Signal %d, core dumping to %s",
sig, CoreDumpDir);
if (chdir(CoreDumpDir) < 0) {
message(LOG_ERR, "Can't chdir to %s err=%d",
CoreDumpDir, errno);
} else {
abort();
}
} else {
message(LOG_ERR, "Signal %d, exiting", sig);
}
exit(1);
break;
default:
message(LOG_INFO, "signal %d. Debug level: %d", sig, Debug);
}
}
#endif
#ifdef UNIX_DAEMON
void daemonize(void) {
pid_t pid;
pid = fork();
if (pid < 0) {
message(LOG_ERR, "Can't create daemon err=%d", errno);
exit(1);
}
if (pid > 0) _exit(0);
MyPid = getpid();
if (setsid() < 0)
message(LOG_WARNING, "Can't create new session err=%d", errno);
if (chdir("/") < 0)
message(LOG_WARNING, "Can't change directory to / err=%d", errno);
umask(0022);
if (close(0) != 0)
message(LOG_WARNING, "Can't close stdin err=%d", errno);
if (close(1) != 0)
message(LOG_WARNING, "Can't close stdout err=%d", errno);
#ifndef NO_SYSLOG
if (Syslog > 1) Syslog = 1;
#endif
if (!LogFileName) LogFp = NULL;
if (close(2) != 0)
message(LOG_WARNING, "Can't close stderr err=%d", errno);
}
#endif
void initialize(int argc, char *argv[]) {
int i;
int j = 0; /* dummy init to suppress warnings */
#ifdef WINDOWS
WSADATA WSAData;
if (WSAStartup(MAKEWORD(1, 1), &WSAData)) {
message(LOG_ERR, "Can't find winsock");
exit(1);
}
atexit((void(*)(void))WSACleanup);
#endif
MyPid = getpid();
LogFp = stderr;
setbuf(stderr, NULL);
#ifdef USE_SSL
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
PairIndex = SSL_get_ex_new_index(0, "Pair index", NULL, NULL, NULL);
MatchIndex = SSL_SESSION_get_ex_new_index(0, "Match index",
newMatch, NULL, freeMatch);
RAND_poll();
if (!RAND_status()) {
message(LOG_WARNING, "Can't collect enough random seeds");
srand(time(NULL));
do {
u_short rnd = (u_short)rand();
RAND_seed(&rnd, sizeof(rnd));
} while (!RAND_status());
}
sslopts_default(&ServerOpts, 1);
sslopts_default(&ClientOpts, 0);
#endif
XHostsTrue = malloc(XHostsBaseSize + sizeof(struct sockaddr_storage));
if (!XHostsTrue) {
message(LOG_CRIT, "Out of memory");
exit(1);
}
XHostsTrue->next = NULL;
XHostsTrue->mbits = 0;
XHostsTrue->mode = 0;
XHostsTrue->xhost.len = sizeof(struct sockaddr_storage);
bzero(&XHostsTrue->xhost.addr, XHostsTrue->xhost.len);
XHostsTrue->xhost.addr.sa_family = AF_UNSPEC;
i = doopts(argc, argv);
if (ConfigFile) {
getconfig();
j = doopts(ConfigArgc, ConfigArgv);
}
#ifdef UNIX_DAEMON
if (DaemonMode) daemonize();
#endif
if (!DryRun && PidFile) {
FILE *fp = fopen(PidFile, "w");
if (fp) {
fprintf(fp, "%d\n", MyPid);
fclose(fp);
}
}
#ifndef NO_SYSLOG
if (Syslog) {
snprintf(SyslogName, STRMAX, "stone[%d]", MyPid);
SyslogName[STRMAX] = '\0';
openlog(SyslogName, 0, LOG_DAEMON);
if (Syslog > 1) setbuf(stdout, NULL);
}
#endif
message(LOG_INFO, "start (%s) [%d]", VERSION, MyPid);
if (Debug > 0) {
message(LOG_DEBUG, "Debug level: %d", Debug);
}
trash.next = NULL;
conns.next = NULL;
#ifndef USE_EPOLL
#ifdef FD_SET_BUG
checkFdSetBug();
#endif
FD_ZERO(&rin);
FD_ZERO(&win);
FD_ZERO(&ein);
#endif
if (ConfigFile && ConfigArgc > j) {
if (argc > i) doargs(argc, i, argv);
doargs(ConfigArgc, j, ConfigArgv);
} else {
doargs(argc, i, argv);
}
#ifndef WINDOWS
signal(SIGHUP, handler);
signal(SIGTERM, handler);
signal(SIGINT, handler);
signal(SIGPIPE, handler);
signal(SIGUSR1, handler);
signal(SIGUSR2, handler);
signal(SIGSEGV, handler);
signal(SIGBUS, handler);
signal(SIGILL, handler);
signal(SIGFPE, handler);
#endif
#ifndef NO_FORK
if (!DryRun && NForks) {
Pid = malloc(sizeof(pid_t) * NForks);
if (!Pid) {
message(LOG_CRIT, "Out of memory");
exit(1);
}
for (i=0; i < NForks; i++) {
Pid[i] = fork();
if (!Pid[i]) break;
}
if (i >= NForks) { /* the mother process */
pid_t id;
for (;;) {
int status;
id = wait(&status);
if (id < 0) continue;
message(LOG_WARNING, "Process died pid=%d, status=%x",
id, status);
for (i=0; i < NForks; i++) {
if (Pid[i] == id) break;
}
if (i < NForks) {
id = fork();
if (!id) break; /* respawned child */
else Pid[i] = id;
} else {
message(LOG_ERR, "This can't happen pid=%d", id);
}
}
}
free(Pid); /* child process */
Pid = NULL;
NForks = 0;
MyPid = getpid();
#ifndef NO_SYSLOG
if (Syslog) {
closelog();
snprintf(SyslogName, STRMAX, "stone[%d]", MyPid);
SyslogName[STRMAX] = '\0';
openlog(SyslogName, 0, LOG_DAEMON);
}
#endif
message(LOG_INFO, "child start (%s) [%d]", VERSION, MyPid);
}
#endif
#ifdef PTHREAD
pthread_attr_init(&thread_attr);
pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
#endif
#ifdef WINDOWS
PairMutex = ConnMutex = OrigMutex = AsyncMutex = NULL;
if (!(PairMutex=CreateMutex(NULL, FALSE, NULL)) ||
!(ConnMutex=CreateMutex(NULL, FALSE, NULL)) ||
!(OrigMutex=CreateMutex(NULL, FALSE, NULL)) ||
!(AsyncMutex=CreateMutex(NULL, FALSE, NULL)) ||
#ifndef USE_EPOLL
!(FdRinMutex=CreateMutex(NULL, FALSE, NULL)) ||
!(FdWinMutex=CreateMutex(NULL, FALSE, NULL)) ||
!(FdEinMutex=CreateMutex(NULL, FALSE, NULL)) ||
#endif
!(ExBufMutex=CreateMutex(NULL, FALSE, NULL)) ||
!(FPairMutex=CreateMutex(NULL, FALSE, NULL)) ||
#ifdef ADDRCACHE
!(HashMutex=CreateMutex(NULL, FALSE, NULL)) ||
#endif
!(PkBufMutex=CreateMutex(NULL, FALSE, NULL)) ) {
message(LOG_ERR, "Can't create Mutex err=%d", (int)GetLastError());
}
#endif
#ifdef OS2
PairMutex = ConnMutex = OrigMutex = AsyncMutex = NULLHANDLE;
if ((j=DosCreateMutexSem(NULL, &PairMutex, 0, FALSE)) ||
(j=DosCreateMutexSem(NULL, &ConnMutex, 0, FALSE)) ||
(j=DosCreateMutexSem(NULL, &OrigMutex, 0, FALSE)) ||
(j=DosCreateMutexSem(NULL, &AsyncMutex, 0, FALSE)) ||
#ifndef USE_EPOLL
(j=DosCreateMutexSem(NULL, &FdRinMutex, 0, FALSE)) ||
(j=DosCreateMutexSem(NULL, &FdWinMutex, 0, FALSE)) ||
(j=DosCreateMutexSem(NULL, &FdEinMutex, 0, FALSE)) ||
#endif
(j=DosCreateMutexSem(NULL, &ExBufMutex, 0, FALSE)) ||
(j=DosCreateMutexSem(NULL, &FPairMutex, 0, FALSE)) ||
#ifdef ADDRCACHE
(j=DosCreateMutexSem(NULL, &HashMutex, 0, FALSE)) ||
#endif
(j=DosCreateMutexSem(NULL, &PkBufMutex, 0, FALSE)) ) {
message(LOG_ERR, "Can't create Mutex err=%d", j);
}
#endif
#ifndef NO_THREAD
#ifdef USE_SSL
if (sslthread_initialize() < 0) {
message(LOG_ERR, "Fail to initialize SSL callback");
}
#endif
#endif
#ifndef NO_CHROOT
if (RootDir) {
char cwd[BUFMAX];
int len = strlen(RootDir);
getcwd(cwd, BUFMAX-1);
if (strncmp(cwd, RootDir, len) != 0) len = -1;
if (chroot(RootDir) < 0) {
message(LOG_WARNING, "Can't change root directory to %s", RootDir);
} else if (len <= 0) {
if (Debug > 0)
message(LOG_DEBUG, "cwd=%s is outside chroot=%s, so chdir /",
cwd, RootDir);
if (chdir("/") < 0) {
message(LOG_WARNING,
"Can't change directory to chroot / err=%d", errno);
}
}
}
#endif
#ifndef NO_SETUID
if (SetUID || SetGID) {
if (AccFileName) fchown(fileno(AccFp), SetUID, SetGID);
if (LogFileName) fchown(fileno(LogFp), SetUID, SetGID);
}
if (SetGID) if (setgid(SetGID) < 0 || setgroups(1, &SetGID) < 0) {
message(LOG_WARNING, "Can't set gid err=%d", errno);
}
if (SetUID) if (setuid(SetUID) < 0) {
message(LOG_WARNING, "Can't set uid err=%d", errno);
}
#endif
#ifdef PR_SET_DUMPABLE
if (CoreDumpDir && (SetUID || SetGID)) {
if (prctl(PR_SET_DUMPABLE, 1) < 0) {
message(LOG_ERR, "prctl err=%d", errno);
}
}
#endif
if (MinInterval > 0) {
if (Debug > 1) message(LOG_DEBUG, "MinInterval: %d", MinInterval);
}
time(&lastEstablished);
lastReadWrite = lastEstablished;
#ifdef USE_EPOLL
/* ePollFd must be created in each process */
ePollFd = epoll_create(BACKLOG_MAX);
if (ePollFd < 0) {
message(LOG_CRIT, "Can't create epoll err=%d", errno);
exit(1);
} else {
Stone *stone;
for (stone=stones; stone != NULL; stone=stone->next) {
struct epoll_event ev;
ev.events = (EPOLLIN | EPOLLPRI);
ev.data.ptr = stone;
if (Debug > 6)
message(LOG_DEBUG, "stone %d: epoll_ctl %d ADD %x",
stone->sd, ePollFd, (int)ev.data.ptr);
if (epoll_ctl(ePollFd, EPOLL_CTL_ADD, stone->sd, &ev) < 0) {
message(LOG_CRIT, "stone %d: epoll_ctl %d ADD err=%d",
stone->sd, ePollFd, errno);
exit(1);
}
}
}
#endif
}
#ifdef NT_SERVICE
void scReportStatus(DWORD curState, DWORD exitCode, DWORD hint) {
static DWORD checkPoint = 1;
if (curState == SERVICE_START_PENDING)
NTServiceStatus.dwControlsAccepted = 0;
else
NTServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
NTServiceStatus.dwCurrentState = curState;
NTServiceStatus.dwWin32ExitCode = exitCode;
NTServiceStatus.dwWaitHint = hint;
if ((curState == SERVICE_RUNNING) || (curState == SERVICE_STOPPED))
NTServiceStatus.dwCheckPoint = 0;
else
NTServiceStatus.dwCheckPoint = checkPoint++;
SetServiceStatus(NTServiceStatusHandle, &NTServiceStatus);
}
void WINAPI serviceCtrl(DWORD code) {
switch(code) {
case SERVICE_CONTROL_STOP:
scReportStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
message(LOG_INFO, "Service stopping..");
if (WaitForSingleObject(NTServiceThreadHandle, 1000) == WAIT_TIMEOUT)
TerminateThread(NTServiceThreadHandle, 0);
break;
default:
break;
}
}
DWORD WINAPI serviceThread(LPVOID lpParms) {
do {
repeater();
} while (NTServiceStatus.dwCurrentState == SERVICE_RUNNING);
ExitThread(0);
return 0;
}
void WINAPI serviceMain(DWORD argc, LPTSTR *argv) {
DWORD thid;
NTServiceStatusHandle
= RegisterServiceCtrlHandler(NTServiceName, serviceCtrl);
if (!NTServiceStatusHandle) {
message(LOG_ERR, "Can't register ServiceCtrlHandler");
return;
}
NTServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
NTServiceStatus.dwServiceSpecificExitCode = 0;
scReportStatus(SERVICE_START_PENDING, NO_ERROR, 3000);
message(LOG_INFO, "Service started");
scReportStatus(SERVICE_RUNNING, NO_ERROR, 0);
NTServiceThreadHandle = CreateThread(0, 0, serviceThread, NULL, 0, &thid);
if (NTServiceThreadHandle) {
WaitForSingleObject(NTServiceThreadHandle, INFINITE);
CloseHandle(NTServiceThreadHandle);
}
message(LOG_INFO, "Service stopped");
scReportStatus(SERVICE_STOPPED, NO_ERROR, 0);
}
#endif
#ifdef CLEAR_ARGS
static void clear_args(int argc, char *argv[]) {
char *argend = argv[argc-1] + strlen(argv[argc-1]);
char *p;
for (p=argv[1]; p < argend; p++) *p = '\0'; /* clear args */
}
#endif
int main(int argc, char *argv[]) {
initialize(argc, argv);
if (DryRun) return 0;
#ifdef NT_SERVICE
if (NTServiceName) {
SERVICE_TABLE_ENTRY dispatchTable[] =
{
{ NTServiceName, (LPSERVICE_MAIN_FUNCTION)serviceMain },
{ NULL, NULL }
};
if (!StartServiceCtrlDispatcher(dispatchTable))
message(LOG_ERR, "StartServiceCtrlDispatcher failed");
return 0;
}
#endif
#ifdef CLEAR_ARGS
clear_args(argc, argv);
#endif
#ifdef MEMLEAK_CHECK
mtrace();
#endif
for (;;) repeater();
return 0;
}
/*
For Gnu Emacs.
Local Variables:
tab-width: 8
c-basic-offset: 4
End:
*/
syntax highlighted by Code2HTML, v. 0.9.1