/* $Xorg: server.c,v 1.5 2000/08/17 19:54:01 cpqbld Exp $ */

/************************************************************************/
/* Copyright (c) 1993 Quarterdeck Office Systems			*/
/*									*/
/* Permission to use, copy, modify, distribute, and sell this software	*/
/* and software and its documentation for any purpose is hereby granted	*/
/* without fee, provided that the above copyright notice appear in all	*/
/* copies and that both that copyright notice and this permission	*/
/* notice appear in supporting documentation, and that the name		*/
/* Quarterdeck Office Systems, Inc. not be used in advertising or	*/
/* publicity pertaining to distribution of this software without	*/
/* specific, written prior permission.					*/
/*									*/
/* THIS SOFTWARE IS PROVIDED `AS-IS'.  QUARTERDECK OFFICE SYSTEMS,	*/
/* INC., DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,		*/
/* INCLUDING WITHOUT LIMITATION ALL IMPLIED WARRANTIES OF		*/
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR		*/
/* NONINFRINGEMENT.  IN NO EVENT SHALL QUARTERDECK OFFICE SYSTEMS,	*/
/* INC., BE LIABLE FOR ANY DAMAGES WHATSOEVER, INCLUDING SPECIAL,	*/
/* INCIDENTAL OR CONSEQUENTIAL DAMAGES, INCLUDING LOSS OF USE, DATA, OR	*/
/* PROFITS, EVEN IF ADVISED OF THE POSSIBILITY THEREOF, AND REGARDLESS	*/
/* OF WHETHER IN AN ACTION IN CONTRACT, TORT OR NEGLIGENCE, ARISING OUT	*/
/* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.	*/
/************************************************************************/
/* $XFree86: xc/programs/rstart/server.c,v 1.5tsi Exp $ */

/* Extended rsh "helper" program */
#include <stdio.h>
#include <ctype.h>
#include <X11/Xos.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/stat.h>

#ifdef	ODT1_DISPLAY_HACK
#include <netdb.h>
#endif	/* ODT1_DISPLAY_HACK */

#include "server.h"

#define	TRUE	1
#define	FALSE	0

static void squish_out_escapes(char *s);
static char *Strdup(char *s);
static int get_a_line(FILE *f, int *pargc, char ***pargv);
static char *Strlwr(char *s0);
static void do_it(void);
static void process(FILE *f, int is_the_real_thing);
static void detach(void);
static void putenv_with_prefix(char *prefix, char *name, char *value);

struct key {
	char *name;
	void (*func)(int ac, char **av);
};

static void key_cmd(int ac, char **av);
static void key_exec(int ac, char **av);
static void key_context(int ac, char **av);
static void key_misc(int ac, char **av);
static void key_generic_cmd(int ac, char **av);
static void key_dir(int ac, char **av);
static void key_detach(int ac, char **av);
static void key_nodetach(int ac, char **av);
static void key_posix_umask(int ac, char **av);
static void key_internal_registries(int ac, char **av);
static void key_internal_local_default(int ac, char **av);
static void key_internal_global_contexts(int ac, char **av);
static void key_internal_local_contexts(int ac, char **av);
static void key_internal_global_commands(int ac, char **av);
static void key_internal_local_commands(int ac, char **av);
static void key_internal_variable_prefix(int ac, char **av);
static void key_internal_print(int ac, char **av);


static struct key keys[] = {
	{ "cmd",			key_cmd },
	{ "exec",			key_exec },
	{ "context",			key_context },
	{ "misc",			key_misc },
	{ "generic-cmd",		key_generic_cmd },
	{ "dir",			key_dir },
	{ "detach",			key_detach },
	{ "nodetach",			key_nodetach },
	{ "posix-umask",		key_posix_umask },
	{ "auth",			key_auth },
	{ "internal-registries",	key_internal_registries },
	{ "internal-local-default",	key_internal_local_default },
	{ "internal-global-contexts",	key_internal_global_contexts },
	{ "internal-local-contexts",	key_internal_local_contexts },
	{ "internal-global-commands",	key_internal_global_commands },
	{ "internal-local-commands",	key_internal_local_commands },
	{ "internal-variable-prefix",	key_internal_variable_prefix },
	{ "internal-print",		key_internal_print },
	{ "internal-auth-program",	key_internal_auth_program },
	{ "internal-auth-input",	key_internal_auth_input },
	{ NULL,				NULL }
};


