/* yaya.lcs.mit.edu:/u/sra/INQUIR/tcpmuxd/tcpmuxd.c, 3-Sep-1990, sra */
/*
 * TCPMUX server (RFC-1078), to be run under inetd.
 *
 * Services are specified by files in the directory specified by
 * TCPMUX_DIRECTORY (normally /etc/tcpmuxd.servers).  Services names
 * are converted to lowercase before looking for a file, so all
 * entries in the directory should be lowercase.  If the server file
 * is setuid or setgid, tcpmuxd will set both the real and effective
 * IDs before checking access or execing the program.  The TCPMUX
 * "help" command is supported by listing files in this directory;
 * files begining with "." are not listed.  Thus, if for some perverse
 * reason you want to have hidden servers, start their contact names
 * with a "." character.
 *
 * Programs listed in /etc/inetd.conf are not automaticly used by
 * tcpmuxd.  It is fairly straightforward to write a shell script
 * which will make the appropriate entries in the service directory,
 * if you care.
 *
 * Note that, due to an unfortunate disagreement between the way unix
 * exec() works and the TCPMUX protocol, there is no way to issue an
 * appropriate TCPMUX error message if the exec() fails.  If you get
 * complaints that clients get a positive acknowledgement immediately
 * followed by the connection closing, the probably is probably a
 * trashed executable file for the specified contact name.
 */

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/dir.h>

#ifndef	TCPMUX_DIRECTORY
#define	TCPMUX_DIRECTORY	"/etc/tcpmuxd.servers/"
#endif

static void fatal(char *jname, char *msg, char *err);
static void help(char *jname);

extern char *sys_errlist[];

void main(int argc, char *argv[])
{
    char filename[512] = TCPMUX_DIRECTORY, *p, *cr = NULL;
    struct stat node;

    for (p = filename+sizeof(TCPMUX_DIRECTORY)-1;; ++p) {
	if (p >= filename+sizeof(filename))
	    fatal(argv[0], "reading contact name", "contact name too long");
	switch (read(0, p, 1)) {
	  case 0:
	    fatal(argv[0], "reading contact name", "premature EOF");
	  case -1:
	    fatal(argv[0], "reading contact name", NULL);
	}
	if (cr != NULL && *p == '\n')
	    break;
	cr = (*p == '\r' ? p : NULL);
	if (isupper(*p))
	    *p = tolower(*p);
    }
    *cr = '\0';
    if (strcmp(filename+sizeof(TCPMUX_DIRECTORY)-1, "help") == 0)
	help(argv[0]);
    if (lstat(filename, &node) == -1)
	fatal(argv[0], "looking up file attributes of server", NULL);
    if ((node.st_mode & S_ISGID) && setgid(node.st_gid) == -1)
	fatal(argv[0], "setting up group ID", NULL);
    if ((node.st_mode & S_ISUID) && setuid(node.st_uid) == -1)
	fatal(argv[0], "setting up user ID", NULL);
    if (access(filename, X_OK) == -1)
	fatal(argv[0], "checking access of server", NULL);
    printf("+Running %s\r\n", filename);
    fflush(stdout);
    execl(filename, filename+sizeof(TCPMUX_DIRECTORY)-1, NULL);
    /*
     * Whoops, execl() failed, but we can't send an error message
     * without violating the protocol.  So we just quietly die.
     */
    exit(1);
}

static void fatal(char *jname, char *msg, char *err)
{
    printf("-%s: %s: %s\r\n", jname, msg,
	   (err == NULL ? sys_errlist[errno] : err));
    exit(1);
}

static void help(char *jname)
{
    DIR *dir;
    struct direct *file;

    if ((dir = opendir(TCPMUX_DIRECTORY)) != NULL) {
	while ((file = readdir(dir)) != NULL)
	    if (file->d_name[0] != '.')
		printf("%s\r\n", file->d_name);

	closedir(dir);
    }
    exit(0);
}
