/************************************************************************
 *
 * Ultrix:
 * # cc -o /usr/bin/pcx_server pcx_server.c -ldnet
 * # ncp
 * ncp> def object 'PCX$SERVER' number 0 file /usr/bin/pcx_server
 * ncp> set object 'PCX$SERVER' number 0 file /usr/bin/pcx_server
 *
 * OSF/1:
 * # cc -o /usr/bin/pcx_server pcx_server.c -ldnet
 * # ncl
 * ncl> create session control application pcx
 * ncl> set session control application pcx image name = /usr/bin/pcx_server
 * ncl> set session control application pcx addresses = { name=PCX$SERVER }
 *
 * Sun DNI:
 * # cc -D DNI pcx_server.c -o pcxserver dnierror.c
 * to register: 
 *     1. copy pcx_server to /usr/sunlink/dni/pcx_server
 *     2.  add the following line to /etc/sunlink/dni/dniserver.reg
 *		0	PCX$SERVER	/usr/sunlink/dni/pcxserver
 *     3. Stop and restart the dniserver process
 *
/************************************************************************
*
* MODULE:
*    pcx_server.c
*
* VERSION:
*    2.0
*
* ABSTRACT:
*     This program is installed as the DECnet object PCX$SERVER and
*     is used by PC DECwindows and eXcursion to remotely start Ultrix
*     applications over a DECnet network.  The command passed to
*     this program has one of the following formats:
*     	<node>,<command> <arguments>
*     	3,<transport>,<node>,<command> <arguments>
*     	4,<server#>,<screen#>,<transport>,<node>,<command> <arguments>
*     <node> = server's DECnet node address TCP/IP address
*     <command> = executable name
*     <arguments> = command line arguments for <command>
*     <transport> = "DECnet" (otherwise TCP/IP is assumed)
*     <server#> = server number on server's node
*     <screen#> = screen number on server
*
* AUTHOR:
*     Michael J. Pfeffer (parts from tell.c - ULTRIX)
*
* CREATED:
*     31-MAR-1988
*
* CHANGE HISTORY:
*
*  J Wasser	15-Jul-1992	Complete rewrite for V2.0
*  J Peterson	22-Oct-1992	added support for the tilda metacharcter
*  S Souza      23-Sep-1993  	added support for Sunlink DNI when compiled 
*				with the "-D DNI" option.
*
************************************************************************/
/*
* Copyright (C) 1985, 1988 by
* Digital Equipment Corporation, Maynard, Mass.
*
* This software is furnished under a license and may be used and copied
* only  in  accordance  with  the  terms  of such  license and with the
* inclusion of the above copyright notice. This software or  any  other
* copies thereof may not be provided or otherwise made available to any
* other person. No title to and ownership of  the  software  is  hereby
* transferred.
*
* The information in this software is subject to change without  notice
* and	should	not be	construed  as  a commitment by Digital Equipment
* Corporation.
*
* Digital assumes no responsibility for the use or  reliability  of its
* software on equipment which is not supplied by Digital.
*
*/

#include <stdio.h>			/* standard i/o defintions */
#include <strings.h>			/* string library definitions */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <pwd.h>

#ifdef DNI
/* Included for SUN workstation support over DECnet Anthony E. Valasek */
#include <fcntl.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <netdni/dni.h>
#endif /* DNI */

#define BUFF_SIZE 1024			/* buffer size */

/* Argument list for spawned shell */
/* NOTE: argv[0] must start with '-' to trigger .profile processing */
/* argv[2] will be set to "(<command line>)" */
char *Child_argv[] = {"-sh", "-c", NULL, NULL} ;

error_exit(message)
    char *message;
{
#ifdef DNI
    write(2, message, strlen(message));
#else
    write(0, message, strlen(message));
#endif
    exit(1);
}