static char **parm_cmd = NULL;
static char **parm_exec = NULL;
static char **parm_generic_cmd = NULL;
static char *parm_dir = NULL;
static char *parm_context = NULL;
static char **parm_internal_registries = NULL;
static char *parm_internal_local_default = NULL;
static char *parm_internal_global_contexts = NULL;
static char *parm_internal_local_contexts = NULL;
static char *parm_internal_global_commands = NULL;
static char *parm_internal_local_commands = NULL;
static char *parm_internal_variable_prefix = NULL;
static int parm_detach = FALSE;

static char *parm_global_default = DEFAULT_CONFIG;
char myname[]=SERVERNAME;

int
main(int argc, char *argv[])
{
	FILE *f;

	if(argc == 3 && !strcmp(argv[1], "-c")) parm_global_default = argv[2];

	setbuf(stdin, NULL);

	printf(
	    "%s: Ready: version 1.0, May 02 1994 X11R6.3\n",
	    myname);
	fflush(stdout);

	f = fopen(parm_global_default, "r");
	if(f) process(f, FALSE);

	if(parm_internal_local_default) {
		/* We start in $HOME */
		f = fopen(parm_internal_local_default, "r");
		if(f) process(f, FALSE);
	}

	process(stdin, TRUE);

	do_it();
	exit(0);
}

static void
squish_out_escapes(char *s)
{
	char *p;
	char *o;

	o = s;
	for(p = s; *p; p++) {
		if(*p == '\\') {
			if(p[1] >= '0' && p[1] <= '3'
			&& p[2] >= '0' && p[2] <= '7'
			&& p[3] >= '0' && p[3] <= '7') {
				*o++ = ((p[1]-'0') << 6)
					+ ((p[2]-'0') << 3)
					+ (p[3]-'0');
				p += 3;
			} else {
				printf(
			"%s: Failure: Improper \\ escape\n",myname);
				exit(255);
			}
		} else *o++ = *p;
	}
	*o = '\0';
}

static char *
Strdup(char *s)
{
    char *cs;

    if (!s)
	return s;
    cs = malloc(strlen(s)+1);
    strcpy(cs, s);
    return cs;
}

static int
get_a_line(FILE *f, int *pargc, char ***pargv)
{
	char buf[2048];
	char *p;
	int c;
	char **pa;
	int was_space;
	char *saved;

	while(1) {
		p = buf;
		while((c = getc(f)) != '\n') {
			switch(c) {
			    case '\0':
			    case '\r':
				/* Ignored, per spec */
				break;
			    case EOF:
				return FALSE;
			    default:
				*p++ = c;
				break;
			}
		}
		*p = '\0';

		saved = Strdup(buf);
		if(!saved) nomem();

		*pargc = 0;
		was_space = TRUE;
		for(p = saved; *p; p++) {
			if(was_space && !isspace(*p)) (*pargc)++;
			was_space = isspace(*p);
		}

		*pargv = (char **)malloc((*pargc+1)*sizeof(char *));
		if(!*pargv) nomem();

		pa = *pargv;
		was_space = TRUE;
		for(p = saved; *p; p++) {
			if(was_space && !isspace(*p)) *pa++ = p;
			was_space = isspace(*p);
			if(isspace(*p)) *p = '\0';
		}
		*pa = NULL;

		if(*pargc > 0 && (*pargv)[0][0] == '#') {
			/* How embarrassing.  All that work for a comment. */
			free(saved);
			free(*pargv);
			continue;
		}

		break;
	}

	for(pa = *pargv; *pa; pa++) {
		squish_out_escapes(*pa);
	}

	return TRUE;
}

void
nomem(void)
{
	printf("%s: Failure: Out of memory\n",myname);
	exit(255);
}

static void
key_internal_registries(int ac, char **av)
{
	parm_internal_registries = av+1;
}

static void
key_internal_variable_prefix(int ac, char **av)
{
	if(ac != 2) {
		printf(
		    "%s: Failure: Malformed INTERNAL-VARIABLE-PREFIX\n",myname);
		exit(255);
	}
	parm_internal_variable_prefix = av[1];
}

static void
key_internal_local_default(int ac, char **av)
{
	if(ac != 2) {
		printf("%s: Failure: Malformed INTERNAL-LOCAL-DEFAULT\n",myname);
		exit(255);
	}
	parm_internal_local_default = av[1];
}

