src/tcp/Connection.c
/***************************************************************************
YAM - Yet Another Mailer
Copyright (C) 1995-2000 Marcel Beck
Copyright (C) 2000-2022 YAM Open Source Team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
YAM Official Support Site : http://www.yam.ch
YAM OpenSource project : http://sourceforge.net/projects/yamos/
$Id$
***************************************************************************/
#include <string.h>
#include <ctype.h>
#include <clib/alib_protos.h>
#include <proto/amissl.h>
#include <proto/amisslmaster.h>
// we include bsdsocket.h but make sure to not let
// it define a global SocketBase or ISocket so that
// our socket stuff is assured to not use a global socket
// library base
#define __NOLIBBASE__
#define __NOGLOBALIFACE__
#include <proto/bsdsocket.h>
#undef __NOGLOBALIFACE__
#undef __NOLIBBASE__
#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/intuition.h>
#if defined(__amigaos3__) || defined(__MORPHOS__)
#include <proto/miami.h>
#include <proto/genesis.h>
#endif
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <netdb.h>
#if defined(__AROS__)
#define _SYS_MBUF_H_
#include <libraries/bsdsocket.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#else
#include <sys/filio.h>
#endif
#include <libraries/genesis.h>
#include "extrasrc.h"
#include "timeval.h"
#include "YAM.h"
#include "YAM_error.h"
#include "mui/YAMApplication.h"
#include "tcp/Connection.h"
#include "tcp/ssl.h"
#include "Config.h"
#include "MailServers.h"
#include "MethodStack.h"
#include "Locale.h"
#include "Requesters.h"
#include "Threads.h"
#include "Debug.h"
#ifndef SHUT_RDWR
#define SHUT_RDWR 2
#endif
#ifndef __STRPTR
#define __STRPTR char *
#endif
// SocketBase must not be shared between tasks, hence we must not use the global
// SocketBase, but the one tied to the current connection.
#if defined(__amigaos4__)
#define GET_SOCKETBASE(conn) struct SocketIFace *ISocket = (conn)->socketIFace
#else
#define GET_SOCKETBASE(conn) struct Library *SocketBase = (conn)->socketBase
#endif
#define INVALID_SOCKET -1
/// InitConnections
// initalize a shared semaphore for all connections
BOOL InitConnections(void)
{
BOOL success = FALSE;
ENTER();
if((G->connectionSemaphore = AllocSysObjectTags(ASOT_SEMAPHORE, TAG_DONE)) != NULL &&
(G->hostResolveSemaphore = AllocSysObjectTags(ASOT_SEMAPHORE, TAG_DONE)) != NULL)
{
success = TRUE;
}
RETURN(success);
return(success);
}
///
/// CleanupConnections
// delete the shared connection semaphore
void CleanupConnections(void)
{
ENTER();
if(G->hostResolveSemaphore != NULL)
{
FreeSysObject(ASOT_SEMAPHORE, G->hostResolveSemaphore);
G->hostResolveSemaphore = NULL;
}
if(G->connectionSemaphore != NULL)
{
FreeSysObject(ASOT_SEMAPHORE, G->connectionSemaphore);
G->connectionSemaphore = NULL;
}
LEAVE();
}
///
/// DupHostEnt
// duplicates a whole hostent structure
static struct hostent *DupHostEnt(const struct hostent *hentry)
{
struct hostent *new_hentry = NULL;
ENTER();
if(hentry != NULL &&
(new_hentry = calloc(1, sizeof(*new_hentry))) != NULL)
{
if(hentry->h_name != NULL)
new_hentry->h_name = strdup(hentry->h_name);
if(hentry->h_aliases != NULL)
{
int i;
int aliascount = 1;
for(i=0; hentry->h_aliases[i] != NULL; i++)
aliascount++;
if((new_hentry->h_aliases = calloc(aliascount, sizeof(char *))) != NULL)
{
for(i=0; hentry->h_aliases[i] != NULL; i++)
new_hentry->h_aliases[i] = strdup(hentry->h_aliases[i]);
}
}
new_hentry->h_addrtype = hentry->h_addrtype;
new_hentry->h_length = hentry->h_length;
if(hentry->h_addr_list != NULL)
{
int i;
int addrcount = 1;
for(i=0; hentry->h_addr_list[i] != NULL; i++)
addrcount++;
if((new_hentry->h_addr_list = calloc(addrcount, sizeof(char *))) != NULL)
{
for(i=0; hentry->h_addr_list[i] != NULL; i++)
new_hentry->h_addr_list[i] = memdup(hentry->h_addr_list[i], (size_t)hentry->h_length);
}
}
}
RETURN(new_hentry);
return new_hentry;
}
///
/// FreeHostEnt
// frees a duplicated host entry
static void FreeHostEnt(struct hostent *hentry)
{
ENTER();
if(hentry != NULL)
{
if(hentry->h_name != NULL)
free(hentry->h_name);
if(hentry->h_aliases != NULL)
{
int i;
for(i=0; hentry->h_aliases[i] != NULL; i++)
free(hentry->h_aliases[i]);
free(hentry->h_aliases);
}
if(hentry->h_addr_list != NULL)
{
int i;
for(i=0; hentry->h_addr_list[i] != NULL; i++)
free(hentry->h_addr_list[i]);
free(hentry->h_addr_list);
}
free(hentry);
}
LEAVE();
}
///
/// SetSocketOpts
// Sets the user specified options for the active socket
static void SetSocketOpts(struct Connection *conn)
{
struct TagItem tags[] =
{
{ SBTM_SETVAL(SBTC_BREAKMASK), 0 },
{ TAG_END, 0 }
};
GET_SOCKETBASE(conn);
ENTER();
// disable CTRL-C checking
SocketBaseTagList(tags);
D(DBF_NET, "set options");
if(C->SocketOptions.KeepAlive == TRUE)
{
int optval = C->SocketOptions.KeepAlive;
if(setsockopt(conn->socket, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) < 0)
{
E(DBF_NET, "setsockopt(SO_KEEPALIVE) error");
ER_NewError(tr(MSG_ER_SOCKETOPTION), "SO_KEEPALIVE");
}
else
D(DBF_NET, "set SO_KEEPALIVE in socket");
}
if(C->SocketOptions.NoDelay == TRUE)
{
int optval = C->SocketOptions.NoDelay;
if(setsockopt(conn->socket, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval)) < 0)
{
E(DBF_NET, "setsockopt(TCP_NODELAY) error");
ER_NewError(tr(MSG_ER_SOCKETOPTION), "TCP_NODELAY");
}
else
D(DBF_NET, "set TCP_NODELAY in socket");
}
if(C->SocketOptions.LowDelay == TRUE)
{
int optval = IPTOS_LOWDELAY;
if(setsockopt(conn->socket, IPPROTO_IP, IP_TOS, &optval, sizeof(optval)) < 0)
{
E(DBF_NET, "setsockopt(IPTOS_LOWDELAY) error");
ER_NewError(tr(MSG_ER_SOCKETOPTION), "IPTOS_LOWDELAY");
}
else
D(DBF_NET, "set IPTOS_LOWDELAY in socket");
}
if(C->SocketOptions.SendBuffer > -1)
{
int optval = C->SocketOptions.SendBuffer;
if(setsockopt(conn->socket, SOL_SOCKET, SO_SNDBUF, &optval, sizeof(optval)) < 0)
{
E(DBF_NET, "setsockopt(SO_SNDBUF) error");
ER_NewError(tr(MSG_ER_SOCKETOPTION), "SO_SNDBUF");
}
else
D(DBF_NET, "set SO_SNDBUF in socket");
}
if(C->SocketOptions.RecvBuffer > -1)
{
int optval = C->SocketOptions.RecvBuffer;
if(setsockopt(conn->socket, SOL_SOCKET, SO_RCVBUF, &optval, sizeof(optval)) < 0)
{
E(DBF_NET, "setsockopt(SO_RCVBUF) error");
ER_NewError(tr(MSG_ER_SOCKETOPTION), "SO_RCVBUF");
}
else
D(DBF_NET, "set SO_RCVBUF in socket");
}
if(C->SocketOptions.SendLowAt > -1)
{
int optval = C->SocketOptions.SendLowAt;
if(setsockopt(conn->socket, SOL_SOCKET, SO_SNDLOWAT, &optval, sizeof(optval)) < 0)
{
E(DBF_NET, "setsockopt(SO_SNDLOWAT) error");
ER_NewError(tr(MSG_ER_SOCKETOPTION), "SO_SNDLOWAT");
}
else
D(DBF_NET, "set SO_SNDLOWAT in socket");
}
if(C->SocketOptions.RecvLowAt > -1)
{
int optval = C->SocketOptions.RecvLowAt;
if(setsockopt(conn->socket, SOL_SOCKET, SO_RCVLOWAT, &optval, sizeof(optval)) < 0)
{
E(DBF_NET, "setsockopt(SO_RCVLOWAT) error");
ER_NewError(tr(MSG_ER_SOCKETOPTION), "SO_RCVLOWAT");
}
else
D(DBF_NET, "set SO_RCVLOWAT in socket");
}
if(C->SocketOptions.SendTimeOut > -1)
{
struct TimeVal tv;
tv.Seconds = C->SocketOptions.SendTimeOut;
tv.Microseconds = 0;
if(setsockopt(conn->socket, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(struct TimeVal)) < 0)
{
E(DBF_NET, "setsockopt(SO_SNDTIMEO) error");
ER_NewError(tr(MSG_ER_SOCKETOPTION), "SO_SNDTIMEO");
}
else
D(DBF_NET, "set SO_SNDTIMEO in socket");
}
if(C->SocketOptions.RecvTimeOut > -1)
{
struct TimeVal tv;
tv.Seconds = C->SocketOptions.RecvTimeOut;
tv.Microseconds = 0;
if(setsockopt(conn->socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(struct TimeVal)) < 0)
{
E(DBF_NET, "setsockopt(SO_RCVTIMEO) error");
ER_NewError(tr(MSG_ER_SOCKETOPTION), "SO_RCVTIMEO");
}
else
D(DBF_NET, "set SO_RCVTIMEO in socket");
}
// lets print out the current socket options
#if defined(DEBUG)
{
int optval;
struct TimeVal tv;
socklen_t optlen;
socklen_t tvlen;
D(DBF_NET, "opened socket %08lx", conn->socket);
// the value of the length pointer must be updated ahead of each call, because
// getsockopt() might have modified it.
optlen = sizeof(optval);
if(getsockopt(conn->socket, SOL_SOCKET, SO_KEEPALIVE, &optval, &optlen) >= 0)
D(DBF_NET, "SO_KEEPALIVE..: %ld [bool]", optval);
optlen = sizeof(optval);
if(getsockopt(conn->socket, IPPROTO_TCP, TCP_NODELAY, &optval, &optlen) >= 0)
D(DBF_NET, "TCP_NODELAY...: %ld [bool]", optval);
optlen = sizeof(optval);
if(getsockopt(conn->socket, IPPROTO_IP, IP_TOS, &optval, &optlen) >= 0)
D(DBF_NET, "IPTOS_LOWDELAY: %ld [bool]", isAnyFlagSet(optval, IPTOS_LOWDELAY));
optlen = sizeof(optval);
if(getsockopt(conn->socket, SOL_SOCKET, SO_SNDBUF, &optval, &optlen) >= 0)
D(DBF_NET, "SO_SNDBUF.....: %ld bytes", optval);
optlen = sizeof(optval);
if(getsockopt(conn->socket, SOL_SOCKET, SO_RCVBUF, &optval, &optlen) >= 0)
D(DBF_NET, "SO_RCVBUF.....: %ld bytes", optval);
optlen = sizeof(optval);
if(getsockopt(conn->socket, SOL_SOCKET, SO_SNDLOWAT, &optval, &optlen) >= 0)
D(DBF_NET, "SO_SNDLOWAT...: %ld bytes", optval);
optlen = sizeof(optval);
if(getsockopt(conn->socket, SOL_SOCKET, SO_RCVLOWAT, &optval, &optlen) >= 0)
D(DBF_NET, "SO_RCVLOWAT...: %ld bytes", optval);
tvlen = sizeof(tv);
if(getsockopt(conn->socket, SOL_SOCKET, SO_SNDTIMEO, &tv, &tvlen) >= 0)
D(DBF_NET, "SO_SNDTIMEO...: %ld.%ld s", tv.Seconds, tv.Microseconds);
tvlen = sizeof(tv);
if(getsockopt(conn->socket, SOL_SOCKET, SO_RCVTIMEO, &tv, &tvlen) >= 0)
D(DBF_NET, "SO_RCVTIMEO...: %ld.%ld s", tv.Seconds, tv.Microseconds);
}
#endif
LEAVE();
}
///
/// CreateConnection
// create a connection structure
struct Connection *CreateConnection(const BOOL needSocket)
{
struct Connection *result = NULL;
struct Connection *conn;
ENTER();
if((conn = calloc(1, sizeof(*conn))) != NULL)
{
LONG abortSignal = ThreadAbortSignal();
BOOL socketOk = FALSE;
conn->socket = INVALID_SOCKET;
if(needSocket == TRUE)
{
if((conn->receiveBuffer = malloc(C->TRBufferSize)) != NULL)
{
if((conn->sendBuffer = malloc(C->TRBufferSize)) != NULL)
{
// each connection gets its own SocketBase, this is required for threads
if((conn->socketBase = OpenLibrary("bsdsocket.library", 2L)) != NULL &&
GETINTERFACE("main", 1, conn->socketIFace, conn->socketBase))
{
struct TagItem tags[] =
{
{ SBTM_SETVAL(SBTC_BREAKMASK), (1UL << abortSignal) },
{ TAG_END, 0 }
};
GET_SOCKETBASE(conn);
D(DBF_NET, "got socket interface");
// tell the stack to react on which break signals
// by default we can be aborted by the standard break signal
if(SocketBaseTagList(tags) == 0)
{
D(DBF_NET, "set break mask");
conn->receivePtr = conn->receiveBuffer;
conn->sendPtr = conn->sendBuffer;
// remember the buffer sizes in case this is
// modified as long as this connection exists
conn->receiveBufferSize = C->TRBufferSize;
conn->sendBufferSize = C->TRBufferSize;
socketOk = TRUE;
}
else
E(DBF_NET, "failed to set break mask");
}
else
E(DBF_NET, "failed open bsdsocket.library");
}
else
E(DBF_NET, "failed to allocate send buffer (%ld bytes)", C->TRBufferSize);
}
else
E(DBF_NET, "failed to allocate receive buffer (%ld bytes)", C->TRBufferSize);
}
else
{
// no socket required
// fake a successful socket
socketOk = TRUE;
}
if(socketOk == TRUE)
{
// set to no error per default
conn->error = CONNECTERR_NO_ERROR;
conn->abortSignal = abortSignal;
conn->connectedFromMainThread = IsMainThread();
result = conn;
}
}
if(result == NULL)
DeleteConnection(conn);
RETURN(result);
return result;
}
///
/// DeleteConnection
// delete a connection structure
void DeleteConnection(struct Connection *conn)
{
ENTER();
if(conn != NULL)
{
DisconnectFromHost(conn);
if(conn->socketBase != NULL)
{
DROPINTERFACE(conn->socketIFace);
CloseLibrary(conn->socketBase);
conn->socketBase = NULL;
}
free(conn->sendBuffer);
free(conn->receiveBuffer);
free(conn);
}
LEAVE();
}
///
/// CheckSingleInterface
// checks a single interface to be online
enum TCPIPStack
{
TCPIP_Generic = 0,
TCPIP_RoadShow,
TCPIP_Miami,
TCPIP_Genesis
};
static BOOL CheckSingleInterface(struct Connection *conn, const char *iface, const enum TCPIPStack tcpipStack, const struct Library *stackBase)
{
BOOL isOnline = FALSE;
ENTER();
switch(tcpipStack)
{
case TCPIP_Generic:
{
if(stackBase == NULL && conn->socketBase != NULL)
{
D(DBF_NET, "assuming interface '%s' to be up", iface);
isOnline = TRUE;
}
}
break;
case TCPIP_RoadShow:
{
#if defined(__amigaos4__)
LONG onlineState = 0;
GET_SOCKETBASE(conn);
if(QueryInterfaceTags((char *)iface, IFQ_State, &onlineState, TAG_END) == 0)
{
if(onlineState == SM_Up)
{
D(DBF_NET, "found RoadShow interface '%s' to be UP", iface);
isOnline = TRUE;
}
else
W(DBF_NET, "found RoadShow interface '%s' to be DOWN", iface);
}
else
E(DBF_NET, "couldn't query interface status. Unknown interface.");
#endif
}
break;
case TCPIP_Miami:
{
#if defined(__amigaos3__) || defined(__MORPHOS__)
struct Library *MiamiBase = (struct Library *)stackBase;
if(MiamiIsOnline(iface[0] != '\0' ? (char *)iface : NULL))
{
D(DBF_NET, "found Miami interface '%s' to be UP", iface);
isOnline = TRUE;
}
else
W(DBF_NET, "found Miami interface '%s' to be DOWN", iface);
#endif
}
break;
case TCPIP_Genesis:
{
#if defined(__amigaos3__) || defined(__MORPHOS__)
struct Library *GenesisBase = (struct Library *)stackBase;
if(IsOnline(iface[0] != '\0' ? (long)iface : 0))
{
D(DBF_NET, "found Genesis interface '%s' to be UP", iface);
isOnline = TRUE;
}
else
W(DBF_NET, "found Genesis interface '%s' to be DOWN", iface);
#endif
}
break;
}
RETURN(isOnline);
return isOnline;
}
///
/// CheckAllInterfaces
// check if any of the given interfaces is online
static BOOL CheckAllInterfaces(struct Connection *conn, const enum TCPIPStack tcpipStack, const struct Library *stackBase)
{
BOOL anyIsOnline = FALSE;
ENTER();
SHOWSTRING(DBF_NET, C->IOCInterfaces);
if(C->IOCInterfaces[0] != '\0')
{
char *ifaces;
// duplicate the interfaces setting and split it into its parts
if((ifaces = strdup(C->IOCInterfaces)) != NULL)
{
char *iface = ifaces;
char *next;
do
{
if((next = strpbrk(iface, ", ")) != NULL)
*next++ = '\0';
if(iface[0] != '\0')
{
// check every single interface to be online and
// bail out as soon as we found an active interface
D(DBF_NET, "checking interface '%s'", iface);
if(CheckSingleInterface(conn, iface, tcpipStack, stackBase) == TRUE)
{
anyIsOnline = TRUE;
break;
}
}
iface = next;
}
while(iface != NULL);
free(ifaces);
}
}
else
{
// check with no interface name
anyIsOnline = CheckSingleInterface(conn, "", tcpipStack, stackBase);
}
RETURN(anyIsOnline);
return anyIsOnline;
}
///
/// ConnectionIsOnline
// check whether the connection can be used to connect to a host
BOOL ConnectionIsOnline(struct Connection *conn)
{
BOOL isonline = FALSE;
BOOL deleteConnection = FALSE;
ENTER();
if(conn == NULL)
{
conn = CreateConnection(TRUE);
deleteConnection = TRUE;
}
if(conn != NULL)
{
// on AmigaOS4 we always do an online check via the v4 version
// of bsdsocket.library (RoadShow) as it should always be present
#if defined(__amigaos4__)
// if we find a bsdsocket.library < v4 on OS4
// we always assume it to be online
if(LIB_VERSION_IS_AT_LEAST(conn->socketBase, 4, 0) == FALSE)
{
isonline = TRUE;
}
else
{
D(DBF_NET, "identified bsdsocket v4 TCP/IP stack (RoadShow)");
// in case the user hasn't specified a specific
// interface or set that the online check for a specific
// interface should be disabled we just do a general query
if(C->IsOnlineCheck == FALSE || IsStrEmpty(C->IOCInterfaces))
{
ULONG status = 0;
struct TagItem tags[] =
{
{ SBTM_GETREF(SBTC_SYSTEM_STATUS), (IPTR)&status },
{ TAG_END, 0 }
};
GET_SOCKETBASE(conn);
if(SocketBaseTagList(tags) == 0)
{
if(isAnyFlagSet(status, SBSYSSTAT_Interfaces))
isonline = TRUE;
}
else
E(DBF_NET, "couldn't query TCP/IP stack for its system status.");
}
else
{
ULONG hasInterfaceAPI = FALSE;
struct TagItem tags[] =
{
{ SBTM_GETREF(SBTC_HAVE_INTERFACE_API), (IPTR)&hasInterfaceAPI },
{ TAG_END, 0 }
};
GET_SOCKETBASE(conn);
if(SocketBaseTagList(tags) == 0 && hasInterfaceAPI == TRUE)
{
// now that we know that we have an interface API, we can
// go and query the interfaces if any of these is up&running
// correctly.
isonline = CheckAllInterfaces(conn, TCPIP_RoadShow, NULL);
}
else
{
E(DBF_NET, "couldn't query TCP/IP stack's interface API (%ld).", hasInterfaceAPI);
}
}
}
#else
#if !defined(__AROS__)
if(C->IsOnlineCheck == TRUE)
{
struct Library *MiamiBase;
struct Library *GenesisBase;
if((MiamiBase = OpenLibrary("miami.library", 10L)) != NULL)
{
D(DBF_NET, "identified Miami TCP/IP stack");
isonline = CheckAllInterfaces(conn, TCPIP_Miami, MiamiBase);
CloseLibrary(MiamiBase);
MiamiBase = NULL;
}
else if((GenesisBase = OpenLibrary("genesis.library", 1L)) != NULL)
{
D(DBF_NET, "identified Genesis TCP/IP stack");
isonline = CheckAllInterfaces(conn, TCPIP_Genesis, GenesisBase);
CloseLibrary(GenesisBase);
GenesisBase = NULL;
}
else if(LIB_VERSION_IS_AT_LEAST(conn->socketBase, 2, 0) == TRUE)
{
D(DBF_NET, "identified generic TCP/IP stack with bsdsocket.library v2+");
isonline = CheckAllInterfaces(conn, TCPIP_Generic, NULL);
}
}
else
#endif // !__AROS__
if(LIB_VERSION_IS_AT_LEAST(conn->socketBase, 2, 0) == TRUE)
isonline = CheckAllInterfaces(conn, TCPIP_Generic, NULL);
#endif // __amigaos4__
}
if(deleteConnection == TRUE)
DeleteConnection(conn);
D(DBF_NET, "found the TCP/IP stack to be %s", isonline ? "ONLINE" : "OFFLINE");
RETURN(isonline);
return isonline;
}
///
/// GetHostByName
// get a struct hostent* with timeout; Note that the returned structure MUST
// be cleaned with FreeHostEnt() afterwards.
struct hostent *GetHostByName(struct Connection *conn, const char *host)
{
struct hostent *hostaddr = NULL;
struct MsgPort *timeoutPort;
GET_SOCKETBASE(conn);
// gethostbyname() has no implicit or explicit timeout mechanism.
// Propose this network setup:
// Amiga <-> hub/switch <-> Windows PC using a UMTS modem
// In this scenario the gethostbyname() call might never return in case the
// Windows machine is shut down while YAM tries to connect to a host. The
// timeout mechanism we put around the gethostbyname() call here makes sure
// that the call will eventually return if it would have been stuck otherwise.
ENTER();
// set up a message port with uses our abort signal as signal bit
// in case the time runs out it will abort the gethostbyname() call
// and let it return NULL
if((timeoutPort = AllocSysObjectTags(ASOT_PORT,
ASOPORT_Signal, conn->abortSignal,
ASOPORT_AllocSig, FALSE,
TAG_DONE)) != NULL)
{
struct TimeRequest *timeoutIO;
if((timeoutIO = AllocSysObjectTags(ASOT_IOREQUEST,
ASOIOR_Size, sizeof(*timeoutIO),
ASOIOR_ReplyPort, (IPTR)timeoutPort,
TAG_DONE)) != NULL)
{
if(OpenDevice(TIMERNAME, UNIT_VBLANK, (struct IORequest *)timeoutIO, 0L) == 0)
{
struct hostent *hentry;
// Let's start the timeout to let gethostbyname() return failure in case
// the name could not be resolved within the given time limit.
timeoutIO->Request.io_Command = TR_ADDREQUEST;
timeoutIO->Time.Seconds = C->SocketTimeout;
timeoutIO->Time.Microseconds = 0;
SendIO((struct IORequest *)timeoutIO);
// we have to makre sure only one task/process uses gethostbyname() at a
// time because it returns static data
ObtainSemaphore(G->hostResolveSemaphore);
// this is what all the fuss is about
// NOTE: on WinUAE this call might lock up when being called from several tasks
// simultaneously, but since we have a timeout mechanism this will not end up
// in a deadlock.
if((hentry = gethostbyname((char *)host)) != NULL)
{
// duplicate the hostent structure as gethostbyname() references static data
// and thus may be overwriting it with the next call.
if((hostaddr = DupHostEnt(hentry)) != NULL)
{
// output some debug information in case users have problems
// with gethostbyname() returning incorrect things
#if defined(DEBUG)
{
int i;
D(DBF_NET, "Host '%s':", host);
D(DBF_NET, " Officially: '%s'", hostaddr->h_name);
for(i = 0; hostaddr->h_aliases[i]; ++i)
D(DBF_NET, " Alias: '%s'", hostaddr->h_aliases[i]);
D(DBF_NET, " Type: '%s'", hostaddr->h_addrtype == AF_INET ? "AF_INET" : "AF_INET6");
if(hostaddr->h_addrtype == AF_INET)
{
for(i = 0; hostaddr->h_addr_list[i]; ++i)
D(DBF_NET, " Address: '%s'", Inet_NtoA(((struct in_addr *)hostaddr->h_addr_list[i])->s_addr));
}
}
#endif
}
}
else
E(DBF_NET, "gethostbyname() returned with error");
// release the semaphore as we are finished calling gethostbyname() and
// copied the information we needed
ReleaseSemaphore(G->hostResolveSemaphore);
// abort the timer in case we were successful and clean up behind us
if(CheckIO((struct IORequest *)timeoutIO) == NULL)
AbortIO((struct IORequest *)timeoutIO);
WaitIO((struct IORequest *)timeoutIO);
// make sure we don't leave the abort signal pending
SetSignal(0UL, 1UL << conn->abortSignal);
CloseDevice((struct IORequest *)timeoutIO);
}
FreeSysObject(ASOT_IOREQUEST, timeoutIO);
}
FreeSysObject(ASOT_PORT, timeoutPort);
}
RETURN(hostaddr);
return hostaddr;
}
///
/// GetFQDN
// function that returns either the hostname or [x.x.x.x] as the full qualified
// domain name (FQDN) for things that have to comply to RFC 2822, etc. This function
// tries to mimic the behaviour of the same functionality in the Thunderbird mail
// client (see http://mxr.mozilla.org/comm-central/source/mailnews/compose/src/nsSmtpProtocol.cpp#324)
int GetFQDN(struct Connection *conn, char *name, size_t namelen)
{
int result = -1;
ENTER();
// if the user has specified an own FQDN we use that one
// instead.
if(C->MachineFQDN[0] != '\0')
{
strlcpy(name, C->MachineFQDN, namelen);
result = 0;
}
else
{
BOOL validFQDN = FALSE;
GET_SOCKETBASE(conn);
if((result = gethostname(name, namelen-1)) == 0)
name[namelen-1] = '\0'; // gethostname() may have returned 255 chars max (cf. man page)
else
name[0] = '\0'; // gethostname() failed: pretend empty string
D(DBF_NET, "gethostname() returned: '%s'", name);
// now we have to check whether gethostname() returned a valid FQDN or
// if there are invalid characters in it. In addition, we check that it is a full
// domain name with a dot or otherwise we use a literal string [X.X.X.X]
if(name[0] != '\0')
{
int i;
// a FQDN must have a least one dot
if(strchr(name, '.') != NULL)
{
validFQDN = TRUE;
for(i=0; name[i] != '\0'; i++)
{
// check if name[i] is within "a-zA-Z0-9.-" or if this is an
// invalid character
if(isalnum(name[i]) == 0 && name[i] != '.' && name[i] != '-')
{
validFQDN = FALSE;
break;
}
}
}
// if we found the hostname to be invalid (e.g. contain invalid characters
// or missing a dot) we get the IP for it and put that into brackets [x.x.x.x]
// instead.
if(validFQDN == FALSE)
{
struct hostent *hentry;
W(DBF_NET, "gethostname returned invalid hostname: '%s'", name);
if((hentry = GetHostByName(conn, name)) != NULL)
{
if(hentry->h_addrtype == AF_INET)
snprintf(name, namelen, "[%s]", Inet_NtoA(((struct in_addr *)hentry->h_addr_list[0])->s_addr));
else
snprintf(name, namelen, "[IPv6:%s]", Inet_NtoA(((struct in_addr *)hentry->h_addr_list[0])->s_addr));
validFQDN = TRUE;
W(DBF_NET, "using literal string '%s' instead", name);
FreeHostEnt(hentry);
}
}
}
else
W(DBF_NET, "gethostname() returned empty string");
// if we still have an invalid FQDN we simply insert the localhost address
// into the hostname string
if(validFQDN == FALSE)
{
strlcpy(name, "[127.0.0.1]", namelen);
W(DBF_NET, "using fallback string '%s' instead", name);
}
}
RETURN(result);
return result;
}
///
/// ConnectToHost
// Creates a new connection and tries to connect to a internet service
enum ConnectError ConnectToHost(struct Connection *conn, const struct MailServerNode *msn)
{
enum ConnectError error = CONNECTERR_NO_CONNECTION;
ENTER();
if(conn != NULL)
{
struct hostent *hostaddr;
GET_SOCKETBASE(conn);
D(DBF_NET, "connecting to host '%s' port %ld", msn->hostname, msn->port);
// obtain the hostent from the supplied host name
hostaddr = GetHostByName(conn, msn->hostname);
// check for a possible abortion and a successful obtainment of the hostent
if(conn->abort == FALSE && hostaddr != NULL)
{
int i;
// now we try a connection for every address we have for this host
// because a hostname can have more than one IP in h_addr_list[]
for(i = 0; hostaddr->h_addr_list[i]; ++i)
{
// lets create a standard AF_INET socket now
if((conn->socket = socket(AF_INET, SOCK_STREAM, 0)) != INVALID_SOCKET)
{
long nonBlockingIO = 1;
// now we set the socket for non-blocking I/O
if(IoctlSocket(conn->socket, FIONBIO, (void *)&nonBlockingIO) != -1)
{
int connectIssued = 0;
struct sockaddr_in socketAddr; // the host this connection was established to
D(DBF_NET, "host '%s', successfully set socket to non-blocking I/O", msn->hostname);
// set the socket options the user has defined
// in the configuration
SetSocketOpts(conn);
// copy the hostaddr data in a local copy for further reference
memset(&socketAddr, 0, sizeof(struct sockaddr_in));
socketAddr.sin_len = sizeof(struct sockaddr_in);
socketAddr.sin_family = AF_INET;
socketAddr.sin_port = htons(msn->port);
memcpy(&socketAddr.sin_addr, hostaddr->h_addr_list[i], (size_t)hostaddr->h_length);
D(DBF_NET, "trying TCP/IP connection to host '%s' with IP %s on port %ld", msn->hostname, Inet_NtoA(((struct in_addr *)hostaddr->h_addr_list[i])->s_addr), msn->port);
// set to no error per default
conn->error = CONNECTERR_NO_ERROR;
// we call connect() to establish the connection to the socket. In case of
// a non-blocking connection this call will return immediately with -1 and
// the errno value will be EINPROGRESS, EALREADY or EISCONN
while(conn->error == CONNECTERR_NO_ERROR &&
connect(conn->socket, (struct sockaddr *)&socketAddr, sizeof(struct sockaddr_in)) == -1)
{
LONG connerr = -1;
struct TagItem tags[] =
{
{ SBTM_GETREF(SBTC_ERRNO), (IPTR)&connerr },
{ TAG_END, 0 }
};
// count the number of connect() processings
connectIssued++;
// get the error value which should normally be set by a connect()
SocketBaseTagList(tags);
// check the errno variable which connect() will set
switch(connerr)
{
// as we are doing non-blocking socket I/O we check the error code
// and see if it is EINPROGRESS, the connection is currently in progress.
// so we go and call WaitSelect() to wait a specific amount of time until
// the connection succeeds.
case EINPROGRESS:
case EALREADY:
{
LONG retVal;
conn->timeout.tv_sec = C->SocketTimeout;
conn->timeout.tv_usec = 0;
// now we put our socket handle into a descriptor set
// we can pass on to WaitSelect()
FD_ZERO(&conn->fdset);
FD_SET(conn->socket, &conn->fdset);
retVal = WaitSelect(conn->socket+1, NULL, &conn->fdset, NULL, (APTR)&conn->timeout, NULL);
// if WaitSelect() returns 1 we successfully waited for
// being able to write to the socket. So we can break out of the
// loop immediately and continue with our stuff.
if(retVal >= 1 && FD_ISSET(conn->socket, &conn->fdset))
{
int errval = -1;
socklen_t errlen = sizeof(errval);
D(DBF_NET, "host '%s', WaitSelect() succeeded", msn->hostname);
// normally we should not set an error code here but
// continue cleanly so that a second connect() will
// return EISCONN. However, it seems there are some broken
// TCP/IP implementations (e.g. bsdsocket of UAE) which
// return an error for subsequent connect() calls instead.
//
// So what we do here to workaround the issue is, we
// query the socket options and see if SO_ERROR is zero
// (no error) which sould signal that the connection
// worked out fine, else we return an unknown error.
if(getsockopt(conn->socket, SOL_SOCKET, SO_ERROR, &errval, &errlen) == 0 &&
errval == 0)
{
conn->error = CONNECTERR_SUCCESS;
}
else
{
E(DBF_NET, "SO_ERROR in socket options found! error value read: %ld", errval);
conn->error = CONNECTERR_UNKNOWN_ERROR;
}
}
else if(retVal == 0)
{
// the WaitSelect() call timed out or it received a break
// signal
W(DBF_NET, "host '%s', WaitSelect() socket timeout reached", msn->hostname);
conn->error = CONNECTERR_TIMEDOUT;
}
else
{
// the rest should signal an error
E(DBF_NET, "host '%s', WaitSelect() returned an error: %ld", msn->hostname, connerr);
conn->error = CONNECTERR_UNKNOWN_ERROR;
}
}
break;
// check for EISCONN
case EISCONN:
{
// EISCONN is only a valid reponse in case we have already issued
// a connect() before
if(connectIssued > 1)
{
D(DBF_NET, "connect() signaled an EISCONN success");
conn->error = CONNECTERR_SUCCESS;
}
else
{
E(DBF_NET, "connect() signaled an EISCONN failure");
conn->error = CONNECTERR_UNKNOWN_ERROR;
}
}
break;
// all other errors are real errors
default:
{
E(DBF_NET, "connect() returned with an unrecoverable error: %ld", connerr);
conn->error = CONNECTERR_UNKNOWN_ERROR;
}
break;
}
}
// in case the connection suceeded immediately (no entered the while)
// we flag this connection as success
if(conn->error == CONNECTERR_NO_ERROR)
conn->error = CONNECTERR_SUCCESS;
if(conn->error == CONNECTERR_SUCCESS)
{
// now we are properly connected
conn->isConnected = TRUE;
// save the msn structure for later reference
conn->server = (struct MailServerNode *)msn;
// one more active connection
ObtainSemaphore(G->connectionSemaphore);
G->activeConnections++;
if(G->activeConnections == 1)
{
// update the AppIcon to show that there is an active connection now
PushMethodOnStack(G->App, 1, MUIM_YAMApplication_UpdateAppIcon);
}
ReleaseSemaphore(G->connectionSemaphore);
// reset the buffer pointers
conn->receiveCount = 0;
conn->receivePtr = conn->receiveBuffer;
conn->sendCount = 0;
conn->sendPtr = conn->sendBuffer;
}
}
else
{
E(DBF_NET, "couldn't establish non-blocking IO: %ld", Errno());
conn->error = CONNECTERR_NO_NONBLOCKIO;
break;
}
}
else
{
// socket() failed
E(DBF_NET, "socket() returned an error: %ld", Errno());
conn->error = CONNECTERR_NO_SOCKET;
break;
}
// if the user pressed the abort button in the transfer
// window we have to exit the loop
if(conn->abort == TRUE)
{
conn->error = CONNECTERR_ABORTED;
break;
}
else if(conn->error == CONNECTERR_SUCCESS)
break;
else
E(DBF_NET, "connection result %ld, trying next IP of host '%s'", conn->error, msn->hostname);
}
// free the duplicate hostent structure
FreeHostEnt(hostaddr);
}
else
{
// gethostbyname() either failed or was aborted
conn->error = (conn->abort == TRUE) ? CONNECTERR_ABORTED : CONNECTERR_UNKNOWN_HOST;
}
if(conn->error != CONNECTERR_SUCCESS)
E(DBF_NET, "ConnectToHost() connection error: %ld", conn->error);
else
D(DBF_NET, "connection to '%s:%ld' succeeded", msn->hostname, msn->port);
error = conn->error;
}
RETURN(error);
return error;
}
///
/// DisconnectFromHost
// Terminate and free a connection
void DisconnectFromHost(struct Connection *conn)
{
ENTER();
if(conn != NULL)
{
GET_SOCKETBASE(conn);
D(DBF_NET, "disconnecting TCP/IP session %08lx", conn);
// shut down the SSL stuff
if(conn->ssl != NULL)
{
int ret;
// clear any error
ERR_clear_error();
// call SSL_shutdown() to shutdown the SSL connection
// but take care of the return values
if((ret = SSL_shutdown(conn->ssl)) < 0)
E(DBF_NET, "SSL_shutdown (1st time) returned fatal error: %d %d", ret, SSL_get_error(conn->ssl, ret));
else if(ret == 0)
{
D(DBF_NET, "SSL_shutdown (1st time) returned: %d %d", ret, SSL_get_error(conn->ssl, ret));
// we wait "10 ticks" before issuing the second attempt to shutdown the SSL
// channel. NOTE: This is required due to a problem/bug in OpenSSL versions < 0.9.8m which
// AmiSSLv3 is based on as the second SSL_shutdown() call should return -1 and signal
// that we either have to perform a SSL_read() or SSL_write() again, which it doesn't.
Delay(10);
// According to docs at the OpenSSL website, this means that the shutdown
// has not yet finished, and we must call SSL_shutdown again..
if((ret = SSL_shutdown(conn->ssl)) <= 0)
W(DBF_NET, "SSL_shutdown (2nd time) failed: %d %d", ret, SSL_get_error(conn->ssl, ret));
else
D(DBF_NET, "SSL_shutdown (2nd time) returned: %d %d", ret, SSL_get_error(conn->ssl, ret));
}
SSL_free(conn->ssl);
conn->ssl = NULL;
}
if(conn->socket != INVALID_SOCKET)
{
// close the connection
shutdown(conn->socket, SHUT_RDWR);
CloseSocket(conn->socket);
conn->socket = INVALID_SOCKET;
}
if(AmiSSLBase != NULL)
CleanupAmiSSLA(NULL);
if(conn->isConnected == TRUE)
{
// we are no longer connected
conn->isConnected = FALSE;
// one active connection less
ObtainSemaphore(G->connectionSemaphore);
G->activeConnections--;
if(G->activeConnections == 0)
{
// update the AppIcon to show that there are no active connections anymore
PushMethodOnStack(G->App, 1, MUIM_YAMApplication_UpdateAppIcon);
}
ReleaseSemaphore(G->connectionSemaphore);
}
}
LEAVE();
}
///
/// ReadFromHost
// an unbuffered implementation/wrapper for SSL_read/recv() where all available
// data upto maxlen will be put into the memory at ptr while taking care of
// non-blocking IO. Returns the number of bytes read or -1 on an error
static int ReadFromHost(struct Connection *conn, char *ptr, const int maxlen)
{
int result;
int nread = -1; // -1 is error
int status = 0; // < 0 error, 0 unknown, > 0 no error
GET_SOCKETBASE(conn);
ENTER();
if(conn->ssl != NULL)
{
// use SSL methods to get/process all data
do
{
// read out data and stop our loop in case there
// isn't anything to read anymore or we have filled
// maxlen data
nread = SSL_read(conn->ssl, ptr, maxlen);
// if nread > 0 we read _some_ data and can
// break out
if(nread > 0)
break;
else // <= 0 found, check error state
{
int err = SSL_get_error(conn->ssl, nread);
switch(err)
{
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
{
// we are using non-blocking socket IO so an SSL_ERROR_WANT_READ
// signals that the SSL socket wants us to wait until data
// is available and reissue the SSL_read() command.
LONG retVal;
conn->timeout.tv_sec = C->SocketTimeout;
conn->timeout.tv_usec = 0;
// now we put our socket handle into a descriptor set
// we can pass on to WaitSelect()
FD_ZERO(&conn->fdset);
FD_SET(conn->socket, &conn->fdset);
// depending on the SSL error (WANT_READ/WANT_WRITE)
// we either do a WaitSelect() on the read or write mode
// as with SSL both things can happen
// see http://www.openssl.org/docs/ssl/SSL_read.html
if(err == SSL_ERROR_WANT_READ)
retVal = WaitSelect(conn->socket+1, &conn->fdset, NULL, NULL, (APTR)&conn->timeout, NULL);
else
retVal = WaitSelect(conn->socket+1, NULL, &conn->fdset, NULL, (APTR)&conn->timeout, NULL);
// if WaitSelect() returns 1 we successfully waited for
// being able to read from the socket. So we go and do another
// iteration in the while() loop as the next connect() call should
// return EISCONN if the connection really succeeded.
if(retVal >= 1 && FD_ISSET(conn->socket, &conn->fdset))
{
// everything fine
continue;
}
else if(retVal == 0)
{
W(DBF_NET, "WaitSelect() socket timeout reached");
status = -1; // signal error
conn->error = CONNECTERR_TIMEDOUT;
}
else
{
// the rest should signal an error
E(DBF_NET, "WaitSelect() returned an error: %ld", err);
status = -1; // signal error
conn->error = CONNECTERR_UNKNOWN_ERROR;
}
}
break;
case SSL_ERROR_ZERO_RETURN:
{
// in case nread is zero the connection
// was shut down cleanly...
if(nread == 0)
{
// signal no error by +1
status = 1;
}
else
{
E(DBF_NET, "SSL_write() returned SSL_ERROR_ZERO_RETURN");
status = -1; // signal error
conn->error = CONNECTERR_UNKNOWN_ERROR;
}
}
break;
default:
{
E(DBF_NET, "SSL_read() returned an error %ld", err);
status = -1; // signal error
conn->error = CONNECTERR_UNKNOWN_ERROR;
}
break;
}
}
}
while(status == 0);
}
else
{
// use normal socket methods to query/process data
do
{
// read out data from our socket and return cleanly in
// case zero is returned (clean shutdown), otherwise
// read as much as possible
nread = recv(conn->socket, ptr, maxlen, 0);
// if nread >= 0 we read _some_ data or reach the
// end of the socket and can break out
if(nread >= 0)
break;
else // < 0 found, check error state
{
LONG err = -1;
struct TagItem tags[] =
{
{ SBTM_GETREF(SBTC_ERRNO), (IPTR)&err },
{ TAG_END, 0 }
};
// get the error value which should normally be set by a recv()
SocketBaseTagList(tags);
switch(err)
{
case EAGAIN:
{
// we are using non-blocking socket IO so an EAGAIN signals
// that we should wait for more data to arrive before we
// can issue a recv() again.
LONG retVal;
conn->timeout.tv_sec = C->SocketTimeout;
conn->timeout.tv_usec = 0;
// now we put our socket handle into a descriptor set
// we can pass on to WaitSelect()
FD_ZERO(&conn->fdset);
FD_SET(conn->socket, &conn->fdset);
retVal = WaitSelect(conn->socket+1, &conn->fdset, NULL, NULL, (APTR)&conn->timeout, NULL);
// if WaitSelect() returns 1 we successfully waited for
// being able to read from the socket. So we go and do another
// iteration in the while() loop as the next connect() call should
// return EISCONN if the connection really succeeded.
if(retVal >= 1 && FD_ISSET(conn->socket, &conn->fdset))
{
// everything fine
continue;
}
else if(retVal == 0)
{
W(DBF_NET, "WaitSelect() socket timeout reached");
status = -1; // signal error
conn->error = CONNECTERR_TIMEDOUT;
}
else
{
// the rest should signal an error
E(DBF_NET, "WaitSelect() returned error: %ld", err);
status = -1; // signal error
conn->error = CONNECTERR_UNKNOWN_ERROR;
}
}
break;
case EINTR:
{
// we received an interrupt signal so we issue the recv()
// command again.
continue;
}
break;
default:
{
E(DBF_NET, "recv() returned error %ld", err);
status = -1; // signal error
conn->error = CONNECTERR_UNKNOWN_ERROR;
}
break;
}
}
}
while(status == 0);
}
// check the status for an error
if(status == -1)
result = -1;
else
result = nread;
RETURN(result);
return result;
}
///
/// ReadFromHostBuffered
// a buffered implementation of read()/recv() which is somehow compatible
// to the I/O fgets() function. It will read out data from the socket in
// 4096 chunks and store them into a static buffer. This function SHOULD
// only be called by TCPReceiveLine() and TCPReceive().
//
// This implementation is a slightly adapted version of readline()/my_read()
// examples from (W.Richard Stevens - Unix Network Programming) - Page 80
// However, they were adapted to non-blocking IO whereever necessary
static int ReadFromHostBuffered(struct Connection *conn, char *ptr, const int maxlen)
{
int result = -1; // -1 = error
ENTER();
// now comes the 'real' socket read stuff where
// we obtain as much data as possible up to the maxlen amount.
// if the buffer is empty we fill it again from
// data we request from the socket. Here we make sure we
// take care of non-blocking IO
if(conn->receiveCount <= 0)
{
// read all data upto the maximum our buffer allows
conn->receiveCount = ReadFromHost(conn, conn->receiveBuffer, conn->receiveBufferSize);
// reset the read_ptr
conn->receivePtr = conn->receiveBuffer;
}
if(conn->receiveCount > 0)
{
// we copy only the minmum of read_cnt and maxlen
int fillCount = MIN(conn->receiveCount, maxlen);
// to speed up the ReadLine we only use memcpy()
// for requests > 1
if(fillCount > 1)
{
// now we copy a maximum of maxlen chars to
// the destination
memcpy(ptr, conn->receivePtr, fillCount);
conn->receiveCount -= fillCount;
conn->receivePtr += fillCount;
}
else
{
conn->receiveCount--;
*ptr = *conn->receivePtr++;
}
result = fillCount;
}
RETURN(result);
return result;
}
///
/// ReceiveLineFromHost
// a buffered version of readline() that reads out charwise from the
// socket via TCPReadBuffered and returns the amount of chars copied
// into the provided character array or -1 on error.
//
// This implementation is a slightly adapted version of readline()/my_read()
// examples from (W.Richard Stevens - Unix Network Programming) - Page 80
int ReceiveLineFromHost(struct Connection *conn, char *vptr, const int maxlen)
{
int result = -1;
ENTER();
if(conn != NULL)
{
// make sure the socket is active.
if(conn->isConnected == TRUE)
{
int n;
char *ptr = vptr;
conn->error = CONNECTERR_NO_ERROR;
for(n = 1; n < maxlen; n++)
{
int rc;
char c;
// read out one buffered char only.
rc = ReadFromHostBuffered(conn, &c, 1);
if(rc == 1)
{
*ptr++ = c;
if(c == '\n')
break; // newline is stored, like with getline()
}
else if(rc == 0)
{
// if n==1 then EOF, no data read
// otherwise EOF, some data was read
if(n == 1)
n = 0;
break;
}
else
{
// error, errno set by readchar()
n = -1;
break;
}
}
*ptr = '\0'; // null terminate like getline()
// perform some debug output on the console if requested
// by the user
if(G->NetLog == TRUE)
{
fprintf(stderr, "SERVER['%s', %04d]: %s", conn->server->description, n, vptr);
// add a linefeed in case of an error
if(n <= 0 || strlen(vptr) == 0)
fprintf(stderr, "\n");
}
//D(DBF_NET, "TCP: received %ld of max %ld bytes", n, maxlen);
// return the number of chars we read
result = n;
}
else
{
conn->error = CONNECTERR_NOT_CONNECTED;
}
}
RETURN(result);
return result;
}
///
/// ReceiveFromHost
// a own wrapper function for recv()/SSL_read() that reads buffered somehow
// it reads maxlen-1 data out of the buffer and stores it into recvdata with
// a null terminated string
int ReceiveFromHost(struct Connection *conn, char *recvdata, const int maxlen)
{
int nread = -1;
ENTER();
if(conn != NULL)
{
// make sure the socket is active.
if(conn->isConnected == TRUE)
{
conn->error = CONNECTERR_NO_ERROR;
// we call the ReadBuffered function so that
// we get out the data from our own buffer.
nread = ReadFromHostBuffered(conn, recvdata, maxlen-1);
if(nread <= 0)
recvdata[0] = '\0';
else
recvdata[nread] = '\0';
if(G->NetLog == TRUE)
{
fprintf(stderr, "SERVER['%s', %04d]: %s", conn->server->description, nread, recvdata);
// add a linefeed in case of an error
if(nread <= 0 || strlen(recvdata) == 0)
fprintf(stderr, "\n");
}
//D(DBF_NET, "TCP: received %ld of max %ld bytes", nread, maxlen);
}
else
{
W(DBF_NET, "socket not connected");
conn->error = CONNECTERR_NOT_CONNECTED;
}
}
RETURN(nread);
return nread;
}
///
/// WriteToHost
// an unbuffered implementation/wrapper for SSL_write/send() where the supplied
// data in ptr will be send out straight away while taking care of non-blocking IO.
// returns the number of bytes written or -1 on an error
static int WriteToHost(struct Connection *conn, const char *ptr, const int len)
{
int result;
int towrite = len;
int status = 0; // < 0 error, 0 unknown, > 0 no error
GET_SOCKETBASE(conn);
ENTER();
if(conn->ssl != NULL)
{
// use SSL methods to get/process all data
do
{
// write data to our socket and then check how much
// we have written and in case we weren't able to write
// all out, we retry it with smaller chunks.
int nwritten = SSL_write(conn->ssl, ptr, towrite);
// if nwritten > 0 we transfered _some_ data
if(nwritten > 0)
{
towrite -= nwritten;
ptr += nwritten;
// check if we are finished with sending all data
// otherwise our next iteration wiill continue
// sending the rest.
if(towrite <= 0)
break;
}
else // if an error occurred we process it
{
int err = SSL_get_error(conn->ssl, nwritten);
switch(err)
{
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
{
// we are using non-blocking socket IO so an SSL_ERROR_WANT_READ
// signals that the SSL socket wants us to wait until data
// is available and reissue the SSL_read() command.
LONG retVal;
conn->timeout.tv_sec = C->SocketTimeout;
conn->timeout.tv_usec = 0;
// now we put our socket handle into a descriptor set
// we can pass on to WaitSelect()
FD_ZERO(&conn->fdset);
FD_SET(conn->socket, &conn->fdset);
// depending on the SSL error (WANT_READ/WANT_WRITE)
// we either do a WaitSelect() on the read or write mode
// as with SSL both things can happen
// see http://www.openssl.org/docs/ssl/SSL_write.html
if(err == SSL_ERROR_WANT_READ)
retVal = WaitSelect(conn->socket+1, &conn->fdset, NULL, NULL, (APTR)&conn->timeout, NULL);
else
retVal = WaitSelect(conn->socket+1, NULL, &conn->fdset, NULL, (APTR)&conn->timeout, NULL);
// if WaitSelect() returns 1 we successfully waited for
// being able to write to the socket. So we go and do another
// iteration in the while() loop as the next connect() call should
// return EISCONN if the connection really succeeded.
if(retVal >= 1 && FD_ISSET(conn->socket, &conn->fdset))
{
// everything fine
continue;
}
else if(retVal == 0)
{
W(DBF_NET, "WaitSelect() socket timeout reached");
status = -1; // signal error
}
else
{
// the rest should signal an error
E(DBF_NET, "WaitSelect() returned error %ld", err);
status = -1; // signal error
}
}
break;
case SSL_ERROR_ZERO_RETURN:
{
// in case nwritten is zero the connection
// was shut down cleanly...
if(nwritten == 0)
{
// signal no error by +1
status = 1;
}
else
{
E(DBF_NET, "SSL_write() returned SSL_ERROR_ZERO_RETURN");
status = -1; // signal error
}
}
break;
default:
{
E(DBF_NET, "SSL_write() returned error %ld", err);
status = -1; // signal error
}
break;
}
}
}
while(status == 0);
}
else
{
// use normal socket methods to query/process data
do
{
// write data to our socket and then check how much
// we have written and in case we weren't able to write
// all out, we retry it with smaller chunks.
int nwritten = send(conn->socket, (APTR)ptr, towrite, 0);
// if nwritten > 0 we transfered _some_ data
if(nwritten > 0)
{
towrite -= nwritten;
ptr += nwritten;
// check if we are finished with sending all data
// otherwise our next iteration wiill continue
// sending the rest.
if(towrite <= 0)
break;
}
else // <= 0 if an error occurred we process it
{
LONG err = -1;
struct TagItem tags[] =
{
{ SBTM_GETREF(SBTC_ERRNO), (IPTR)&err },
{ TAG_END, 0 }
};
// get the error value which should normally be set by a send()
SocketBaseTagList(tags);
switch(err)
{
case EAGAIN:
{
// we are using non-blocking socket IO so an EAGAIN signals
// that we should wait for more data to arrive before we
// can issue a recv() again.
LONG retVal;
conn->timeout.tv_sec = C->SocketTimeout;
conn->timeout.tv_usec = 0;
// now we put our socket handle into a descriptor set
// we can pass on to WaitSelect()
FD_ZERO(&conn->fdset);
FD_SET(conn->socket, &conn->fdset);
retVal = WaitSelect(conn->socket+1, NULL, &conn->fdset, NULL, (APTR)&conn->timeout, NULL);
// if WaitSelect() returns 1 we successfully waited for
// being able to write to the socket. So we go and do another
// iteration in the while() loop as the next connect() call should
// return EISCONN if the connection really succeeded.
if(retVal >= 1 && FD_ISSET(conn->socket, &conn->fdset))
{
// everything fine
continue;
}
else if(retVal == 0)
{
W(DBF_NET, "WaitSelect() socket timeout reached");
status = -1; // signal error
}
else
{
// the rest should signal an error
E(DBF_NET, "WaitSelect() returned error %ld", err);
status = -1; // signal error
}
}
break;
case EINTR:
{
// we received an interrupt signal so we issue the send()
// command again.
continue;
}
break;
default:
{
E(DBF_NET, "send() returned error %ld", err);
status = -1; // signal error
}
break;
}
}
}
while(status == 0);
}
#if defined(DEBUG)
if(towrite != 0)
E(DBF_NET, "TCPWrite() short item count: %ld of %ld transfered!", len-towrite, len);
#endif
// check the status
if(status == -1)
result = -1;
else
result = len-towrite;
RETURN(result);
return result;
}
///
/// WriteToHostBuffered
// a buffered implementation of SSL_write()/send(). This function will buffer
// all write operations in a temporary buffer and as soon as the buffer is
// full or it will be explicitly flushed, it will write out the data to the
// socket. This function SHOULD only be called by TCPSend() somehow.
//
// This function will also optimize the write operations by always making
// sure that a full buffer will be written out to the socket. i.e. if the
// buffer is filled up so that the next call would flush it, we copy as
// many data to the buffer as possible and flush it immediately.
static int WriteToHostBuffered(struct Connection *conn, const char *ptr, int maxlen, const int flags)
{
int result = -1; // -1 = error
int fillCount = 0;
BOOL abortProc = FALSE;
ENTER();
// in case our write buffer is already filled up or
// the caller wants to just flush the buffer we immediately use
// the socket functions send/SSL_write() to transfer everything
// and therefore clear the buffer.
if(conn->sendCount >= conn->sendBufferSize || hasTCP_ONLYFLUSH(flags))
{
// make sure we write out the data to the socket
if(WriteToHost(conn, conn->sendBuffer, conn->sendCount) != conn->sendCount)
{
// abort and signal an error
abortProc = TRUE;
}
else
{
// set the ptr to the start of the buffer
conn->sendPtr = conn->sendBuffer;
conn->sendCount = 0;
// if this write operation was just because of a ONLYFLUSH
// flag we can abort immediately, but make sure we don't
// return an error but a count of zero
if(hasTCP_ONLYFLUSH(flags))
{
abortProc = TRUE;
result = 0;
}
}
}
// check if we should continue
if(abortProc == FALSE)
{
// if the string we want to copy into the buffer
// wouldn't fit, we copy as much as we can, clear the buffer
// and continue until there is enough space left
while(conn->sendCount+maxlen > conn->sendBufferSize)
{
int fillable = conn->sendBufferSize - conn->sendCount;
// after the copy we have to increase the pointer of the
// array we want to copy, because in the next cycle we have to
// copy the rest out of it.
memcpy(conn->sendPtr, ptr, fillable);
ptr += fillable;
conn->sendCount += fillable;
fillCount += fillable;
// decrease maxlen now by the amount of bytes we have written
// to the buffer
maxlen -= fillable;
// now we write out the data
if(WriteToHost(conn, conn->sendBuffer, conn->sendCount) != conn->sendCount)
{
// abort and signal an error
abortProc = TRUE;
break;
}
else
{
// set the ptr to the start of the buffer
// as we flushed everything
conn->sendPtr = conn->sendBuffer;
conn->sendCount = 0;
}
}
// check if we should abort
if(abortProc == FALSE)
{
// if we end up here we have enough space for our
// string in the buffer, so lets copy it in
memcpy(conn->sendPtr, ptr, maxlen);
conn->sendPtr += maxlen;
conn->sendCount += maxlen;
fillCount += maxlen;
// if the user has supplied the FLUSH flag we have to clear/flush
// the buffer immediately after having copied everything
if(hasTCP_FLUSH(flags))
{
// write our whole buffer out to the socket
if(WriteToHost(conn, conn->sendBuffer, conn->sendCount) != conn->sendCount)
{
// abort and signal an error
abortProc = TRUE;
}
else
{
// set the ptr to the start of the buffer
// as we flushed everything
conn->sendPtr = conn->sendBuffer;
conn->sendCount = 0;
}
}
// if we still haven't received an abort signal
// we can finally set the result to fillCount
if(abortProc == FALSE)
result = fillCount;
}
}
RETURN(result);
return result;
}
///
/// SendToHost
// a own wrapper function for send()/SSL_write() that writes buffered somehow
// if called with flag != TCP_FLUSH - otherwise it will write and flush immediately
int SendToHost(struct Connection *conn, const char *ptr, const int len, const int flags)
{
int nwritten = -1;
ENTER();
if(conn != NULL)
{
// make sure the socket is active.
if(conn->isConnected == TRUE)
{
conn->error = CONNECTERR_NO_ERROR;
// perform some debug output on the console if requested
// by the user
if(G->NetLog == TRUE && ptr != NULL)
fprintf(stderr, "CLIENT['%s', %04d]: %s", conn->server->description, len, ptr);
// we call the WriteBuffered() function to write this characters
// out to the socket. the provided flag will define if it
// really will be buffered or if we write and flush the buffer
// immediatly
nwritten = WriteToHostBuffered(conn, ptr, len, flags);
D(DBF_NET, "TCP: sent %ld of %ld bytes (%ld)", nwritten, len, flags);
}
else
{
W(DBF_NET, "socket not connected");
conn->error = CONNECTERR_NOT_CONNECTED;
}
}
RETURN(nwritten);
return nwritten;
}
///
/// SendLineToHost
// send out a line of text
int SendLineToHost(struct Connection *conn, const char *vptr)
{
int result;
ENTER();
result = SendToHost(conn, vptr, strlen(vptr), TCPF_FLUSH);
RETURN(result);
return result;
}
///
/// FlushConnection
// send out any still pending data
int FlushConnection(struct Connection *conn)
{
int result;
ENTER();
result = SendToHost(conn, NULL, 0, TCPF_FLUSHONLY);
RETURN(result);
return result;
}
///