main(argc, argv, arge)
    int argc;
    char *argv[];
    char *arge[];
{
    char **environment;
    int entry_count;

    int display_entry = -1;
    char *path = NULL;
    char *shell = "/bin/sh";
    char buff[BUFF_SIZE+1];		/* network read buffer */

#ifdef DNI 			/* Sunlink DNI variables */
    /* 
     * Under Sunlink DNI we need to have some additional variable 
     * and a logical link file descriptors defined.
     */
    int ll; 
    static SessionData sd = {0, {0, ""}};
#endif 

    int server = 0;
    int screen = 0;
    char transport[BUFF_SIZE];	/* buffer for transport name*/
    char node[BUFF_SIZE];           /* buffer for node address */
    char *command;                  /* pointer into buff at command line */ 
    char application[BUFF_SIZE];    /* buffer for app name from command */
    char shell_arg[BUFF_SIZE];	/* buffer for command line in parens */
    char filespec[BUFF_SIZE];	/* buffer for parsing the path */

    char display[100];	/* buffer for DISPLAY environment variable */

    char *cp;
    int count;
    struct stat buf;
    struct passwd *pwent;
    int retval;

    int fd;		/* File Descriptor for /dev/null */

    /* Don't run under set-uid */
    seteuid(getuid());	

    /* Determine the number of entries in the current environment */
    for (entry_count = 0; arge[entry_count] != NULL; entry_count++)
        ; /* This line intentionally left blank */

    /* Allocate room for n+1 entries and a terminator */
    environment = (char **) malloc((entry_count+3) * sizeof (char *));
    if (environment == (char **)NULL)
		error_exit("Out of memory");

    /* Copy the environment and look for DISPLAY, PATH and SHELL */

    for (entry_count = 0; arge[entry_count] != NULL; entry_count++)
    {
        environment[entry_count] = arge[entry_count];
        if (strncmp(environment[entry_count], "DISPLAY=", 8) == 0)
            display_entry = entry_count;
        if (strncmp(environment[entry_count], "PATH=", 5) == 0)
            path = arge[entry_count] + 5;
        if (strncmp(environment[entry_count], "SHELL=", 6) == 0)
            shell = arge[entry_count] + 6;
    }
    environment[entry_count] = NULL;	/* Terminator */

    /*
    ** Get a line from the network connection. 
    */
	
#ifdef DNI
    /* 
     * Under Sunlink dni, First we must accept the logical link.
     * The file  The ascii description of the logical link file 
     * descriptor is passed as argv[1] by the dniserver.
     */

    ll = atoi(argv[1]);
    if (ioctl(ll, SES_ACCEPT, &sd) < 0)
    {
        dnierror("Ioctl Accept failed");
        exit(1);
    }
#endif

    count = read(0, buff, sizeof(buff));
    if (count == 0)
        error_exit("No data read from network\n");
    buff[count] = '\0';

    /* Parse the line from the network */
    if (buff[0] == '4' && buff[1] == ',')
    {
        /* Format 4 */
        /* 4,<server#>,<screen#>,<transport>,<node>,<command> <arguments> */
        count = sscanf(buff, "4 , %d , %d , %[^,] , %[^,] , ",
			&server, &screen, transport, node);
        if (count != 4)
			error_exit("Bad type 4 format");

        command = NULL;
        for (cp = buff, count = 0; *cp != '\0'; cp++)
        {
            if (*cp == ',')
            {
                count++;
                if (count == 5)
                {
                    command = cp+1;
                    break;
                }
            }
        }
        if (command == NULL)
            error_exit("Bad type 4 format: too few commas");
    }
    else if (buff[0] == '3' && buff[1] == ',')
    {
        /* Format 3 */
        /* 3,<transport>,<node>,<command> <arguments> */
        count = sscanf(buff, "3 , %[^,] , %[^,] , ",
			transport, node);
        if (count != 2)
			error_exit("Bad type 3 format");
        command = NULL;
        for (cp = buff, count = 0; *cp != '\0'; cp++)
        {
            if (*cp == ',')
            {
                count++;
                if (count == 3)
                {
                    command = cp+1;
                    break;
                }
            }
        }
        if (command == NULL)
            error_exit("Bad type 3 format: too few commas");
    }
    else
    {
        /* Format 2: no format specifier */
        /* <node>,<command> <arguments> */
        count = sscanf(buff, "%[^,] , ", node);
        strcpy(transport, "DECNET");
        if (count != 1)
            error_exit("Bad type 2 format");
        command = NULL;
        for (cp = buff, count = 0; *cp != '\0'; cp++)
        {
            if (*cp == ',')
            {
                command = cp+1;
                break;
            }
        }
        if (command == NULL)
            error_exit("Bad type 2 format: no commas");
    }

    if (strcmp(transport,"DECNET") == 0)
    {
        /* Use DECnet transport (two colons after node) */
        int area, number, address;
        /* Convert DECnet address from area.number to name */
        count = sscanf(node,"%d.%d", &area, &number);
        if (count != 2)
			error_exit("Bad DECnet address format");
        address = (area * 1024) + number;

#ifdef DNI
        /*
         * Sunlink DNI will work with display set to area.node::0.0
         * but not with address::0  (Where address is defined as above)
         */
        sprintf(display, "DISPLAY=%d.%d::%d.%d",area,number,server,screen);
#else
        sprintf(display, "DISPLAY=%d::%d.%d", address, server, screen);
#endif
    }
    else
    {
        /* Assume TCP/IP (one colon after node) */
        sprintf(display, "DISPLAY=%s:%d.%d", node, server, screen);
    }

    /* Set display environment variable to point to the server & screen */
    if (display_entry != -1)
    {
        environment[display_entry] = display;
    }
    else
    {
        environment[entry_count++] = display;
        environment[entry_count] = NULL;
    }

    /* The arguemnt tot he shell -c switch is a command line in parens */
    strcpy(shell_arg, "(");
    strcat(shell_arg, command);
    strcat(shell_arg, ")");
    Child_argv[2] = shell_arg;	/* application command line */

    /* Copy characters up to the first blank to application */
    sscanf(command, "%s", application);

    if ((application[0] == '~') && (application[1] == '/'))
    {
        pwent = getpwuid(getuid());
        if (pwent == NULL)
        {
            retval == NULL;
        }
        else
        {
            strcpy(filespec, pwent->pw_dir);
            strcat(filespec, &application[1]);
            retval = stat(filespec, &buf);
        }
    }
    else
    if (application[0] == '~')
    {
        char *ptr;
        ptr = index(application, '/');
        if (ptr != NULL)
			*ptr = '\0';
        pwent = getpwnam(&application[1]);
        if ((ptr == NULL) || (pwent == NULL))
        {
            retval == NULL;
        }
        else
        {
            strcpy(filespec, pwent->pw_dir);
            *ptr = '/';
            strcat(filespec, ptr);
            retval = stat(filespec, &buf);
        }
    }
    else if ((application[0] == '/') || (path == NULL))
    {
        retval = stat(application, &buf);
    }
    else
    {
        /* Search the path */
        cp = filespec;
        while (*path != '\0')
        {
            /* Copy a path entry */
            while ((*path != ':') && (*path != '\0'))
                *cp++ = *path++;
            *cp = '\0';

            /* Append the filename */
            strcat(filespec, "/");
            strcat(filespec, application);

            /* Check for the existance of the file */
            retval = stat(filespec, &buf);
            if (retval == 0)
                break;		/* Found it! */

            /* Not there, try next entry */
            if (*path == ':')
            {
                cp = filespec;
                path++;
            }
        }
    }

    if ((retval == 0) && ((S_IEXEC&buf.st_mode) == S_IEXEC))
        write(0, "OK", 2); /* File found */
    else
        error_exit("File not found");

    /*
    ** Direct stdin, stdout and stderr to /dev/null so programs will
    ** not get errors because we have closed the network link.
    */
    fd = open("/dev/null", O_RDONLY|O_NDELAY);
    dup2(fd, 0);	/* Replace stdin */
    fd = open("/dev/null", O_WRONLY|O_NDELAY);
    dup2(fd, 1);	/* Replace stdout */
    fd = open("/dev/null", O_WRONLY|O_NDELAY);
    dup2(fd, 2);	/* Replace stderr */

    /*
    ** start up a shell, to execute the command
    */
#ifdef DNI /* Needed for posix compliance on SunOS 4.1.X systems. */
    if (setsid() == -1) 
        error_exit("Error setting session leader");
#endif
    execve(shell, Child_argv, environment);
    exit(0);
}