static void
key_internal_global_commands(int ac, char **av)
{
	if(ac != 2) {
		printf("%s: Failure: Malformed INTERNAL-GLOBAL-COMMANDS\n",myname);
		exit(255);
	}
	parm_internal_global_commands = av[1];
}

static void
key_internal_local_commands(int ac, char **av)
{
	if(ac != 2) {
		printf("%s: Failure: Malformed INTERNAL-LOCAL-COMMANDS\n",myname);
		exit(255);
	}
	parm_internal_local_commands = av[1];
}

static void
key_internal_global_contexts(int ac, char **av)
{
	if(ac != 2) {
		printf("%s: Failure: Malformed INTERNAL-GLOBAL-CONTEXTS\n",myname);
		exit(255);
	}
	parm_internal_global_contexts = av[1];
}

static void
key_internal_local_contexts(int ac, char **av)
{
	if(ac != 2) {
		printf("%s: Failure: Malformed INTERNAL-LOCAL-CONTEXTS\n",myname);
		exit(255);
	}
	parm_internal_local_contexts = av[1];
}

static void
key_cmd(int ac, char **av)
{
	if(parm_cmd || parm_exec || parm_generic_cmd) {
		printf(
	"%s: Failure: more than one of CMD, EXEC, GENERIC-CMD\n",myname);
		exit(255);
	}
	parm_cmd = av;
}

static void
key_exec(int ac, char **av)
{
	if(parm_cmd || parm_exec || parm_generic_cmd) {
		printf(
	"%s: Failure: more than one of CMD, EXEC, GENERIC-CMD\n",myname);
		exit(255);
	}
	parm_exec = av;
}

static char *
Strlwr(char *s0)
{
	char *s;

	for(s = s0; *s; s++) {
		if(isupper(*s)) *s = tolower(*s);
	}
	return s0;
}


static void
key_context(int ac, char **av)
{
	char buf[1024];
	int ok;
	FILE *f;

	if(ac != 2) {
		printf("%s: Failure: Malformed CONTEXT\n",myname);
		exit(255);
	}
	Strlwr(av[1]);
	parm_context = av[1];

	ok = FALSE;

	if(parm_internal_global_contexts) {
		strcpy(buf, parm_internal_global_contexts);
		strcat(buf, "/");
		strcat(buf, parm_context);
		if((f = fopen(buf, "r"))) {
			process(f, FALSE);
			ok = TRUE;
		}
	}

	if(parm_internal_local_contexts) {
		strcpy(buf, parm_internal_local_contexts);
		strcat(buf, "/");
		strcat(buf, parm_context);
		if((f = fopen(buf, "r"))) {
			process(f, FALSE);
			ok = TRUE;
		}
	}

	if(!ok) {
		printf("%s: Error: Unknown context '%s'\n",myname, parm_context);
		exit(255);
	}
}

static void
key_dir(int ac, char **av)
{
	if(ac != 2) {
		printf("%s: Failure: malformed DIR line\n",myname);
		exit(255);
	}
	parm_dir = av[1];
}

static void
key_misc(int ac, char **av)
{
	char **pp;

	if(ac != 3) {
		printf("%s: Failure: malformed MISC line\n",myname);
		exit(255);
	}

	Strlwr(av[1]);

	if(parm_internal_registries) {
		for(pp = parm_internal_registries; *pp; pp++) {
			if(!strcmp(av[1], *pp)) goto ok;
		}
	}
	printf("%s: Warning: registry %s not recognized\n",myname, av[1]);
	fflush(stdout);

ok:
	;

#ifdef	ODT1_DISPLAY_HACK
	/* The X apps in ODT version 1 don't know how to look up */
	/* host names using DNS.  Do it for them. */
	if(!strcmp(av[1], "x")
	&& !strncmp(av[2], "DISPLAY=", 8)
	&& odt1_display_hack(av[2]+8)) return;
#endif	/* ODT1_DISPLAY_HACK */

	putenv(av[2]);
}

#ifdef	ODT1_DISPLAY_HACK
odt1_display_hack(char *s)
{
	char buf[80];
	struct hostent *he;
	char *p;
	int ok;

	ok = FALSE;
	for(p = s; *p; p++) {
		if(*p == ':') {
			if(!ok) break;
			*p = '\0';
			he = gethostbyname(s);
			*p = ':';
			if(!he) break;
			sprintf(buf, "DISPLAY=%u.%u.%u.%u%s",
				he->h_addr_list[0][0] & 0xff,
				he->h_addr_list[0][1] & 0xff,
				he->h_addr_list[0][2] & 0xff,
				he->h_addr_list[0][3] & 0xff,
				p);
			s = Strdup(buf);
			if(!s) nomem();
			putenv(s);
			return TRUE;
		}
		if(!isdigit(*p) && *p != '.') ok = TRUE;
	}
	return FALSE;
}
#endif	/* ODT1_DISPLAY_HACK */

