psyced/config/gentoo/ldmud/files/erq/erq.c

2613 lines
80 KiB
C

/*---------------------------------------------------------------------------
* External Request Daemon
*
*---------------------------------------------------------------------------
* This is the standard implementation of the forked ERQ daemon. The
* implementation is straightforward and follows what is documented in
* doc/util/erq.
*
* The erq can handle up to MAX_CHILDS (yes, it should be "children")
* concurrent tasks. Simple non-blocking tasks (all the TCP/UDP functions)
* are handled by the master ERQ, the other tasks are loaded off to
* subservers - forked copies of the ERQ. If the driver requests to
* start a program, one of the subservers is selected and forks again
* to run the program; the communication chain is then driver <-> ERQ
* <-> subserver <-> program. If a subserver is finished with its
* task, it is allowed to stay around for a certain idle time CHILD_EXPIRE,
* reducing the number of costly fork() operations when several requests
* follow after each other.
*
* This ERQ implementation uses two type of tickets.
* For programs:
* { int32 child_number }
* For sockets:
* { int32 child_number; int32 rnd; int32 seq; }
*
* TODO: This program could be commented MUCH better, but in comparison
* TODO:: to its original state it's already a big improvement.
* TODO:: And it surely still contains bugs.
*---------------------------------------------------------------------------
*/
#include <sys/types.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <stdio.h>
#include <ctype.h>
#include <time.h>
#include <signal.h>
#include <sys/times.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "machine.h"
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#else
# include <strings.h>
#endif
#ifdef HAVE_BSTRING_H
# include <bstring.h>
#endif
#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif
#ifdef HAVE_SYS_TERMIOS_H
# include <sys/termios.h>
#endif
#ifdef _AIX
# include <sys/select.h>
#endif
#ifdef WEXITSTATUS
# define wait_status_t int
#else
# define wait_status_t union wait
# define WEXITSTATUS(status) ((status).w_retcode)
# define WTERMSIG(status) ((status).w_termsig)
#endif
#ifdef sun
time_t time(time_t *);
#endif
#ifndef SIGCLD
# define SIGCLD SIGCHLD
#endif
#ifndef SIGKILL
# define SIGKILL 9
#endif
#ifndef SIGTERM
# define SIGTERM 15
#endif
#ifdef _AIX
typedef unsigned long length_t; /* *sigh* */
#else
typedef int length_t;
#endif
#define AUTH_PORT 113 /* according to RFC 931 */
#include "config.h"
#ifdef USE_IPV6
# define __IPV6__
#endif
#include "erq.h"
#include "srv.c"
#define randomize_tickets(n) srandom(n)
#define get_ticket() random()
#ifndef ERQ_DEBUG
# define ERQ_DEBUG 0
#endif
/*-------------------------------------------------------------------------*/
/* #define DETACH */
/* Define this for automatic detach from console.
*/
#define MAX_CHILDS 36
/* Maximum number of children.
*/
#define TIME_TO_CHECK_CHILDS 900
/* Delaytime for select() if no TCP children have data to send to
* the driver.
*/
#define CHILD_EXPIRE 900
/* Maximum idle time before a child is killed.
*/
#define ERQ_BUFSIZE 1024
/* Size of the data buffer.
*/
#define MAX_REPLY ERQ_MAX_REPLY
#if MAX_REPLY >= ERQ_BUFSIZE
# undef MAX_REPLY
# define MAX_REPLY (ERQ_BUFSIZE - 1)
#endif
/*-------------------------------------------------------------------------*/
typedef struct child_s child_t;
/* --- ticket: one ticket for a TCP connection ---
*
* The actual ticket is composed of the .rnd and .seq
* field - the union with c[] helps in memcpy()s.
*/
union ticket_u
{
struct ticket_s
{
long rnd, seq;
} s;
char c[1];
};
/* --- struct child_s: one child ---
*
* This structure is used to describe one child.
*/
struct child_s
{
int socket; /* Socket this child holds */
union /* Child specific information */
{
struct /* Subserver child */
{
pid_t pid; /* pid of the subserver */
time_t last_used; /* time of the last use */
} c;
struct /* Communication child */
{
char handle[4];
/* The handle assigned by the driver */
union ticket_u ticket;
time_t last_recv;
/* Time of the last received transmission */
int bytes_recv;
/* Number of bytes left to send to the driver */
} s;
} u;
int state;
# define CHILD_FREE 0 /* Idle subserver */
# define CHILD_LISTEN 1
# define CHILD_BUSY 2 /* Active subserver */
# define CHILD_UDP 3 /* UDP socket child */
# define CHILD_TCP 4 /* TCP socket child */
# define CHILD_ACCEPT 5 /* TCP port waiting for connections */
child_t *next_free; /* Next child in free list */
child_t *next_all; /* Next child in its list */
};
/*-------------------------------------------------------------------------*/
static child_t childs[MAX_CHILDS] = {{0}};
/* Table of all children created so far.
*/
static int next_child_index = 0;
/* Next index in childs[] to use.
*/
static child_t *all_childs = NULL;
/* List of all children connected to active subservers.
*/
static child_t *free_childs = NULL;
/* List of all children connected to idle subservers.
*/
static child_t *udp_sockets = NULL;
/* List of children with an open UDP socket.
*/
static child_t *tcp_sockets = NULL;
/* List of children with an open TCP connection.
*/
static child_t *accept_sockets = NULL;
/* List of children waiting for TCP connections.
*/
static child_t *child_slots = NULL;
/* List of unused children.
*/
static int childs_waited_for = 0;
/* Number of children which termination has been acknowledged by wait().
* If waitpid() is not available, this value is compared with
* childs_terminated to see if there are zombies to be waited for.
*/
static volatile int childs_terminated = 0;
/* Count of terminated children, maintained by SIGCLD.
*/
static fd_set current_fds, readfds;
/* Master 'read' fdset and the copy for select().
*/
static fd_set current_fds2, writefds;
/* Master 'write' fdset and the copy for select().
*/
static int nfds = 2;
/* Number of opened fds for select().
*/
static time_t current_time;
/* The current time() after select() returns.
*/
static char buf[ERQ_BUFSIZE];
/* The receive buffer.
*/
static const char * erq_dir = ERQ_DIR;
/* The filename of the directory with the ERQ executables. */
/*-------------------------------------------------------------------------*/
/* Forward declarations */
static void start_subserver (long server_num, long seed);
/*-------------------------------------------------------------------------*/
static char *
time_stamp (void)
/* Return a textual representation of the current time
* in the form "YYYY.MM.DD HH:MM:SS [xerq]".
* Result is a pointer to a static buffer.
*
* Putting this function in strfuns is not a good idea, because
* it is need by almost every module anyway.
*/
{
time_t t;
static char result[27];
struct tm *tm;
t = time(NULL);
tm = localtime(&t);
strftime(result, sizeof(result), "%Y.%m.%d %H:%M:%S [erq]", tm);
return result;
} /* time_stamp() */
/*-------------------------------------------------------------------------*/
static long
get_seed(void)
/* Return a seed for the random generator.
*/
{
#ifdef HAVE_GETTIMEOFDAY
struct timeval tv; struct timezone tz;
gettimeofday(&tv, &tz);
return tv.tv_sec * 1000000L + tv.tv_usec;
#else
/* SunOS crashes on times(0), and times returns 0/-1 */
ret = (long)times(&dummy_tms);
if (!ret)
ret = (long)time((time_t *)0) +
dummy_tms.tms_utime
dummy_tms.tms_stime +
dummy_tms.tms_cutime +
dummy_tms.tms_cstime;
return ret;
#endif
} /* get_seed() */
/*-------------------------------------------------------------------------*/
static long
read_32 (char *str)
/* Extract an int32 from the data stream at <str> and return it.
* TODO: Put functions like this, and complete msg-functions into
* TODO:: an erqlib.c module.
*/
{
unsigned char *p = (unsigned char *)str;
return (long)p[0]<<24 | (long)p[1]<<16 | (long)p[2]<<8 | p[3];
} /* read_32() */
/*-------------------------------------------------------------------------*/
static void
write_32 (char *str, long n)
/* Store the int32 <n> into the data stream at <str>.
*/
{
*(str+=3) = n;
*--str = n >>= 8;
*--str = n >>= 8;
*--str = n >>= 8;
} /* write_32() */
/*-------------------------------------------------------------------------*/
static long
readn (int s, void *buf, long expected)
/* Read at least <expected> bytes from fd <s> into <buf>.
* Return the number of bytes actually read; 0 for EOF and -1 on error.
*/
{
long num, total = 0;
char * pbuf;
pbuf = (char *)buf;
do {
do
num = read(s, pbuf+total, expected-total);
while (num == -1 && errno == EINTR);
if (num <= 0)
{
if (!num)
{
fprintf(stderr, "%s read: EOF\n", time_stamp());
return 0;
}
else
{
perror("read");
return -1;
}
}
total += num;
#if ERQ_DEBUG > 0
if (total < expected)
fprintf(stderr, "%s read fragment %ld\n", time_stamp(), num);
#endif
} while (num > 0 && total < expected);
return total;
} /* readn() */
/*-------------------------------------------------------------------------*/
static void
writen (int n, void *message, long length)
/* Write the <message> of <length> bytes onto fd <n>.
*/
{
long num;
do
num = write(n, message, length);
while (num == -1 && errno == EINTR);
if (num != length)
{
fprintf(stderr, "%s wrote %ld, should be %ld\n"
, time_stamp(), num, length);
fprintf(stderr, "%s Giving up.\n", time_stamp());
abort();
}
} /* writen() */
/*-------------------------------------------------------------------------*/
static void
write1 (void *message, long length)
/* Write the <message> of <length> bytes onto stdout, ie to the gamedriver.
*/
{
writen(1, message, length);
} /* write1() */
/*-------------------------------------------------------------------------*/
static int
execute (char *buf, long buflen, char *status, int *sockets)
/* exec() the command <buf> (length <buflen>), store the ERQ_-success code
* into <status> and return the pid (0 on failure).
* If <sockets> is non-NULL, it points to an int[4] into which two
* socketpairs are generated.
*/
{
char path[256], argbuf[1024], *p, *args[96], **argp, c;
pid_t pid;
int quoted;
quoted = 0;
status[0] = ERQ_E_FORKFAIL; /* Good default */
status[1] = 0;
if (buflen >= sizeof argbuf)
{
status[0] = ERQ_E_ARGLENGTH;
return 0;
}
argp = &args[0];
*argp++ = p = argbuf;
while (--buflen >= 0)
{
c = *buf++;
if (c == '\\')
{
if (--buflen >= 0)
{
*p++ = *buf++;
}
else
{
status[0] = ERQ_E_ARGFORMAT;
return 0;
}
}
else if (c == '"')
{
quoted = !quoted;
}
else if (isgraph(c) || quoted)
{
*p++ = c;
}
else
{
*p++ = '\0';
*argp++ = p;
if (argp == &args[sizeof args/sizeof args[0]])
{
status[0] = ERQ_E_ARGNUMBER;
return 0;
}
while( ( (c = *buf) == ' ' || c == '\t') && --buflen >= 0)
buf++;
}
}
*p++ = '\0';
*argp++ = 0;
p = args[0];
if (p[0] == '/' || strstr(p, ".."))
{
status[0] = ERQ_E_ILLEGAL;
return 0;
}
if (strlen(erq_dir) + strlen(p) + 2 > sizeof(path))
{
status[0] = ERQ_E_PATHLEN;
return 0;
}
sprintf(path, "%s/%s", erq_dir, p);
if (sockets)
{
if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0)
{
perror("socketpair");
status[0] = ERQ_E_FORKFAIL;
status[1] = errno;
return 0;
}
if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets + 2) < 0)
{
perror("socketpair");
close(sockets[0]);
close(sockets[1]);
status[0] = ERQ_E_FORKFAIL;
status[1] = errno;
return 0;
}
}
pid = fork();
if (!pid)
{
close(0);
close(1);
if (sockets)
{
dup2(sockets[0], 0);
dup2(sockets[0], 1);
dup2(sockets[2], 2);
close(sockets[0]);
close(sockets[1]);
close(sockets[2]);
close(sockets[3]);
}
execv(path, args);
_exit(1);
}
if (sockets)
{
close(sockets[0]);
close(sockets[2]);
}
if (pid < 0)
{
if (sockets)
{
close(sockets[1]);
close(sockets[3]);
}
status[0] = ERQ_E_FORKFAIL;
status[1] = errno;
return 0;
}
return pid;
} /* execute() */
/*-------------------------------------------------------------------------*/
static void
count_sigcld (int sig)
/* SIGCLD handler, which is called whenever a child process terminates.
* The function takes care to handle multiple SIGCLD occuring at
* the same time - the tricky point is to setup the the next signal(SIGCLD,...)
* after the last pending SIGCLD has been resolved.
*/
{
#ifdef __MWERKS__
# pragma unused(sig)
#endif
#ifndef HAVE_WAITPID
static volatile int calling_signal = 0; /* Mutex */
static volatile int call_signal_again = 0; /* Number of pending calls */
if (!calling_signal)
{
calling_signal = 1;
(void)signal(SIGCLD, (RETSIGTYPE(*)())count_sigcld);
while (call_signal_again)
{
--call_signal_again;
(void)signal(SIGCLD, (RETSIGTYPE(*)())count_sigcld);
}
calling_signal = 0;
}
else
{
call_signal_again++;
}
#if ERQ_DEBUG > 0
write(2, "child terminated\n", 17);
#endif
childs_terminated++;
#endif
} /* count_sigcld() */
/*-------------------------------------------------------------------------*/
static void
dispose_child (child_t *child)
/* The subserver <child> is dead and to be cleant up.
*/
{
struct child_s **chp;
/* Remove it from the fdsets */
FD_CLR(child->socket, &current_fds);
FD_CLR(child->socket, &current_fds2);
close(child->socket);
/* Remove it from the free list */
if (child->state == CHILD_FREE)
{
for (chp = &free_childs; *chp != child; chp = &(*chp)->next_free) /* NOOP */;
*chp = child->next_free;
}
child->state = CHILD_FREE;
/* Remove it from the all list */
for (chp = &all_childs; *chp != child; chp = &(*chp)->next_all) /* NOOP */;
*chp = child->next_all;
/* Put it into the list of unused children */
child->next_all = child_slots;
child_slots = child;
} /* dispose_child() */
/*-------------------------------------------------------------------------*/
static void
kill_child (child_t *child)
/* Kill the subserver child <child>.
*/
{
#if ERQ_DEBUG > 0
fprintf(stderr, "%s kill_child called\n", time_stamp());
#endif
kill(child->u.c.pid, SIGKILL);
dispose_child(child);
} /* kill_child() */
/*-------------------------------------------------------------------------*/
static int
free_socket_childs (void)
/* Is there a child available?
*/
{
return !(!child_slots && next_child_index >= MAX_CHILDS);
} /* free_socket_childs() */
/*-------------------------------------------------------------------------*/
static child_t *
get_socket_child(void)
/* Get a new socket child - if possible, reuse an earlier child.
*/
{
struct child_s *child;
if ( !(child = child_slots) )
{
child = &childs[next_child_index++];
}
else
{
child_slots = child->next_all;
}
return child;
} /* get_socket_child() */
/*-------------------------------------------------------------------------*/
static void
free_socket_child (child_t *child, child_t **listp)
/* The socket <child> in list <listp> is no longer used - remove it
* from the list and make it available again.
*/
{
while (*listp != child)
listp = &(*listp)->next_all;
*listp = child->next_all;
child->next_all = child_slots;
child->state = CHILD_FREE;
child_slots = child;
} /* free_socket_child() */
/*-------------------------------------------------------------------------*/
static int
get_subserver (void)
/* Start a new subserver, resp. reactivate an idle one, and return
* its socket.
*/
{
struct child_s *child;
#if ERQ_DEBUG > 0
fprintf(stderr, "%s get_subserver called\n", time_stamp());
#endif
if (free_childs)
{
/* There is an idle one we can use */
child = free_childs;
free_childs = child->next_free;
}
else
{
/* We have to start a new subserver */
int sockets[2];
int pid;
if (!free_socket_childs())
return -1;
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0)
{
int err = errno;
fprintf(stderr, "%s Error in socketpair()", time_stamp());
errno = err;
perror("socketpair");
return -1;
}
pid = fork();
if(pid == -1)
{
close(sockets[0]);
close(sockets[1]);
return -1;
}
child = get_socket_child();
if (!pid)
{
/* This is the child process */
dup2(sockets[0], 0); /* Read and write on the same socket */
dup2(sockets[0], 1);
close(sockets[0]);
close(sockets[1]);
start_subserver(child - &childs[0], (long)get_ticket());
/* NOTREACHED */
}
/* Set up the child and put it into the subserver list */
close(sockets[0]);
child->u.c.pid = pid;
child->socket = sockets[1];
FD_SET(child->socket, &current_fds);
if (child->socket >= nfds)
nfds = child->socket + 1;
child->next_all = all_childs;
all_childs = child;
}
child->state = CHILD_BUSY;
return child->socket;
} /* get_subserver() */
/*-------------------------------------------------------------------------*/
static void
start_subserver (long server_num, long seed)
/* We are ERQ subserver number <num> and shall use <seed> for our ticket.
* The ERQ main process forks us off to do some time-expensive things
* (like looking up hostnames). If we are idle too long, we will be killed
* off again. Communication to the main process is through stdin/stdout.
*/
{
union ticket_u ticket;
char header[16];
long num, msglen;
int request;
pid_t child = 0;
int child_sockets[4];
char child_handle[4];
#if defined(DETACH) && defined(TIOCNOTTY)
/* Detach from console */
num = open("/dev/tty", O_RDWR);
if (num >= 0)
{
/* We supply header as a 'dummy' argument in case typing is
* too strict
*/
ioctl(num, TIOCNOTTY, header);
close(num);
}
#endif
randomize_tickets(seed ^ get_seed());
FD_ZERO(&readfds);
FD_ZERO(&writefds);
nfds = 1;
childs_terminated = 0;
childs_waited_for = 0;
/* possible race conditions make switching the signal handler awkward */
(void)signal(SIGCLD, (RETSIGTYPE(*)())count_sigcld);
for (;;) /* The Loop (tm) */
{
/* select() for data */
if (child)
{
FD_SET(child_sockets[1], &readfds);
FD_SET(child_sockets[3], &readfds);
}
FD_SET(0, &readfds);
num = select(nfds, &readfds, 0, 0, 0);
if (num < 0)
{
if (errno == EINTR)
continue;
perror ("select");
abort ();
}
/* Look for data from our child.
* We do this before we wait for a died child to make sure that
* all the data produced by the child is received by us.
*/
if (child)
{
int n = 3;
do {
if (FD_ISSET(child_sockets[n], &readfds))
{
do
num = read(child_sockets[n], buf+14, MAX_REPLY - 13);
while (num == -1 && errno == EINTR);
if (num <= 0)
{
perror("read from spawned child\n");
}
else
{
#if ERQ_DEBUG > 0
fprintf(stderr,
"%s %d bytes from socket no. %d\n", time_stamp(), num, n);
fprintf(stderr,
"%s '%.*s'\n", time_stamp(), num, buf+14);
#endif
write_32(buf, (num += 14) - 1);
write_32(buf+4, ERQ_HANDLE_KEEP_HANDLE);
buf[8] = CHILD_LISTEN;
memcpy(buf+9, child_handle, 4);
buf[13] = n == 1 ? ERQ_STDOUT : ERQ_STDERR;
write1(buf, num);
}
}
} while ((n-=2) > 0);
}
/* Check for zombie children and wait for them */
#ifdef HAVE_WAITPID
while(1) {
#else
while (childs_terminated != childs_waited_for) {
#endif
wait_status_t status;
pid_t pid;
do {
#ifdef HAVE_WAITPID
pid = waitpid(-1, &status, WNOHANG);
#else
pid = wait(&status);
#endif
#ifdef ERESTARTSYS
if (pid < 0 && errno == ERESTARTSYS)
continue;
#endif
} while (0);
#ifdef HAVE_WAITPID
if (pid <= 0)
break;
#else
if (pid <= 0)
{
fprintf(stderr,
"%s SIGCLD handler %d times called, but wait not sucessful\n",
time_stamp(), childs_terminated - childs_waited_for
);
abort();
}
#endif
if (pid == child)
{
/* Our child terminated - prepare the header for the reply.
*/
header[8] = CHILD_FREE;
if (WIFEXITED(status))
{
header[9] = ERQ_EXITED;
header[10] = WEXITSTATUS(status);
}
else if (WIFSIGNALED(status))
{
header[9] = ERQ_SIGNALED;
header[10] = WTERMSIG(status);
}
else
{
header[9] = ERQ_E_UNKNOWN;
header[10] = 0;
}
child = 0;
close(child_sockets[1]);
close(child_sockets[3]);
FD_CLR(child_sockets[1], &readfds);
FD_CLR(child_sockets[3], &readfds);
write_32(header, 10);
memcpy(header+4, child_handle, 4);
write1(header, 11); /* releases handle */
}
childs_waited_for++;
} /* wait for zombies */
if (!FD_ISSET(0, &readfds))
continue;
/* Receive the new request from the master ERQ */
do
num = readn(0, header, 9);
while (num == -1 && errno == EINTR);
if (num != 9)
{
fprintf(stderr, "%s read %ld, should be 9!\n", time_stamp(), num);
if (num < 0)
perror("read");
break;
}
msglen = read_32(header) - 9;
request = header[8];
/* ... and the rest of the data */
if (msglen > 0)
{
if (msglen > sizeof(buf))
{
/* Prevent a buffer overflow */
fprintf(stderr, "%s ERROR: Read %ld > buffer size %ld\n", time_stamp(), msglen, sizeof(buf));
num = readn(0, buf, sizeof(buf));
/* Discard the rest of the input from the channel */
msglen -= num;
while (msglen > 0)
{
char tmp[256];
num = readn(0, tmp, sizeof(tmp));
msglen -= num;
}
msglen = sizeof(buf); /* Pretent this was all */
}
else
{
num = readn(0, buf, msglen);
if (num != msglen) {
fprintf(stderr, "%s Read %ld, should be %ld\n", time_stamp(), num, msglen);
break;
}
}
}
/* What does the ERQ want? */
switch(request)
{
case ERQ_RLOOKUP:
{
/* Lookup ip -> name */
struct hostent *hp;
/* handle stays in header[4..7] */
header[8] = CHILD_FREE;
memcpy(header+9, buf, 4); /* copy address */
hp = gethostbyaddr(buf, 4, AF_INET);
if (!hp && h_errno == TRY_AGAIN)
{
/* DNS needs a bit more time */
sleep(3);
hp = gethostbyaddr(buf, 4, AF_INET);
}
if (!hp && h_errno == TRY_AGAIN)
{
/* DNS needs a even more time */
sleep(7);
hp = gethostbyaddr(buf, 4, AF_INET);
}
if (hp)
{
/* Send the address and the name */
msglen = strlen(hp->h_name) + 1;
write_32(header, msglen + 12);
write1(header, 13);
write1((void*)hp->h_name, msglen);
}
else
{
/* No answer, send just the address */
write_32(header, 12);
write1(header, 13);
}
break;
}
#ifdef ERQ_LOOKUP
case ERQ_LOOKUP:
{
/* Lookup name -> ip */
struct hostent *hp;
/* handle stays in header[4..7] */
header[8] = CHILD_FREE;
memcpy(header+9, buf, msglen); /* copy address */
buf[msglen] = '\0'; /* Make sure the string is terminated */
#if ERQ_DEBUG > 3
fprintf(stderr, "%s: ERQ_LOOKUP '%s'\n", time_stamp(), buf);
#endif
hp = gethostbyname(buf);
if (!hp && h_errno == TRY_AGAIN)
{
#if ERQ_DEBUG > 2
fprintf(stderr, "%s: ERQ_LOOKUP '%s': trying again in 3 sec.\n", time_stamp(), buf);
#endif
/* DNS needs more time */
sleep(3);
hp = gethostbyname(buf);
}
if (!hp && h_errno == TRY_AGAIN)
{
#if ERQ_DEBUG > 2
fprintf(stderr, "%s: ERQ_LOOKUP '%s': trying again in 7 sec.\n", time_stamp(), buf);
#endif
/* DNS needs even more time */
sleep(7);
hp = gethostbyname(buf);
}
if (hp)
{
#if ERQ_DEBUG > 3
fprintf(stderr, "%s: ERQ_LOOKUP '%s' -> %d.%d.%d.%d\n"
, time_stamp(), buf
, hp->h_addr[0] & 255
, hp->h_addr[1] & 255
, hp->h_addr[2] & 255
, hp->h_addr[3] & 255
);
#endif
/* Send the name and the address */
msglen = 4;
write_32(header, msglen + 8);
write1(header, 9);
write1(hp->h_addr, msglen);
}
else
{
/* No answer, send an empty message */
write_32(header, 8);
write1(header, 9);
}
break;
}
#endif
#ifdef ERQ_RLOOKUPV6
case ERQ_RLOOKUPV6:
{
int i;
char *mbuff;
struct addrinfo req, *ai, *ai2;
/* handle stays in header[4..7] */
header[8] = CHILD_FREE;
buf[msglen] = 0;
memset(&req, 0, sizeof(struct addrinfo));
req.ai_family = AF_INET6;
req.ai_flags = AI_CANONNAME;
i = getaddrinfo(buf, NULL, &req, &ai);
if (!i)
for (ai2 = ai
; ai2 && (ai2->ai_family != AF_INET)
&& (ai2->ai_family != AF_INET6)
; ai2 = ai2->ai_next) /* NOOP */;
if (!i && ai2 &&ai2->ai_canonname)
{
mbuff = malloc(strlen(ai2->ai_canonname)+strlen(buf)+2);
if (!mbuff)
{
perror("malloc");
exit(errno);
}
strcpy(mbuff, buf);
strcat(mbuff, " ");
strcat(mbuff, ai2->ai_canonname);
msglen = strlen(mbuff) + 1;
}
else
{
mbuff = malloc(strlen("invalid-format")+strlen(buf)+1);
if (!mbuff)
{
perror("malloc");
exit(errno);
}
strcpy(mbuff, "invalid-format");
msglen = strlen(mbuff) + 1;
}
write_32(header, msglen + 8);
write1(header, 9);
write1(mbuff, msglen);
free(mbuff);
if (!i)
freeaddrinfo(ai);
break;
}
#endif /* ERQ_RLOOKUPV6 */
#ifdef ERQ_LOOKUP_SRV
case ERQ_LOOKUP_SRV:
{
/* lookup list of srv records */
struct srvhost * r;
struct srvhost * s;
char service[50], proto[20], hostname[200];
int counter;
char srvbuf[MAX_REPLY];
header[8] = CHILD_FREE;
counter = 0;
srvbuf[0] = '\0';
// TODO: sscanf this from req string
// format: <service>.<proto>.<hostname>
// example: _psyc._tcp.ve.symlynX.com
// getsrv(hostname, service, proto)
memcpy(header + 9, buf, msglen);
buf[msglen] = '\0';
sscanf(buf, "%49[^.].%19[^.].%199s", service, proto, hostname);
#if ERQ_DEBUG > 0
fprintf(stderr, "%s: ERQ_LOOKUP_SRV '%s', %s,%s,%s\n", time_stamp(), buf, service, proto, hostname);
#endif
r = getsrv(hostname, service, proto);
if (!r) {
/* lookup failed, send empty message*/
write_32(header, 8);
write1(header, 9);
#if ERQ_DEBUG > 0
fprintf(stderr, "%s: ERQ_LOOKUP_SRV could not srv_resolve '%s'\n", time_stamp(), buf);
#endif
break;
} else {
s = r;
while (s) {
/* walk list of structures */
/* important members: name, port, pref, weight */
if (strlen(srvbuf) < MAX_REPLY - 1) {
strncat(srvbuf, s -> name, MAX_REPLY - strlen(srvbuf));
strncat(srvbuf, "\n", MAX_REPLY - strlen(srvbuf));
if (strlen(srvbuf) < MAX_REPLY - 1) {
snprintf(hostname, MAX_REPLY - 1, "%d%c", s -> port, '\n');
strncat(srvbuf, hostname, MAX_REPLY - strlen(srvbuf));
if (strlen(srvbuf) < MAX_REPLY - 1) {
snprintf(hostname, MAX_REPLY - 1, "%d%c", s -> pref, '\n');
strncat(srvbuf, hostname, MAX_REPLY - strlen(srvbuf));
if (strlen(srvbuf) < MAX_REPLY - 1) {
snprintf(hostname, MAX_REPLY - 1, "%d%c", s -> weight, '\n');
strncat(srvbuf, hostname, MAX_REPLY - strlen(srvbuf));
} else s = NULL;
} else s = NULL;
} else s = NULL;
} else s = NULL;
s = s -> next;
counter++;
}
freesrvhost(r);
}
write_32(header, strlen(srvbuf) + 9);
write1(header, 9);
write1(srvbuf, strlen(srvbuf) + 1);
break;
}
#endif /* ERQ_LOOKUP_SRV */
case ERQ_EXECUTE:
{
/* Execute a program, wait for its termination and
* return the exit status.
*/
pid_t pid1, pid2;
header[8] = CHILD_FREE;
if ((pid1 = execute(buf, msglen, &header[9], 0)))
{
wait_status_t status;
do {
pid2 = wait(&status);
#ifdef ERESTARTSYS
if (pid2 < 0 && errno == ERESTARTSYS)
continue;
#endif
if (pid2 > 0)
childs_waited_for++;
} while (pid2 >= 1 && pid2 != pid1);
if (pid2 < 1) {
header[9] = ERQ_E_NOTFOUND;
} else if (WIFEXITED(status)) {
header[9] = ERQ_OK;
header[10] = WEXITSTATUS(status);
} else if (WIFSIGNALED(status)) {
header[9] = ERQ_SIGNALED;
header[10] = WTERMSIG(status);
} else {
header[9] = ERQ_E_UNKNOWN;
}
}
write_32(header, 10);
write1(header, 11);
break;
}
case ERQ_FORK:
/* Fork off a process and let it run */
header[8] = CHILD_FREE;
if (execute(buf, msglen, &header[9], 0)) {
header[9] = ERQ_OK;
}
write_32(header, 10);
write1(header, 11);
break;
case ERQ_SPAWN:
/* Similar to ERQ_FORK, but we send back additional
* information to the ERQ so that further communcation
* is possible.
*/
#if ERQ_DEBUG > 0
if (child) {
fprintf(stderr, "%s ERQ_SPAWN: busy\n", time_stamp());
abort();
}
#endif
if ((child = execute(buf, msglen, &header[9], child_sockets)))
{
/* We want to avoid race conditions when a stale ticket
* accesses a fresh process (in the unlikely event that
* the rnd part is equal). The below formula ensures that
* more than 71 minutes pass before a stale ticket can
* refer to a new process (assuming gettimeofday get_seed())
* It also makes ticket guessing harder.
*/
ticket.s.seq += ((get_seed() - ticket.s.seq) & 0x7fffffff) + 1;
ticket.s.rnd = get_ticket();
if (child_sockets[1] >= nfds)
nfds = child_sockets[1] + 1;
if (child_sockets[3] >= nfds)
nfds = child_sockets[3] + 1;
memcpy(child_handle, header+4, 4);
write_32(buf, 17 + sizeof ticket);
write_32(buf+4, ERQ_HANDLE_KEEP_HANDLE);
buf[8] = CHILD_LISTEN;
memcpy(buf+9, header+4, 4);
buf[13] = ERQ_OK;
write_32(buf+14, server_num);
memcpy(buf+18, ticket.c, sizeof ticket);
write1(buf, 18 + sizeof ticket);
}
else
{
header[8] = CHILD_FREE;
write_32(header, 10);
write1(header, 11);
}
break;
case ERQ_SEND:
/* Send some data to our child process */
if (msglen < sizeof ticket + 1)
goto bad_request;
if (!child)
goto no_child;
header[8] = CHILD_LISTEN;
if (memcmp(buf, ticket.c, sizeof ticket))
goto bad_ticket;
msglen -= sizeof ticket;
do
num = write(child_sockets[1], buf + sizeof ticket, msglen);
while (num == -1 && errno == EINTR);
if (num == msglen) {
header[9] = ERQ_OK;
write_32(header, 9);
write1(header, 10);
} else {
if (num >= 0) {
header[9] = ERQ_E_INCOMPLETE;
write_32(header+10, num);
} else {
write_32(header+10, errno);
if (0
#ifdef EAGAIN
|| errno == EAGAIN
#endif
#ifdef EWOULDBLOCK
|| errno == EWOULDBLOCK
#endif
)
{
header[9] = ERQ_E_WOULDBLOCK;
} else if (errno == EPIPE) {
header[9] = ERQ_E_PIPE;
} else {
header[9] = ERQ_E_UNKNOWN;
}
}
write_32(header, 13);
write1(header, 14);
}
break;
case ERQ_KILL:
/* Send the child a signal */
if (msglen != sizeof ticket + 4 && msglen != sizeof ticket)
goto bad_request;
if (!child) {
no_child:
/* could happen due to a race condition */
#if ERQ_DEBUG > 0
fprintf(stderr, "%s ERQ_SEND/ERQ_KILL: No child\n", time_stamp());
#endif
header[8] = CHILD_FREE;
goto notify_bad_ticket;
}
header[8] = CHILD_LISTEN;
if (memcmp(buf, ticket.c, sizeof ticket)) {
bad_ticket:
#if ERQ_DEBUG > 0
fprintf(stderr, "%s ticket.s.rnd: %x vs. %x\n",
time_stamp(), ((struct ticket_s *)buf)->rnd, ticket.s.rnd);
fprintf(stderr, "%s ticket.s.seq: %x vs. %x\n",
time_stamp(), ((struct ticket_s *)buf)->seq, ticket.s.seq);
#endif
notify_bad_ticket:
header[9] = ERQ_E_TICKET;
} else {
int sig;
sig = msglen >= 4 ? read_32(buf+sizeof ticket) : SIGKILL;
#if ERQ_DEBUG > 0
fprintf(stderr, "%s len: %d sig: %d\n", time_stamp(), msglen, sig);
#endif
if (sig >= 0)
sig = kill(child, sig);
header[9] = sig < 0 ? ERQ_E_ILLEGAL : ERQ_OK;
#if ERQ_DEBUG > 0
if (sig < 0)
perror("kill");
#endif
}
write_32(header, 9);
write1(header, 10);
break;
#ifdef ERQ_AUTH
case ERQ_AUTH:
{
/* Connect to the authd on the remote system, and send
* its answer back the the master ERQ resp. the driver.
*/
struct sockaddr_in server_addr, *remote;
int s;
long mud_port;
long num, total;
if (msglen != sizeof (struct sockaddr_in) + 4)
goto bad_request;
header[8] = CHILD_FREE;
remote = (struct sockaddr_in *)buf;
server_addr.sin_family = remote->sin_family;
server_addr.sin_port = htons(AUTH_PORT);
server_addr.sin_addr = remote->sin_addr;
s = socket(remote->sin_family, SOCK_STREAM, 0);
if (s < 0) {
perror("socket");
goto die;
}
if (
connect(s, (struct sockaddr *)&server_addr, sizeof server_addr)
< 0)
{
perror("connect");
write_32(header, 8);
write1(header, 9);
break;
}
mud_port = read_32(buf + msglen - 4);
sprintf(buf, "%d,%ld\r\n", ntohs(remote->sin_port), mud_port);
writen(s, buf, strlen(buf));
total = 0;
do {
do {
num = read(s, buf+total, MAX_REPLY - 8 - total);
} while (num == -1 && errno == EINTR);
if (num <= 0) {
if (buf[total-2] == '\r' && buf[total-1] == '\n')
break;
perror("read (auth)");
goto die;
}
total += num;
} while (num > 0 && total < sizeof buf);
close(s);
write_32(header, 8 + total);
write1(header, 9);
write1(buf, total);
break;
}
#endif /* ERQ_AUTH */
default:
bad_request:
fprintf(stderr, "%s Bad request %d\n", time_stamp(), request);
fprintf(stderr, "%s %x %x %x %x %x %x %x %x %x\n"
, time_stamp()
, header[0],header[1],header[2],header[3]
,header[4],header[5],header[6],header[7]
,header[8]);
fprintf(stderr, "%s %c %c %c %c %c %c %c %c %c\n"
, time_stamp()
, header[0],header[1],header[2],header[3]
,header[4],header[5],header[6],header[7]
,header[8]);
write_32(header, 8);
header[8] = child ? CHILD_LISTEN : CHILD_FREE;
write1(header, 9);
break;
}
} /* for(;;) */
die:
/* We terminate - and also the child if there is one */
if (child)
{
if (kill(child, SIGTERM))
perror("kill");
sleep(1);
kill(child, SIGKILL);
/* make an attempt to release the handle, but without error
* checking, because things are likely to be screwed up.
*/
write_32(header, 10);
memcpy(header+4, child_handle, 4);
header[9] = ERQ_E_UNKNOWN;
header[10] = 0;
write(1, header, 11); /* releases handle */
}
fprintf(stderr, "%s Subserver giving up.\n", time_stamp());
exit(1);
} /* start_subserver() */
/*-------------------------------------------------------------------------*/
int
main (int argc, char **argv)
/* Main program of the ERQ. It contains the main loop waiting for requests.
*/
{
char header[32];
long num;
long msglen;
int subserver;
int s;
int num_ready;
struct timeval timeout;
child_t *child, *next_child;
union ticket_u ticket;
/* Print information about this daemon to help debugging */
{
fprintf(stderr, "%s Amylaar ERQ %s: Path '%s', debuglevel %d\n"
, time_stamp(), __DATE__, argv[0], ERQ_DEBUG
);
}
/* Quick and dirty commandline parser */
{
int is_forked = 0;
int i;
for (i = 1; i < argc; i++)
{
if (!strcmp(argv[i], "--forked"))
is_forked = 1;
else if (!strcmp(argv[i], "--execdir"))
{
if (i+1 >= argc)
{
fprintf(stderr, "%s Missing value for --execdir.\n"
, time_stamp());
goto die;
}
erq_dir = argv[i+1];
i++;
}
else
{
fprintf(stderr, "%s Unknown argument '%s'.\n"
, time_stamp(), argv[i]);
goto die;
}
}
/* Check if we have been forked off the driver */
if (is_forked)
{
write1("1", 1); /* indicate sucessful fork/execl */
}
else
{
fprintf(stderr, "%s dynamic attachement unimplemented\n", time_stamp());
goto die;
}
}
#if defined(DETACH) && defined(TIOCNOTTY)
/* Detach from console */
s = open("/dev/tty", O_RDWR);
if (s >= 0)
{
/* We supply header as a 'dummy' argument in case typing is
* too strict
*/
ioctl(s, TIOCNOTTY, header);
close(s);
}
#endif
/* Initialize variables */
randomize_tickets(get_seed());
FD_ZERO(&current_fds);
FD_ZERO(&current_fds2);
FD_SET(1, &current_fds);
(void)signal(SIGCLD, (RETSIGTYPE(*)())count_sigcld);
/* The main loop */
for (subserver = 0;;)
{
int still_corked; /* Determines the select() wait time */
/* Scan the list of TCP children and check which sent data
* to the driver more than 3 seconds ago. Those are added
* back to current_fds.
* Existence of children which sent data less than 3 seconds
* ago are flagged as 'still_corked'.
*/
still_corked = 0;
for (next_child = tcp_sockets; NULL != (child = next_child); )
{
next_child = child->next_all;
/* Uncork the bottle */
if (child->u.s.bytes_recv)
{
if (child->u.s.last_recv + 3 < time(NULL))
{
#if ERQ_DEBUG > 1
fprintf(stderr,"%s Uncorking child\n", time_stamp());
#endif
child->u.s.bytes_recv = 0;
FD_SET(child->socket, &current_fds);
}
else
still_corked = 1;
}
} /* for(tcpsockets) */
#if ERQ_DEBUG > 0
fprintf(stderr,"%s still_corked = %d\n", time_stamp(), still_corked);
#endif
/* select() for data.
*/
readfds = current_fds;
writefds = current_fds2;
timeout.tv_sec = (still_corked ? 3 : TIME_TO_CHECK_CHILDS);
timeout.tv_usec = 0;
#if ERQ_DEBUG > 0
fprintf(stderr, "%s calling select (nfds = %d)\n", time_stamp(), nfds);
#endif
num_ready = select(nfds, &readfds, &writefds, 0, &timeout);
#if ERQ_DEBUG > 0
fprintf(stderr, "%s select returns %d\n", time_stamp(), num_ready);
#endif
if (num_ready < 0 && errno != EINTR)
{
perror ("select");
abort ();
}
current_time = time(NULL);
/* Kill off idle free children */
{
time_t expired;
expired = current_time - CHILD_EXPIRE;
for (next_child = free_childs; (child = next_child); )
{
next_child = child->next_free;
if (child->u.c.last_used > expired)
continue;
#if ERQ_DEBUG > 0
fprintf(stderr, "%s Max child idle time expired.\n", time_stamp());
#endif
kill_child(child);
}
}
/* Check for zombie children and wait for them */
#ifdef HAVE_WAITPID
while(1) {
#else
while (childs_terminated != childs_waited_for) {
#endif
wait_status_t status;
pid_t pid;
do {
#ifdef HAVE_WAITPID
pid = waitpid(-1, &status, WNOHANG);
#else
pid = wait(&status);
#endif
#ifdef ERESTARTSYS
if (pid < 0 && errno == ERESTARTSYS)
continue;
#endif
} while (0);
if (pid <= 0)
break;
for (child = all_childs; child; child = child->next_all)
{
if (child->u.c.pid == pid)
{
dispose_child(child);
break;
}
}
childs_waited_for++;
}
if (num_ready > 0)
{
/* Check for data from the subservers for the gamedriver */
for (next_child = all_childs; (child = next_child); )
{
long replylen;
char replyheader[12];
char replybuf[ERQ_BUFSIZE];
next_child = child->next_all;
s = child->socket;
if (!FD_ISSET(s, &readfds))
continue;
#if ERQ_DEBUG > 0
fprintf(stderr, "%s query child %d\n", time_stamp(), child - &childs[0]);
#endif
/* Read the standard erq header plus the one-byte
* child state.
*/
num = readn(s, replyheader, 9);
if (num != 9)
{
fprintf(stderr, "%s read %ld, should be 9.\n", time_stamp(), num);
kill_child(child);
continue;
}
/* If there is more data, read the rest of message */
replylen = read_32(replyheader) - 8;
if (replylen > 0)
{
if (replylen > sizeof replybuf)
{
fprintf(stderr, "%s Too long reply.\n", time_stamp());
kill_child(child);
continue;
}
num = readn(s, replybuf, replylen);
if (num != replylen)
{
fprintf(stderr, "%s read %ld, should be %ld\n"
, time_stamp(), num, replylen);
kill_child(child);
continue;
}
}
/* Pass the message received (sans the state byte) on
* to the gamedriver.
*/
write1(replyheader, 8);
write1(replybuf, replylen);
/* We can't simply test for ERQ_HANDLE_KEEP_HANDLE, because a
* subserver can have several handles at a time.
*/
if ((child->state = replyheader[8]) == CHILD_FREE)
{
child->u.c.last_used = current_time;
child->next_free = free_childs;
free_childs = child;
}
}
#if ERQ_DEBUG > 0
fprintf(stderr, "%s queried all children\n", time_stamp());
#endif
#ifdef ERQ_OPEN_UDP
/* Check for data received on UDP sockets for the gamedriver */
for (next_child = udp_sockets; (child = next_child); )
{
length_t length;
long replylen;
char replyheader[16];
char replybuf[ERQ_BUFSIZE];
struct sockaddr_in addr;
int cnt;
next_child = child->next_all;
s = child->socket;
if (!FD_ISSET(s, &readfds))
continue;
/* Receive the UDP message */
length = sizeof addr;
cnt = recvfrom( s, replybuf, sizeof(replybuf), 0
, (struct sockaddr *)&addr, &length);
/* Compose and send the UDP-erq message to the driver */
replylen = length + 19;
if (replylen > MAX_REPLY)
replylen = MAX_REPLY;
write_32(replyheader, replylen);
write_32(replyheader+4, ERQ_HANDLE_KEEP_HANDLE);
memcpy(replyheader+8, child->u.s.handle, 4);
replyheader[12] = ERQ_STDOUT;
write1(replyheader, 13);
write1(&addr.sin_addr.s_addr, 4);
write1(&addr.sin_port, 2);
write1(replybuf, replylen - 19);
}
#endif /* ERQ_OPEN_UDP */
#ifdef ERQ_OPEN_TCP
/* Exchange data between the TCP sockets and the driver */
for (next_child = tcp_sockets; (child = next_child); )
{
int length;
long replylen;
char replyheader[30];
char replybuf[ERQ_BUFSIZE];
int cnt;
next_child = child->next_all;
s = child->socket;
if (FD_ISSET(s, &writefds))
{
/* Socket is ready for writing - nevertheless check
* if there is data pending.
*/
FD_CLR(s, &current_fds2);
cnt = recv(s, replybuf, 1, MSG_PEEK);
if (cnt < 0 && (errno != EWOULDBLOCK && errno != EAGAIN))
{
/* TCP connection died - inform the driver and
* get rid of the child.
*/
replyheader[8] = ERQ_E_UNKNOWN;
replyheader[9] = errno;
replylen = 10;
write_32(replyheader, replylen);
memcpy(replyheader+4, child->u.s.handle, 4);
write1(replyheader, replylen); /* ..and release handle */
FD_CLR(child->socket,&current_fds);
FD_CLR(child->socket,&current_fds2);
close(child->socket);
free_socket_child(child,&tcp_sockets);
break;
}
/* Inform the driver that there is data pending */
replyheader[12] = ERQ_OK;
write_32(replyheader, 17+sizeof(child->u.s.ticket));
write_32(replyheader+4, ERQ_HANDLE_KEEP_HANDLE);
memcpy(replyheader+8, child->u.s.handle, 4);
write_32(replyheader+13, child - &childs[0]);
memcpy(replyheader+17, child->u.s.ticket.c
, sizeof(child->u.s.ticket));
write1(replyheader, 17+sizeof(child->u.s.ticket));
break;
}
else if (!FD_ISSET(s, &readfds))
continue;
/* Read the data */
cnt = read(s, replybuf, MAX_REPLY-100);
FD_CLR(s,&readfds);
if (cnt <= 0)
{
/* No data there - EOF or error */
if (!cnt)
{
replyheader[8] = ERQ_EXITED;
replylen = 9;
}
else
{
replyheader[8] = ERQ_E_UNKNOWN;
replyheader[9] = errno;
replylen = 10;
}
write_32(replyheader, replylen);
memcpy(replyheader+4, child->u.s.handle, 4);
write1(replyheader, replylen); /* ..and release handle */
FD_CLR(child->socket,&current_fds);
FD_CLR(child->socket,&current_fds2);
close(child->socket);
free_socket_child(child,&tcp_sockets);
break;
}
else
{
/* We got data - send it to the driver (but make
* sure not to overrun it).
*/
length = cnt;
#if 0
/* Update the "data pending" strategy */
child->u.s.last_recv = time(NULL);
child->u.s.bytes_recv += cnt;
#endif
if (child->u.s.bytes_recv > 4096)
{
/* Cork the bottle. Let the MUD swallow first */
FD_CLR(s, &current_fds);
#if ERQ_DEBUG > 1
fprintf(stderr,"%s Corking child.\n", time_stamp());
#endif
}
/* Compose the message for the driver and send it */
replylen = length + 13;
if (replylen > MAX_REPLY)
replylen = MAX_REPLY;
write_32(replyheader, replylen);
write_32(replyheader+4, ERQ_HANDLE_KEEP_HANDLE);
memcpy(replyheader+8, child->u.s.handle, 4);
replyheader[12] = ERQ_STDOUT;
write1(replyheader, 13);
write1(replybuf, replylen - 13);
}
}
#endif /* ERQ_OPEN_TCP */
#ifdef ERQ_LISTEN
for (next_child = accept_sockets; (child = next_child); )
{
int length;
long replylen;
char replyheader[30];
char replybuf[ERQ_BUFSIZE];
int cnt;
next_child = child->next_all;
s = child->socket;
if (!FD_ISSET(s, &readfds))
continue;
FD_CLR(s,&current_fds);
/* Connection pending - inform the driver */
replylen = 13;
write_32(replyheader, replylen);
write_32(replyheader+4, ERQ_HANDLE_KEEP_HANDLE);
memcpy(replyheader+8, child->u.s.handle, 4);
replyheader[12] = ERQ_STDOUT;
write1(replyheader, replylen);
}
#endif /* ERQ_LISTEN */
} /* if (num_ready) */
/* We needed to send data to a subserver in the previous loop,
* but didn't get one then. Now try again.
*/
if (subserver < 0)
{
subserver = get_subserver();
if (subserver < 0)
continue;
writen(subserver, header, 9);
writen(subserver, buf, msglen);
}
if (num_ready > 0 && FD_ISSET(1, &readfds))
{
/* TODO: We read from 0 when 1 is ready? */
/* Read the erq message incl. request */
num = readn(0, header, 9);
if (num != 9)
{
fprintf(stderr, "%s Read %ld, should be 9!\n"
, time_stamp(), num);
if (num < 0)
perror("read");
break;
}
#if ERQ_DEBUG > 0
fprintf(stderr, "%s read command %d\n", time_stamp(), header[8]);
#endif
/* Read the rest of the message */
msglen = read_32(header) - 9;
if (msglen > 0)
{
num = readn(0, buf, msglen);
if (num != msglen) {
fprintf(stderr, "%s Read %ld, should be %ld\n"
, time_stamp(), num, msglen);
break;
}
}
/* Switch on the request received */
switch(header[8])
{
/* ----- Send a signal or data ----- */
case ERQ_SEND:
case ERQ_KILL:
{
long n;
/* Check the ticket - the next_child_index at the time
* of creation.
*/
n = read_32(buf);
if ((unsigned long)n >= (unsigned long)next_child_index)
goto bad_ticket;
child = &childs[n];
switch(child->state)
{
struct child_s **listp;
bad_ticket:
default:
{
#if ERQ_DEBUG > 0
fprintf(stderr, "%s Ticket rejected n: 0x%x nxt: 0x%x state: %d\n",
time_stamp(), n, next_child_index,
(unsigned long)n >= (unsigned long)next_child_index ?
0 : childs[n].state);
#endif
subserver = 0; /* ready for new commands */
write_32(header, 9);
header[8] = ERQ_E_TICKET;
write1(header, 9);
break;
}
case CHILD_LISTEN:
/* Pass on the message to the listening child */
subserver = child->socket;
write_32(header, 9 + msglen - 4);
writen(subserver, header, 9);
writen(subserver, buf+4, msglen-4);
break;
#ifdef ERQ_LISTEN
case CHILD_ACCEPT:
/* Can only KILL accept children, not SEND data */
if (header[8] == ERQ_SEND)
goto bad_ticket;
listp = &accept_sockets;
goto handle_send_on_socket;
case CHILD_TCP:
listp = &tcp_sockets;
goto handle_send_on_socket;
#endif
#ifdef ERQ_OPEN_UDP
case CHILD_UDP:
listp = &udp_sockets;
#endif
#if defined(ERQ_OPEN_UDP) || defined(ERQ_LISTEN)
handle_send_on_socket:
/* Check the rest of the ticket */
if (msglen < sizeof(union ticket_u)
|| memcmp(buf+4, child->u.s.ticket.c,
sizeof(union ticket_u)))
{
#if ERQ_DEBUG > 0
fprintf(stderr,"%s Ticket mismatch. (%d, %d)\n", time_stamp(), msglen,sizeof(union ticket_u));
#endif
goto bad_ticket;
}
msglen -= sizeof(union ticket_u);
subserver = 0; /* ready for new commands */
write_32(header, 9); /* "msg_len" of the answer */
if (header[8] == ERQ_SEND)
{
while(1){
if (child->state == CHILD_UDP)
{
struct sockaddr_in host_ip_addr;
if (msglen < 6)
{
header[8] = ERQ_E_ARGLENGTH;
break;
}
memcpy(&host_ip_addr.sin_addr,
buf+sizeof(union ticket_u), 4);
host_ip_addr.sin_family = AF_INET;
memcpy(&host_ip_addr.sin_port,
buf+sizeof(union ticket_u)+4, 2);
num = sendto(child->socket,
buf+sizeof(union ticket_u)+6,
msglen - 6, 0,
(struct sockaddr *)&host_ip_addr,
sizeof(host_ip_addr));
}
else
{
/* Program or TCP socket */
num = write(child->socket,
buf+sizeof(union ticket_u)+4,
msglen-4);
}
if (num != msglen-4)
{
/* Prepare the error message to send back */
if (num < 0)
{
switch(errno) {
case EWOULDBLOCK:
#if EAGAIN != EWOULDBLOCK
case EAGAIN:
#endif
header[3] = 9;
header[8] = ERQ_E_WOULDBLOCK;
break;
case EPIPE:
header[3] = 9;
header[8] = ERQ_E_PIPE;
break;
case EINTR:
continue;
default:
header[3] = 10;
header[8] = ERQ_E_UNKNOWN;
header[9] = errno;
break;
}
}
else
{
header[3] = 13;
header[8] = ERQ_E_INCOMPLETE;
write_32(header+9, num);
}
}
else
{
/* Send back "ok" */
header[3] = 9;
header[8] = ERQ_OK;
}
break;
}
}
else
{
/* header[8] == ERQ_KILL */
FD_CLR(child->socket,&current_fds);
FD_CLR(child->socket,&current_fds2);
if (child->state == CHILD_ACCEPT)
shutdown(child->socket,0);
close(child->socket);
free_socket_child(child,listp);
write_32(header,9);
header[8] = ERQ_OK;
}
write1(header, header[3]);
break;
#endif /* defined(ERQ_OPEN_UDP) || defined(ERQ_LISTEN) */
}
break; /* end of ERQ_KILL / ERQ_SEND */
}
default:
{
/* Non-communication request: pass it on to
* a new subserver.
*/
subserver = get_subserver();
if (subserver >= 0)
{
writen(subserver, header, 9);
writen(subserver, buf, msglen);
}
}
break;
#ifdef ERQ_OPEN_UDP
case ERQ_OPEN_UDP:
{
/* Open a UDP socket */
subserver = 0; /* ready for new commands */
write_32(header, 10);
do {
struct sockaddr_in host_ip_addr;
int tmp;
if (msglen != 2)
{
header[8] = ERQ_E_ARGLENGTH;
header[9] = 0;
break;
}
host_ip_addr.sin_addr.s_addr = INADDR_ANY;
host_ip_addr.sin_family = AF_INET;
memcpy(&host_ip_addr.sin_port, buf, 2);
if (!free_socket_childs())
{
header[8] = ERQ_E_NSLOTS;
header[9] = MAX_CHILDS;
break;
}
s = socket(AF_INET, SOCK_DGRAM, 0);
if (s < 0) {
header[8] = ERQ_E_UNKNOWN;
header[9] = errno;
break;
}
tmp = 1;
if (setsockopt (s, SOL_SOCKET, SO_REUSEADDR,
(char *) &tmp, sizeof (tmp)) < 0)
{
close(s);
header[8] = ERQ_E_UNKNOWN;
header[9] = errno;
break;
}
if (bind(s, (struct sockaddr *)&host_ip_addr,
sizeof host_ip_addr) == -1)
{
close(s);
header[8] = ERQ_E_UNKNOWN;
header[9] = errno;
break;
}
/* We got the socket, now make the child */
child = get_socket_child();
child->socket = s;
FD_SET(child->socket,&current_fds);
child->state = CHILD_UDP;
child->next_all = udp_sockets;
udp_sockets = child;
ticket.s.seq +=
((get_seed() - ticket.s.seq) & 0x7fffffff) + 1;
ticket.s.rnd = get_ticket();
if (s >= nfds)
nfds = s + 1;
memcpy(child->u.s.handle, header+4, 4);
memcpy(child->u.s.ticket.c, ticket.c, sizeof ticket);
/* Compose the answer to the driver */
header[3] = 17 + sizeof ticket;
memcpy(header+8, header+4, 4);
write_32(header+4, ERQ_HANDLE_KEEP_HANDLE);
header[12] = ERQ_OK;
write_32(header+13, child - &childs[0]);
memcpy(header+17, ticket.c, sizeof ticket);
} while(0);
write1(header, header[3]);
}
break;
#endif /* ERQ_OPEN_UDP */
#ifdef ERQ_OPEN_TCP
case ERQ_OPEN_TCP:
{
/* Open a TCP socket and connect it to a given
* address.
*/
subserver = 0; /* ready for new commands */
write_32(header, 10);
do {
struct sockaddr_in host_ip_addr;
int tmp;
if (msglen != 6)
{
header[8] = ERQ_E_ARGLENGTH;
header[9] = 0;
break;
}
host_ip_addr.sin_family = AF_INET;
memcpy(&host_ip_addr.sin_port, buf+4, 2);
memcpy(&host_ip_addr.sin_addr.s_addr, buf, 4);
if (!free_socket_childs())
{
header[8] = ERQ_E_NSLOTS;
header[9] = MAX_CHILDS;
break;
}
s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0)
{
header[8] = ERQ_E_UNKNOWN;
header[9] = errno;
break;
}
if ((tmp = fcntl(s,F_GETFL,0)) < 0)
{
fprintf(stderr,"%s fnctl 1\n", time_stamp());
header[8] = ERQ_E_UNKNOWN;
header[9] = errno;
break;
}
if ((tmp = fcntl(s,F_SETFL,tmp | O_NDELAY)) < 0)
{
fprintf(stderr,"%s fnctl 2\n", time_stamp());
header[8] = ERQ_E_UNKNOWN;
header[9] = errno;
break;
}
tmp = 1;
if (connect(s, (struct sockaddr *)&host_ip_addr,
sizeof host_ip_addr) == -1)
{
if(errno != EINPROGRESS)
{
header[8] = ERQ_E_UNKNOWN;
header[9] = errno;
close(s);
break;
}
}
/* Got the socket, now create the child */
child = get_socket_child();
child->socket = s;
FD_SET(child->socket,&current_fds);
FD_SET(child->socket,&current_fds2);
child->state = CHILD_TCP;
child->u.s.bytes_recv = 0;
child->next_all = tcp_sockets;
tcp_sockets = child;
ticket.s.seq +=
((get_seed() - ticket.s.seq) & 0x7fffffff) + 1;
ticket.s.rnd = get_ticket();
if (s >= nfds)
nfds = s + 1;
memcpy(child->u.s.handle, header+4, 4);
memcpy(child->u.s.ticket.c, ticket.c, sizeof ticket);
/* Compose the answer to the driver */
header[3] = 17 + sizeof ticket;
memcpy(header+8, header+4, 4);
write_32(header+4, ERQ_HANDLE_KEEP_HANDLE);
header[12] = ERQ_OK;
header[3] = 0;
write_32(header+13, child - &childs[0]);
memcpy(header+17, ticket.c, sizeof ticket);
} while(0);
if(header[3]) write1(header, header[3]);
}
break;
#endif /* ERQ_OPEN_TCP */
#ifdef ERQ_LISTEN
case ERQ_LISTEN:
{
subserver = 0; /* ready for new commands */
write_32(header, 10);
do {
struct sockaddr_in host_ip_addr;
int tmp;
if (msglen != 2)
{
header[8] = ERQ_E_ARGLENGTH;
header[9] = 0;
break;
}
host_ip_addr.sin_family = AF_INET;
host_ip_addr.sin_addr.s_addr = INADDR_ANY;
memcpy(&host_ip_addr.sin_port, buf, 2);
if (!free_socket_childs())
{
header[8] = ERQ_E_NSLOTS;
header[9] = MAX_CHILDS;
break;
}
s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0)
{
header[8] = ERQ_E_UNKNOWN;
header[9] = errno;
break;
}
if((tmp = fcntl(s,F_GETFL,0)) < 0)
{
fprintf(stderr,"%s fnctl 1\n", time_stamp());
header[8] = ERQ_E_UNKNOWN;
header[9] = errno;
break;
}
if((tmp = fcntl(s,F_SETFL,tmp | O_NDELAY)) < 0)
{
fprintf(stderr,"%s fnctl 2\n", time_stamp());
header[8] = ERQ_E_UNKNOWN;
header[9] = errno;
break;
}
tmp = 1;
if (bind(s, (struct sockaddr *)&host_ip_addr,
sizeof host_ip_addr) == -1)
{
header[8] = ERQ_E_UNKNOWN;
header[9] = errno;
break;
}
if (listen(s,2) < 0)
{
header[8] = ERQ_E_UNKNOWN;
header[9] = errno;
break;
}
/* Got the socket, now create the child */
child = get_socket_child();
child->socket = s;
FD_SET(child->socket,&current_fds);
child->state = CHILD_ACCEPT;
child->next_all = accept_sockets;
accept_sockets = child;
ticket.s.seq +=
((get_seed() - ticket.s.seq) & 0x7fffffff) + 1;
ticket.s.rnd = get_ticket();
if (s >= nfds)
nfds = s + 1;
memcpy(child->u.s.handle, header+4, 4);
memcpy(child->u.s.ticket.c, ticket.c, sizeof ticket);
/* Compose the message for the driver */
header[3] = 17 + sizeof ticket;
memcpy(header+8, header+4, 4);
write_32(header+4, ERQ_HANDLE_KEEP_HANDLE);
header[12] = ERQ_OK;
write_32(header+13, child - &childs[0]);
memcpy(header+17, ticket.c, sizeof ticket);
} while(0);
if(header[3]) write1(header, header[3]);
}
break;
#endif /* ERQ_LISTEN */
#ifdef ERQ_ACCEPT
case ERQ_ACCEPT:
{
/* Accept a connection from a socket */
subserver = 0; /* ready for new commands */
write_32(header, 10);
do {
struct child_s *parent;
struct sockaddr_in host_ip_addr;
long tmp2;
int tmp;
length_t len;
int n;
if (msglen != sizeof(union ticket_u) + 4)
{
header[8] = ERQ_E_ARGLENGTH;
header[9] = 0;
break;
}
n = read_32(buf);
if((unsigned long) n >= (unsigned long) next_child_index)
{
fprintf(stderr,"%s given: %d, nxt: %d\n", time_stamp(), n,next_child_index);
goto accept_bad_ticket;
}
parent = &childs[n];
if(parent->state != CHILD_ACCEPT)
{
fprintf(stderr,"%s State is %d, should be %d!\n", time_stamp(), parent->state,CHILD_ACCEPT);
goto accept_bad_ticket;
}
if(memcmp(buf+4,parent->u.s.ticket.c,sizeof(union ticket_u)))
{
accept_bad_ticket:
fprintf(stderr,"%s Accept: Ticket mismatch.\n", time_stamp());
header[8] = ERQ_E_TICKET;
header[9] = 0;
break;
}
if (!free_socket_childs())
{
header[8] = ERQ_E_NSLOTS;
header[9] = MAX_CHILDS;
break;
}
len = sizeof(host_ip_addr);
s = accept( parent->socket
, (struct sockaddr *) &host_ip_addr
, &len);
if (s < 0)
{
header[8] = ERQ_E_UNKNOWN;
header[9] = errno;
break;
}
if((tmp = fcntl(s,F_GETFL,0)) < 0)
{
fprintf(stderr,"%s fnctl 1\n", time_stamp());
header[8] = ERQ_E_UNKNOWN;
header[9] = errno;
break;
}
if((tmp = fcntl(s,F_SETFL,tmp | O_NDELAY)) < 0)
{
fprintf(stderr,"%s fnctl 2\n", time_stamp());
header[8] = ERQ_E_UNKNOWN;
header[9] = errno;
break;
}
tmp = 1;
child = get_socket_child();
child->socket = s;
/* Socket accepted, wait for more conns */
FD_SET(parent->socket, &current_fds);
FD_SET(child->socket, &current_fds);
child->state = CHILD_TCP;
child->u.s.bytes_recv = 0;
child->next_all = tcp_sockets;
tcp_sockets = child;
ticket.s.seq +=
((get_seed() - ticket.s.seq) & 0x7fffffff) + 1;
ticket.s.rnd = get_ticket();
if (s >= nfds)
nfds = s + 1;
memcpy(child->u.s.handle, header+4, 4);
memcpy(child->u.s.ticket.c, ticket.c, sizeof ticket);
header[3] = 23 + sizeof ticket;
memcpy(header+8, header+4, 4);
write_32(header+4, ERQ_HANDLE_KEEP_HANDLE);
header[12] = ERQ_OK;
memcpy(header+13, &host_ip_addr.sin_addr.s_addr, 4);
memcpy(header+17, &host_ip_addr.sin_port, 2);
write_32(header+19, child - &childs[0]);
memcpy(header+23, ticket.c, sizeof ticket);
} while(0);
if (header[3])
write1(header, header[3]);
}
break;
#endif /* ERQ_ACCEPT */
} /* switch() */
} /* if (num_ready > 0 && FD_ISSET(1, &readfds)) */
if (subserver < 0)
FD_CLR(1, &current_fds);
else
FD_SET(1, &current_fds);
} /* main loop */
die:
fprintf(stderr, "%s Giving up.\n", time_stamp());
return 1;
} /* main() */
/***************************************************************************/