# define INCLUDE_FILE_IO
# include "dgd.h"
# define FD_SETSIZE 1024
# include <winsock.h>
# include <process.h>
# include "str.h"
# include "array.h"
# include "object.h"
# include "comm.h"
# define MAXHOSTNAMELEN 256
# define NFREE 32
typedef struct _ipaddr_ {
struct _ipaddr_ *link; /* next in hash table */
struct _ipaddr_ *prev; /* previous in linked list */
struct _ipaddr_ *next; /* next in linked list */
Uint ref; /* reference count */
struct in_addr ipnum; /* ip number */
char name[MAXHOSTNAMELEN]; /* ip name */
} ipaddr;
static SOCKET in, out; /* connection from/to name resolver */
static ipaddr **ipahtab; /* ip address hash table */
static unsigned int ipahtabsz; /* hash table size */
static ipaddr *qhead, *qtail; /* request queue */
static ipaddr *ffirst, *flast; /* free list */
static int nfree; /* # in free list */
static ipaddr *lastreq; /* last request */
static bool busy; /* name resolver busy */
/*
* NAME: ipaddr->run()
* DESCRIPTION: host name lookup sub-program
*/
static void ipa_run(void *dummy)
{
char buf[sizeof(struct in_addr)];
struct hostent *host;
int len;
for (;;) {
/* read request */
if (recv(out, buf, sizeof(struct in_addr), 0) <= 0) {
return; /* connection closed */
}
/* lookup host */
host = gethostbyaddr(buf, sizeof(struct in_addr), PF_INET);
if (host == (struct hostent *) NULL) {
Sleep(5000);
host = gethostbyaddr(buf, sizeof(struct in_addr), PF_INET);
}
if (host != (struct hostent *) NULL) {
/* write host name */
len = strlen(host->h_name);
if (len >= MAXHOSTNAMELEN) {
len = MAXHOSTNAMELEN - 1;
}
send(out, host->h_name, len, 0);
} else {
send(out, "", 1, 0); /* failure */
}
}
}
/*
* NAME: ipaddr->init()
* DESCRIPTION: initialize name lookup
*/
static bool ipa_init(int maxusers)
{
in = socket(PF_INET, SOCK_STREAM, 0);
if (in == INVALID_SOCKET) {
return FALSE;
}
ipahtab = ALLOC(ipaddr*, ipahtabsz = maxusers);
memset(ipahtab, '\0', ipahtabsz * sizeof(ipaddr*));
qhead = qtail = ffirst = flast = lastreq = (ipaddr *) NULL;
nfree = 0;
busy = FALSE;
return TRUE;
}
/*
* NAME: ipadd->start()
* DESCRIPTION: start resolver thread
*/
static void ipa_start(SOCKET sock)
{
struct sockaddr_in addr;
int len;
len = sizeof(struct sockaddr_in);
getsockname(sock, (struct sockaddr *) &addr, &len);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
connect(in, (struct sockaddr *) &addr, len);
out = accept(sock, (struct sockaddr *) &addr, &len);
_beginthread(ipa_run, 0, NULL);
}
/*
* NAME: ipaddr->finish()
* DESCRIPTION: stop name lookup
*/
static void ipa_finish(void)
{
closesocket(in);
}
/*
* NAME: ipaddr->new()
* DESCRIPTION: return a new ipaddr
*/
static ipaddr *ipa_new(struct in_addr *ipnum)
{
ipaddr *ipa, **hash;
/* check hash table */
hash = &ipahtab[(Uint) ipnum->s_addr % ipahtabsz];
while (*hash != (ipaddr *) NULL) {
ipa = *hash;
if (ipnum->s_addr == ipa->ipnum.s_addr) {
/*
* found it
*/
if (ipa->ref == 0) {
/* remove from free list */
if (ipa->prev == (ipaddr *) NULL) {
ffirst = ipa->next;
} else {
ipa->prev->next = ipa->next;
}
if (ipa->next == (ipaddr *) NULL) {
flast = ipa->prev;
} else {
ipa->next->prev = ipa->prev;
}
ipa->prev = ipa->next = (ipaddr *) NULL;
--nfree;
}
ipa->ref++;
return ipa;
}
hash = &ipa->link;
}
if (nfree >= NFREE && ffirst != (ipaddr *) NULL) {
ipaddr **h;
/*
* use first ipaddr in free list
*/
ipa = ffirst;
ffirst = ipa->next;
if (ffirst == (ipaddr *) NULL) {
flast = (ipaddr *) NULL;
}
--nfree;
/* remove from hash table */
for (h = &ipahtab[(Uint) ipa->ipnum.s_addr % ipahtabsz];
*h != ipa;
h = &(*h)->link) ;
*h = ipa->next;
if (ipa == lastreq) {
lastreq = (ipaddr *) NULL;
}
} else {
/*
* allocate new ipaddr
*/
m_static();
ipa = ALLOC(ipaddr, 1);
m_dynamic();
}
/* put in hash table */
ipa->link = *hash;
*hash = ipa;
ipa->ref++;
ipa->ipnum = *ipnum;
ipa->name[0] = '\0';
if (!busy) {
/* send query to name resolver */
send(in, (char *) ipnum, sizeof(struct in_addr), 0);
ipa->prev = ipa->next = (ipaddr *) NULL;
lastreq = ipa;
busy = TRUE;
} else {
/* put in request queue */
ipa->prev = qtail;
if (qtail == (ipaddr *) NULL) {
qhead = ipa;
}
qtail = ipa;
ipa->next = (ipaddr *) NULL;
}
return ipa;
}
/*
* NAME: ipaddr->del()
* DESCRIPTION: delete an ipaddr
*/
static void ipa_del(ipaddr *ipa)
{
if (--ipa->ref == 0) {
if (ipa->prev != (ipaddr *) NULL || qhead == ipa) {
/* remove from queue */
if (ipa->prev != (ipaddr *) NULL) {
ipa->prev->next = ipa->next;
} else {
qhead = ipa->next;
}
if (ipa->next != (ipaddr *) NULL) {
ipa->next->prev = ipa->prev;
} else {
qtail = ipa->prev;
}
}
/* add to free list */
if (flast != (ipaddr *) NULL) {
flast->next = ipa;
ipa->prev = flast;
} else {
ffirst = flast = ipa;
ipa->prev = (ipaddr *) NULL;
}
ipa->next = (ipaddr *) NULL;
nfree++;
}
}
/*
* NAME: ipaddr->lookup()
* DESCRIPTION: lookup another ip name
*/
static void ipa_lookup(void)
{
ipaddr *ipa;
if (lastreq != (ipaddr *) NULL) {
/* read ip name */
lastreq->name[recv(in, lastreq->name, MAXHOSTNAMELEN, 0)] = '\0';
qhead = lastreq->next;
if (qhead == (ipaddr *) NULL) {
qtail = (ipaddr *) NULL;
}
} else {
char buf[MAXHOSTNAMELEN];
/* discard ip name */
recv(in, buf, MAXHOSTNAMELEN, 0);
}
/* if request queue not empty, write new query */
if (qhead != (ipaddr *) NULL) {
ipa = qhead;
send(in, (char *) &ipa->ipnum, sizeof(struct in_addr), 0);
qhead = ipa->next;
if (qhead == (ipaddr *) NULL) {
qtail = (ipaddr *) NULL;
}
ipa->prev = ipa->next = (ipaddr *) NULL;
lastreq = ipa;
busy = TRUE;
} else {
lastreq = (ipaddr *) NULL;
busy = FALSE;
}
}
struct _connection_ {
SOCKET fd; /* file descriptor */
int bufsz; /* # bytes in buffer */
char *udpbuf; /* datagram buffer */
ipaddr *addr; /* internet address of connection */
unsigned short port; /* port of connection */
struct _connection_ *next; /* next in list */
};
static int nusers; /* # of users */
static connection *connections; /* connections array */
static connection *flist; /* list of free connections */
static connection **udphtab; /* UDP hash table */
static int udphtabsz; /* UDP hash table size */
static SOCKET telnet; /* telnet port socket descriptor */
static SOCKET binary; /* binary port socket descriptor */
static SOCKET udp; /* UDP port socket descriptor */
static fd_set infds; /* file descriptor input bitmap */
static fd_set outfds; /* file descriptor output bitmap */
static fd_set waitfds; /* file descriptor wait-write bitmap */
static fd_set readfds; /* file descriptor read bitmap */
static fd_set writefds; /* file descriptor write map */
/*
* NAME: conn->init()
* DESCRIPTION: initialize connection handling
*/
bool conn_init(int maxusers, unsigned int telnet_port, unsigned int binary_port)
{
WSADATA wsadata;
struct sockaddr_in sin;
struct hostent *host;
int n;
connection *conn;
char buffer[MAXHOSTNAMELEN];
int on;
/* initialize winsock */
if (WSAStartup(MAKEWORD(1, 1), &wsadata) != 0) {
P_message("WSAStartup failed (no winsock?)\n");
return FALSE;
}
if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
WSACleanup();
P_message("Winsock 1.1 not supported\n");
return FALSE;
}
gethostname(buffer, MAXHOSTNAMELEN);
host = gethostbyname(buffer);
if (host == (struct hostent *) NULL) {
P_message("gethostbyname() failed\n");
return FALSE;
}
telnet = socket(PF_INET, SOCK_STREAM, 0);
binary = socket(PF_INET, SOCK_STREAM, 0);
udp = socket(PF_INET, SOCK_DGRAM, 0);
if (telnet == INVALID_SOCKET || binary == INVALID_SOCKET ||
udp == INVALID_SOCKET) {
P_message("socket() failed\n");
return FALSE;
}
on = 1;
if (setsockopt(telnet, SOL_SOCKET, SO_REUSEADDR, (char *) &on,
sizeof(on)) != 0) {
P_message("setsockopt() failed\n");
return FALSE;
}
on = 1;
if (setsockopt(telnet, SOL_SOCKET, SO_OOBINLINE, (char *) &on,
sizeof(on)) != 0) {
P_message("setsockopt() failed\n");
return FALSE;
}
on = 1;
if (setsockopt(binary, SOL_SOCKET, SO_REUSEADDR, (char *) &on,
sizeof(on)) != 0) {
P_message("setsockopt() failed\n");
return FALSE;
}
on = 1;
if (setsockopt(binary, SOL_SOCKET, SO_OOBINLINE, (char *) &on,
sizeof(on)) != 0) {
P_message("setsockopt() failed\n");
return FALSE;
}
on = 1;
if (setsockopt(udp, SOL_SOCKET, SO_REUSEADDR, (char *) &on,
sizeof(on)) != 0) {
P_message("setsockopt() failed\n");
return FALSE;
}
memset(&sin, '\0', sizeof(sin));
memcpy(&sin.sin_addr, host->h_addr, host->h_length);
sin.sin_port = htons((u_short) telnet_port);
sin.sin_family = host->h_addrtype;
sin.sin_addr.s_addr = INADDR_ANY;
if (bind(telnet, (struct sockaddr *) &sin, sizeof(sin)) != 0) {
P_message("telnet bind failed\n");
return FALSE;
}
sin.sin_port = htons((u_short) binary_port);
if (bind(binary, (struct sockaddr *) &sin, sizeof(sin)) != 0) {
P_message("binary bind failed\n");
return FALSE;
}
if (bind(udp, (struct sockaddr *) &sin, sizeof(sin)) != 0) {
P_message("udp bind failed\n");
return FALSE;
}
if (!ipa_init(maxusers)) {
return FALSE;
}
flist = (connection *) NULL;
connections = ALLOC(connection, nusers = maxusers);
for (n = nusers, conn = connections; n > 0; --n, conn++) {
conn->fd = INVALID_SOCKET;
conn->next = flist;
flist = conn;
}
udphtab = ALLOC(connection*, udphtabsz = maxusers);
memset(udphtab, '\0', udphtabsz * sizeof(connection*));
FD_ZERO(&infds);
FD_ZERO(&outfds);
FD_ZERO(&waitfds);
FD_SET(telnet, &infds);
FD_SET(binary, &infds);
FD_SET(udp, &infds);
FD_SET(in, &infds);
return TRUE;
}
/*
* NAME: conn->finish()
* DESCRIPTION: terminate connections
*/
void conn_finish(void)
{
ipa_finish();
WSACleanup();
}
/*
* NAME: conn->listen()
* DESCRIPTION: start listening on telnet port and binary port
*/
void conn_listen(void)
{
unsigned long nonblock;
if (listen(telnet, 64) != 0 || listen(binary, 64) != 0) {
fatal("listen() failed");
}
ipa_start(binary);
nonblock = TRUE;
if (ioctlsocket(telnet, FIONBIO, &nonblock) != 0) {
fatal("fcntl() failed");
}
nonblock = TRUE;
if (ioctlsocket(binary, FIONBIO, &nonblock) != 0) {
fatal("fcntl() failed");
}
nonblock = TRUE;
if (ioctlsocket(udp, FIONBIO, &nonblock) != 0) {
fatal("fcntl() failed");
}
}
/*
* NAME: conn->tnew()
* DESCRIPTION: accept a new telnet connection
*/
connection *conn_tnew(void)
{
SOCKET fd;
int len;
struct sockaddr_in sin;
connection *conn;
if (!FD_ISSET(telnet, &readfds)) {
return (connection *) NULL;
}
len = sizeof(sin);
fd = accept(telnet, (struct sockaddr *) &sin, &len);
if (fd == INVALID_SOCKET) {
return (connection *) NULL;
}
conn = flist;
flist = conn->next;
conn->fd = fd;
conn->udpbuf = (char *) NULL;
conn->addr = ipa_new(&sin.sin_addr);
conn->port = sin.sin_port;
FD_SET(fd, &infds);
FD_SET(fd, &outfds);
FD_CLR(fd, &readfds);
FD_SET(fd, &writefds);
return conn;
}
/*
* NAME: conn->bnew()
* DESCRIPTION: accept a new binary connection
*/
connection *conn_bnew(void)
{
SOCKET fd;
int len;
struct sockaddr_in sin;
connection *conn;
if (!FD_ISSET(binary, &readfds)) {
return (connection *) NULL;
}
len = sizeof(sin);
fd = accept(binary, (struct sockaddr *) &sin, &len);
if (fd == INVALID_SOCKET) {
return (connection *) NULL;
}
conn = flist;
flist = conn->next;
conn->fd = fd;
conn->udpbuf = (char *) NULL;
conn->addr = ipa_new(&sin.sin_addr);
conn->port = sin.sin_port;
FD_SET(fd, &infds);
FD_SET(fd, &outfds);
FD_CLR(fd, &readfds);
FD_SET(fd, &writefds);
return conn;
}
/*
* NAME: conn->udp()
* DESCRIPTION: enable the UDP channel of a binary connection
*/
void conn_udp(connection *conn)
{
connection **hash;
m_static();
conn->udpbuf = ALLOC(char, BINBUF_SIZE);
m_dynamic();
conn->bufsz = -1;
hash = &udphtab[((Uint) conn->addr->ipnum.s_addr ^ conn->port) % udphtabsz];
conn->next = *hash;
*hash = conn;
}
/*
* NAME: conn->del()
* DESCRIPTION: delete a connection
*/
void conn_del(connection *conn)
{
connection **hash;
if (conn->fd != INVALID_SOCKET) {
closesocket(conn->fd);
FD_CLR(conn->fd, &infds);
FD_CLR(conn->fd, &outfds);
FD_CLR(conn->fd, &waitfds);
conn->fd = INVALID_SOCKET;
}
if (conn->udpbuf != (char *) NULL) {
FREE(conn->udpbuf);
for (hash = &udphtab[((Uint) conn->addr->ipnum.s_addr ^ conn->port) %
udphtabsz];
*hash != conn;
hash = &(*hash)->next) ;
*hash = conn->next;
}
ipa_del(conn->addr);
conn->next = flist;
flist = conn;
}
/*
* NAME: conn->block()
* DESCRIPTION: block or unblock input from connection
*/
void conn_block(conn, flag)
register connection *conn;
int flag;
{
if (conn->fd != INVALID_SOCKET) {
if (flag) {
FD_CLR(conn->fd, &infds);
FD_CLR(conn->fd, &readfds);
} else {
FD_SET(conn->fd, &infds);
}
}
}
/*
* NAME: conn->select()
* DESCRIPTION: wait for input from connections
*/
int conn_select(int wait)
{
struct timeval timeout;
int retval;
/*
* First, check readability and writability for binary sockets with pending
* data only.
*/
memcpy(&readfds, &infds, sizeof(fd_set));
memcpy(&writefds, &waitfds, sizeof(fd_set));
timeout.tv_sec = (int) wait;
timeout.tv_usec = 0;
retval = select(0, &readfds, &writefds, (fd_set *) NULL, &timeout);
/*
* Now check writability for all sockets in a polling call.
*/
memcpy(&writefds, &outfds, sizeof(fd_set));
timeout.tv_sec = 0;
timeout.tv_usec = 0;
select(0, (fd_set *) NULL, &writefds, (fd_set *) NULL, &timeout);
/* check for UDP packet */
if (FD_ISSET(udp, &readfds)) {
char buffer[BINBUF_SIZE];
struct sockaddr_in from;
int fromlen, size;
register connection **hash;
fromlen = sizeof(struct sockaddr_in);
size = recvfrom(udp, buffer, BINBUF_SIZE, 0, (struct sockaddr *) &from,
&fromlen);
if (size >= 0) {
hash = &udphtab[((Uint) from.sin_addr.s_addr ^ from.sin_port) %
udphtabsz];
while (*hash != (connection *) NULL) {
if ((*hash)->addr->ipnum.s_addr == from.sin_addr.s_addr &&
(*hash)->port == from.sin_port) {
/*
* copy to connection's buffer
*/
memcpy((*hash)->udpbuf, buffer, (*hash)->bufsz = size);
break;
}
hash = &(*hash)->next;
}
/* else from unknown source: ignore */
}
}
/* handle ip name lookup */
if (FD_ISSET(in, &readfds)) {
ipa_lookup();
}
return retval;
}
/*
* NAME: conn->read()
* DESCRIPTION: read from a connection
*/
int conn_read(connection *conn, char *buf, unsigned int len)
{
int size;
if (conn->fd == INVALID_SOCKET) {
return -1;
}
if (!FD_ISSET(conn->fd, &readfds)) {
return 0;
}
size = recv(conn->fd, buf, len, 0);
return (size == 0) ? -1 : size;
}
/*
* NAME: conn->udpread()
* DESCRIPTION: read a message from a UDP channel
*/
int conn_udpread(connection *conn, char *buf, unsigned int len)
{
if (conn->bufsz >= 0) {
/* udp buffer is not empty */
if (conn->bufsz <= len) {
memcpy(buf, conn->udpbuf, len = conn->bufsz);
} else {
len = 0;
}
conn->bufsz = -1;
return len;
}
return -1;
}
/*
* NAME: conn->write()
* DESCRIPTION: write to a connection; return the amount of bytes written
*/
int conn_write(connection *conn, char *buf, unsigned int len)
{
int size;
if (conn->fd != INVALID_SOCKET) {
FD_CLR(conn->fd, &waitfds);
if (len == 0) {
return 0; /* send_message("") can be used to flush buffer */
}
if (!FD_ISSET(conn->fd, &writefds)) {
/* the write would fail */
FD_SET(conn->fd, &waitfds);
return -1;
}
if ((size=send(conn->fd, buf, len, 0)) == SOCKET_ERROR &&
WSAGetLastError() != WSAEWOULDBLOCK) {
closesocket(conn->fd);
FD_CLR(conn->fd, &infds);
FD_CLR(conn->fd, &outfds);
conn->fd = INVALID_SOCKET;
} else if ((unsigned int) size != len) {
/* waiting for wrdone */
FD_SET(conn->fd, &waitfds);
FD_CLR(conn->fd, &writefds);
}
return size;
}
return 0;
}
/*
* NAME: conn->udpwrite()
* DESCRIPTION: write a message to a UDP channel
*/
int conn_udpwrite(connection *conn, char *buf, unsigned int len)
{
struct sockaddr_in to;
if (conn->fd >= 0) {
to.sin_family = AF_INET;
to.sin_addr.s_addr = conn->addr->ipnum.s_addr;
to.sin_port = conn->port;
return sendto(udp, buf, len, 0, (struct sockaddr *) &to,
sizeof(struct sockaddr_in));
}
return 0;
}
/*
* NAME: conn->wrdone()
* DESCRIPTION: return TRUE if a connection is ready for output
*/
bool conn_wrdone(connection *conn)
{
if (conn->fd == INVALID_SOCKET || !FD_ISSET(conn->fd, &waitfds)) {
return TRUE;
}
if (FD_ISSET(conn->fd, &writefds)) {
FD_CLR(conn->fd, &waitfds);
return TRUE;
}
return FALSE;
}
/*
* NAME: conn->ipnum()
* DESCRIPTION: return the ip number of a connection
*/
char *conn_ipnum(connection *conn)
{
return inet_ntoa(conn->addr->ipnum);
}
/*
* NAME: conn->ipname()
* DESCRIPTION: return the ip name of a connection
*/
char *conn_ipname(connection *conn)
{
return (conn->addr->name[0] != '\0') ?
conn->addr->name : inet_ntoa(conn->addr->ipnum);
}