static void
key_generic_cmd(int ac, char **av)
{
	if(parm_cmd || parm_exec || parm_generic_cmd) {
		printf(
	"%s: Failure: more than one of CMD, EXEC, GENERIC-CMD\n",myname);
		exit(255);
	}
	parm_generic_cmd = av;
}

static void
do_it(void)
{
	if(parm_dir) {
		if(chdir(parm_dir)) {
			printf("%s: Error: %s - %s\n",myname,
				parm_dir, strerror(errno));
			exit(255);
		}
	}

	if(parm_internal_variable_prefix) {
		putenv_with_prefix(parm_internal_variable_prefix,
			"CONTEXT", parm_context);
		putenv_with_prefix(parm_internal_variable_prefix,
			"LOCAL_COMMANDS", parm_internal_local_commands);
		putenv_with_prefix(parm_internal_variable_prefix,
			"GLOBAL_COMMANDS", parm_internal_global_commands);
		putenv_with_prefix(parm_internal_variable_prefix,
			"LOCAL_CONTEXTS", parm_internal_local_contexts);
		putenv_with_prefix(parm_internal_variable_prefix,
			"GLOBAL_CONTEXTS", parm_internal_global_contexts);
	}

	do_auth();

	if(parm_cmd) {
		char *shell;
		char *buf;
		int len;
		char **pa;

		if(!(shell = getenv("SHELL"))) shell = "/bin/sh";

		len = 0;
		for(pa = parm_cmd+1; *pa; pa++) {
			len += strlen(*pa) + 1;
		}

		buf = malloc(len+1);
		if(!buf) nomem();

		buf[0] = '\0';
		for(pa = parm_cmd+1; *pa; pa++) {
			strcat(buf, " ");
			strcat(buf, *pa);
		}

		printf("%s: Success: about to exec %s -c %s\n",myname,
			shell, buf+1);
		fflush(stdout);

		if(parm_detach) detach();

		execl(shell, shell, "-c", buf+1, (char *)NULL);
		printf("%s: Error: %s - %s\n",myname,
			shell, strerror(errno));
		exit(255);
	}

	if(parm_exec) {
		printf("%s: Success: about to exec %s with args\n",myname,
			parm_exec[1]);
		fflush(stdout);

		if(parm_detach) detach();

		execvp(parm_exec[1], parm_exec+2);
		printf("%s: Error: %s - %s\n",myname,
			parm_exec[0], strerror(errno));
		exit(255);
	}

	if(parm_generic_cmd) {
		char buf[1024];

		if(!parm_internal_local_commands
		&& !parm_internal_global_commands) {
			printf(
	"%s: Failure: No generic command directory!\n",myname);
			exit(255);
		}

		printf("%s: Success: about to exec generic command\n",myname);
		fflush(stdout);

		if(parm_detach) detach();

		/* Try context-specific generic commands first */
		if(parm_context) {
			if(parm_internal_local_commands) {
				strcpy(buf, parm_internal_local_commands);
				strcat(buf, "/");
				strcat(buf, parm_context);
				strcat(buf, "/");
				strcat(buf, parm_generic_cmd[1]);
				execv(buf, parm_generic_cmd+1);
			}

			if(parm_internal_global_commands) {
				strcpy(buf, parm_internal_global_commands);
				strcat(buf, "/");
				strcat(buf, parm_context);
				strcat(buf, "/");
				strcat(buf, parm_generic_cmd[1]);
				execv(buf, parm_generic_cmd+1);
			}
		}

		/* Failing that, try non-context-specific */
		if(parm_internal_local_commands) {
			strcpy(buf, parm_internal_local_commands);
			strcat(buf, "/");
			strcat(buf, parm_generic_cmd[1]);
			execv(buf, parm_generic_cmd+1);
		}

		if(parm_internal_global_commands) {
			strcpy(buf, parm_internal_global_commands);
			strcat(buf, "/");
			strcat(buf, parm_generic_cmd[1]);
			execv(buf, parm_generic_cmd+1);
		}

		printf("%s: Error: %s - %s\n",myname,
			buf, strerror(errno));
		exit(255);
	}

	printf("%s: Failure: No CMD, EXEC, or GENERIC-CMD.\n",myname);
	exit(255);
}

