/*--------------------------------------------------------------------------- * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "machine.h" #ifdef HAVE_SYS_TIME_H # include #endif #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_STRING_H # include #else # include #endif #ifdef HAVE_BSTRING_H # include #endif #ifdef HAVE_STDLIB_H # include #endif #ifdef HAVE_SYS_TERMIOS_H # include #endif #ifdef _AIX # include #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 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 into the data stream at . */ { *(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 bytes from fd into . * 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 of bytes onto fd . */ { 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 of 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 (length ), store the ERQ_-success code * into and return the pid (0 on failure). * If 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 is dead and to be cleant up. */ { struct child_s **chp; /* Remove it from the fdsets */ FD_CLR(child->socket, ¤t_fds); FD_CLR(child->socket, ¤t_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 . */ { #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 in list 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, ¤t_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 and shall use 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: .. // 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(¤t_fds); FD_ZERO(¤t_fds2); FD_SET(1, ¤t_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, ¤t_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, ¤t_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,¤t_fds); FD_CLR(child->socket,¤t_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,¤t_fds); FD_CLR(child->socket,¤t_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, ¤t_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,¤t_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,¤t_fds); FD_CLR(child->socket,¤t_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,¤t_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,¤t_fds); FD_SET(child->socket,¤t_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,¤t_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, ¤t_fds); FD_SET(child->socket, ¤t_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, ¤t_fds); else FD_SET(1, ¤t_fds); } /* main loop */ die: fprintf(stderr, "%s Giving up.\n", time_stamp()); return 1; } /* main() */ /***************************************************************************/