[comp.sources.reviewed] v01i003: supersrv - offer any program as a network service, Part02/02

Steven Grimm <Steven.Grimm@Eng.Sun.COM> (06/01/91)

Submitted-by: Steven Grimm <Steven.Grimm@Eng.Sun.COM>
Posting-number: Volume 1, Issue 3
Archive-name: supersrv/part02

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 2 (of 2)."
# Contents:  supersrv.c
# Wrapped by csr@calvin.doc.ca on Fri May 31 23:28:09 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'supersrv.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'supersrv.c'\"
else
echo shar: Extracting \"'supersrv.c'\" \(8732 characters\)
sed "s/^X//" >'supersrv.c' <<'END_OF_FILE'
X#include "common.h"
X#include <netinet/in.h>
X#include <arpa/inet.h>
X
Xextern int errno;
X
X/*
X * SuperServer.
X *
X * 1.2: REGISTER handler sends QUIT to all old servers running with the same
X *      username.
X * 1.3: Fix infinite loop bug
X *	Pass client IP address to server
X * 1.4: Services can have descriptions
X */
X
X#define WELCOME	"SuperServer -- enter service desired.\n"
X#define NOSERV	"Service not offered.\n"
X
Xint thirty;
X
X/*
X * This structure is used to keep the database of available services.
X */
Xstruct service {
X	char	name[20];	/* Service name */
X	char	desc[50];	/* Service description */
X	int	fd;		/* File descriptor that offers it */
X	struct service *next;	/* Next service in list */
X} *list = (struct service *)0;
X
Xchar users[NOFILE][12];		/* user connected to each fd */
X
X/*
X * The peer of the most recent connection.
X */
Xstruct sockaddr_in rcon;
X
Xmain()
X{
X	int	fd_so,			/* Socket() file descriptor */
X		fd_co;			/* Connected file descriptor */
X	short	portno;			/* Port number to listen on */
X	char	request[80];
X	extern int sigchld();
X
X/*
X * First things first: put ourselves in the background.
X */
X/*	if (fork())
X		exit(0);*/
X
X	portno = SUPERPORT;
X	thirty = 30;
X
X/*
X * Set up the server socket on the appropriate port number and listen on it.
X * If we get an address-already-in-use error, silently exit, since we're
X * presumably waiting for the TCP port to be cleared up, and will be executed
X * again by server.
X */
X	fd_so = serversock(portno);
X	if (fd_so < 0)
X	{
X		if (errno != EADDRINUSE)
X			perror("serversock");
X		exit(errno);
X	}
X
X	(void)listen(fd_so, 5);
X	setsockopt(fd_so, SOL_SOCKET, SO_LINGER, &thirty, sizeof(thirty));
X	fcntl(fd_so, F_SETOWN, getpid());
X
X/*
X * And we'll need to accomodate child processes.
X */
X	signal(SIGCHLD, sigchld);
X/*
X * We will catch broken pipes in our main loop, so don't fuss with signals.
X */
X	signal(SIGPIPE, SIG_IGN);
X
X/*
X * Now keep accepting connections and interpreting them.
X */
X	while (1)
X	{
X		fd_co = getcon(fd_so);
X
X		if (fd_co < 0)
X		{
X			perror("accept");
X			exit(0);
X		}
X
X		fcntl(fd_co, F_SETOWN, getpid());
X		setsockopt(fd_co,SOL_SOCKET,SO_LINGER,&thirty, sizeof(thirty));
X
X		write(fd_co, WELCOME, sizeof(WELCOME)-1);
X		if (getline(fd_co, request, sizeof(request)-1))
X		{
X			if (handle(fd_co, request))
X				close(fd_co);
X		}
X		else
X			close(fd_co);
X	}
X}
X
X/*
X * Get a connection, or handle a disconnected server.
X */
Xgetcon(old)
Xint old;
X{
X	struct	service *cur;
X	fd_set	reed, tread, other;
X	int	fd, size;
X
X	FD_ZERO(&reed);
X	FD_ZERO(&other);
X
X	for (cur = list; cur; cur = cur->next)
X		FD_SET(cur->fd, &reed);
X	FD_SET(old, &reed);
X
X	while (1)
X	{
X		tread = reed;
X		select(NOFILE, &tread, NULL, NULL, NULL);
X		if (FD_ISSET(old, &tread))
X			break;
X		for (fd = 0; fd < NOFILE; fd++)
X			if (FD_ISSET(fd, &tread))
X			{
X				killfd(fd);
X				close(fd);
X				FD_CLR(fd, &reed);
X			}
X	}
X	size = sizeof(rcon);
X	return( accept(old, &rcon, &size) );
X}
X
X
X/*
X * Get an input line from a file descriptor.  This is probably very slow.
X * Since it's only called once, though...
X */
Xgetline(fd, buf, len)
Xint fd, len;
Xchar *buf;
X{
X	int	index = 0;
X	char	c;
X
X	while (read(fd, &c, 1) == 1)
X	{
X		if (c == '\n')
X			break;
X
X		if (c == '\r')
X			continue;
X
X		if (index < len)
X			buf[index++] = c;
X	}
X
X	buf[index] = 0;
X	return index;
X}
X
X
X/*
X * Handle a user request.  This will either be "REGISTER" or some
X * user-defined function.
X */
Xhandle(fd, string)
Xint fd;
Xchar *string;
X{
X	struct service *cur;
X	char user[16], hisip[16];
X
X	if (string[0] == '\0')
X		return 1;
X/*
X * If a subserver wants to register itself, grab service
X * names from it until it outputs an empty line.
X */
X	if (!strcmp(string, "REGISTER"))
X	{
X		char	name[70];
X		int	i;
X
X		if (! getline(fd, users[fd], 11))
X			return 1;
X
X/*
X * If this user already has servers connected, kill them off.
X */
X		for (i = 0; i < NOFILE; i++)
X			if (i != fd && ! strcmp(users[i], users[fd]))
X			{
X				killfd(i);
X				write(i, "QUIT\0xxxxxxxxxxxxxxx", 20);
X				close(i);
X				users[i][0] = '\0';
X			}
X
X/*
X * Get service names.  If there are descriptions, use those as well.
X */
X		while (getline(fd, name, 69))
X		{
X			char *desc, *index();
X
X#ifdef DEBUG
X			printf("supersrv got input '%s'\n", name);
X#endif
X			desc = index(name, '\t');
X			if (desc != NULL)
X				*desc++ = '\0';
X
X			cur = (struct service *)malloc(sizeof(*cur));
X			strcpy(cur->name, name);
X			if (desc != NULL)
X				strcpy(cur->desc, desc);
X			else
X				cur->desc[0] = '\0';
X
X			cur->fd = fd;
X			cur->next = list;
X			list = cur;
X		}
X		return 0;	/* Keep file descriptor open */
X	}
X
X	getline(fd, user, 15);
X
X/*
X * Print a nicely-formatted list of services.  If there aren't any services,
X * print a message to that effect.
X */
X	if (!strcmp(string, "LIST"))
X	{
X		char buf[100], format[20];
X		int maxlen = 10;
X#define LISTHEAD	"Username     Service\n--------     -------\n"
X#define NOSERVS		"No services available.\n"
X
X		if (list)
X		{
X			write(fd, LISTHEAD, sizeof(LISTHEAD)-1);
X
X			for (cur = list; cur; cur = cur->next)
X				if (maxlen < strlen(cur->name))
X					maxlen = strlen(cur->name);
X			sprintf(format, "%%-12s %%-%ds%%s\n", maxlen + 2);
X
X			for (cur = list; cur; cur = cur->next)
X			{
X				if (user[0] && strcmp(user, users[cur->fd]))
X					continue;
X				sprintf(buf, format, users[cur->fd], cur->name,
X						cur->desc);
X				write(fd, buf, strlen(buf));
X			}
X		}
X		else
X			write(fd, NOSERVS, sizeof(NOSERVS)-1);
X		return 1;
X
X	}
X
X	for (cur = list; cur; cur=cur->next)
X		if (! strcmp(string, cur->name))
X			if ((! user[0]) || (! strcmp(user, users[cur->fd])))
X				break;
X
X	if (! cur)
X	{
X		write(fd, NOSERV, sizeof(NOSERV));
X		return 1;
X	}
X
X	write(cur->fd, string, 20);
X/*
X * 1.3: Write out the client's IP address (which we know will be in rcon.)
X */
X	sprintf(hisip, "%s\n", inet_ntoa(rcon.sin_addr));
X	write(cur->fd, hisip, strlen(hisip));
X
X	shuffle(cur->fd, fd);
X
X	return 1;
X}
X
Xsigchld()
X{
X	wait(0);
X}
X
X/*
X * Kill all entries in the linked list with a certain file
X * descriptor.
X */
Xkillfd(fd)
Xint fd;
X{
X	struct service *cur, *temp;
X
X	while (list && list->fd == fd)
X	{
X		temp = list->next;
X		free(list);
X		list = temp;
X	}
X
X	if (list)
X		for (cur = list; cur; cur = cur->next)
X			while (cur->next && cur->next->fd == fd)
X			{
X				temp = cur->next;
X				cur->next = cur->next->next;
X				free(temp);
X			}
X}
X
X
X/*
X * This is the kludgy part.  We want to effectively connect the
X * client and the appropriate subserver.  Since there's no way to
X * connect two sockets together, we have to fork off a child and
X * sit there shuffling bytes back and forth between the two file
X * descriptors.  When one of them shuts down, we shut the other one
X * down and die.
X *
X * For now, since only one client can be talking to each subserver
X * at a given time, we erase all the subserver's services from the
X * service list.  It will reconnect when it's done forking off the
X * service.
X */
X#ifndef MIN
X#define MIN(x,y)	(((x)>(y))?(y):(x))
X#endif
X
Xshuffle(subsrv, client)
Xint subsrv, client;
X{
X	int		fd;
X	fd_set		reed, rite, except;
X	extern void	quit();
X
X	killfd(subsrv);
X
X	if (fork())
X	{
X		close(subsrv);
X		return;
X	}
X
X	for (fd = 0; fd < NOFILE; fd++)
X		if (fd != client && fd != subsrv)
X			close(fd);
X
X	FD_ZERO(&reed);
X	FD_SET(client, &reed);
X	FD_SET(subsrv, &reed);
X	FD_ZERO(&rite);
X	except = reed;
X
X	fcntl(client, F_SETOWN, getpid());
X	fcntl(subsrv, F_SETOWN, getpid());
X/*	fcntl(client, F_SETFL, FNDELAY);
X	fcntl(subsrv, F_SETFL, FNDELAY);
X*/
X
X	signal(SIGURG, quit);
X	signal(SIGPIPE, quit);
X
X	while (1)
X	{
X		fd_set	tread, twrite, texcept;
X		int	numbytes, bsize, numread;
X		char	buf[4096];
X
X		tread = reed;
X		twrite = rite;
X		texcept = except;
X
X		select(NOFILE, &tread, &twrite, &texcept, (void *)0);
X
X		if (FD_ISSET(subsrv, &tread))
X		{
X			ioctl(subsrv, FIONREAD, &numbytes);
X			bsize = MIN(numbytes, sizeof(buf));
X			numread = read(subsrv, buf, bsize);
X			if (numread < 0 && errno != EWOULDBLOCK)
X			{
X				perror("subsrv");
X				exit(0);
X			}
X			if (! numread)
X			{
X				shutdown(client, 1);
X				shutdown(subsrv, 0);
X				FD_CLR(subsrv, &reed);
X			}
X			else
X				write(client, buf, numread);
X		}
X
X		if (FD_ISSET(client, &tread))
X		{
X			ioctl(client, FIONREAD, &numbytes);
X			bsize = MIN(numbytes, sizeof(buf));
X			numread = read(client, buf, bsize);
X			if (numread < 0 && errno != EWOULDBLOCK)
X			{
X				perror("client");
X				exit(0);
X			}
X			if (! numread)
X			{
X				shutdown(client, 0);
X				shutdown(subsrv, 1);
X				FD_CLR(client, &reed);
X			}
X			else
X				write(subsrv, buf, numread);
X		}
X
X/* If both sides were shut down, leave. */
X		if (! (FD_ISSET(client, &reed) || FD_ISSET(subsrv, &reed)))
X		{
X			close(client);
X			close(subsrv);
X			exit(0);
X		}
X
X		if (FD_ISSET(client, &texcept) || FD_ISSET(subsrv, &texcept))
X		{
X			close(client);
X			close(subsrv);
X			exit(0);
X		}
X	}
X}
X
Xvoid quit()
X{
X	exit(0);
X}
X
END_OF_FILE
if test 8732 -ne `wc -c <'supersrv.c'`; then
    echo shar: \"'supersrv.c'\" unpacked with wrong size!
fi
# end of 'supersrv.c'
fi
echo shar: End of archive 2 \(of 2\).
cp /dev/null ark2isdone
MISSING=""
for I in 1 2 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked both archives.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0
exit 0 # Just in case...
-- 
        Andrew Patrick acting as Comp.Sources.Reviewed Moderator
              Department of Communications, Ottawa, CANADA
                           csr@calvin.doc.CA