static void
process(FILE *f, int is_the_real_thing)
{
	int line_argc;
	char **line_argv;
	struct key *pk;

	while(1) {
		if(!get_a_line(f, &line_argc, &line_argv)) {
			if(is_the_real_thing) {
				printf(
		"%s: Failure: No blank line after request\n",myname);
				exit(255);
			}
			return;
		}

		if(line_argc == 0) {
			if(is_the_real_thing) return;
			continue;
		}

		Strlwr(line_argv[0]);
		for(pk = keys; pk->name; pk++) {
			if(!strcmp(line_argv[0], pk->name)) {
				(*pk->func)(line_argc, line_argv);
				goto ok;
			}
		}
		printf("%s: Failure: %s not recognized\n",myname, line_argv[0]);
		exit(255);
ok:
		;
	}
}

static void
key_internal_print(int ac, char **av)
{
	printf("%s: Debug:",myname);
	while(*++av) printf(" %s", *av);
	printf("\n");
}

static void
key_detach(int ac, char **av)
{
	parm_detach = TRUE;
}

static void
key_nodetach(int ac, char **av)
{
	parm_detach = FALSE;
}

static void
detach(void)
{
	/* I'm not exactly sure how you're supposed to handle stdio here */
	switch(fork()) {
	    case -1:
		printf("%s: Error: fork - %s\n",myname, strerror(errno));
		exit(255);
	    case 0:
		/* Child */
		close(0);
		close(1);
		close(2);
		dup(dup(open("/dev/null", O_RDWR)));
		return;
	    default:
		/* Parent */
		_exit(0);
	}
}

static void
putenv_with_prefix(char *prefix, char *name, char *value)
{
	char *s;

	if(!value) return;

	s = malloc(strlen(prefix) + strlen(name) + strlen(value) + 3);

	if(!s) nomem();

	sprintf(s, "%s_%s=%s", prefix, name, value);

	putenv(s);
}

static void
key_posix_umask(int ac, char **av)
{
	int i;
	char *s;

	if(ac != 2) {
		printf(
	"%s: Failure: Malformed POSIX-UMASK - wrong number of args\n",myname);
		exit(255);
	}

	i = strtol(av[1], &s, 8);

	if(*s || i < 0 || i > 0777) {
		printf(
	"%s: Failure: Malformed POSIX-UMASK - bad arg\n",myname);
		exit(255);
	}

	umask(i);
}

#ifdef NOPUTENV
/*
 * define our own putenv() if the system doesn't have one.
 * putenv(s): place s (a string of the form "NAME=value") in
 * the environment; replacing any existing NAME.  s is placed in
 * environment, so if you change s, the environment changes (like
 * putenv on a sun).  Binding removed if you putenv something else
 * called NAME.
 */
int
putenv(char *s)
{
    char *v;
    int varlen, idx;
    extern char **environ;
    char **newenv;
    static int virgin = 1; /* true while "environ" is a virgin */

    v = index(s, '=');
    if(v == 0)
	return 0; /* punt if it's not of the right form */
    varlen = (v + 1) - s;

    for (idx = 0; environ[idx] != 0; idx++) {
	if (strncmp(environ[idx], s, varlen) == 0) {
	    if(v[1] != 0) { /* true if there's a value */
		environ[idx] = s;
		return 0;
	    } else {
		do {
		    environ[idx] = environ[idx+1];
		} while(environ[++idx] != 0);
		return 0;
	    }
	}
    }
    
    /* add to environment (unless no value; then just return) */
    if(v[1] == 0)
	return 0;
    if(virgin) {
	register i;

	newenv = (char **) malloc((unsigned) ((idx + 2) * sizeof(char*)));
	if(newenv == 0)
	    return -1;
	for(i = idx-1; i >= 0; --i)
	    newenv[i] = environ[i];
	virgin = 0;     /* you're not a virgin anymore, sweety */
    } else {
	newenv = (char **) realloc((char *) environ,
				   (unsigned) ((idx + 2) * sizeof(char*)));
	if (newenv == 0)
	    return -1;
    }

    environ = newenv;
    environ[idx] = s;
    environ[idx+1] = 0;
    
    return 0;
}
#endif /* NOPUTENV */
