/*
 * $Id: shell_args.c,v 1.11 1998/12/08 17:02:48 agalat Rel $
 *
 * This file contains a function to break up the shell command arguments
 * into a string of arguments.  Because the string originates from libpwdb
 * it is possible that a PAM module will have modified the default user
 * entry to give a multi-token command. No facility is provided for
 * running such commands in the background. In such cases a command
 * wrapper should be written.
 *
 * This is intended for RADIUS type use.
 *
 * The assumptions used by this code are that a an argument list is
 * returned in the following cases:
 *
 *      1.  login is true [command is always ignored]
 *
 *           - the user has a shell entry which is used
 *           - if the user does not have a shell entry NULL is returned
 *
 *      2.  login is false. Here the shell is the first of the following:
 *
 *           - if the user's shell is a single argument this is **assumed**
 *             to be a "normal" shell, this is what is retuned. If
 *             command is not NULL, it is appended as "-c" "command"
 *             arguments. ["Good" shells, by definition, understand "-c".]
 *
 *           - if the user's shell is more than one argument or has no shell
 *             the default shell is substituted. If command is not NULL it
 *             is appended as two arguments: "-c" "command".
 *
 *           Note, DEFAULT_SHELL is defined in the parent program which
 *           includes this file.
 *
 */

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>

#include "../include/shell_args.h"

#ifdef DEBUG
#include <security/pam_misc.h>
#else
#define D(x)
#endif

#define DEFAULT_SHELL             "/bin/sh"

char * const *build_shell_args(const char *pw_shell
				      , int login, const char *command)
{
    int use_default = 1;  /* flag to signal we should use the default shell */
    const char **args=NULL;             /* array of PATH+ARGS+NULL pointers */

    D(("called."));
    if (login) {
	command = NULL;                 /* command always ignored for login */
    }

    if (pw_shell && *pw_shell != '\0') {
	char *line;
	const char *tmp, *tmpb=NULL;
	int arg_no=0,i;

	/* first find the number of arguments */
	D(("non-null shell"));
	for (tmp=pw_shell; *tmp; ++arg_no) {

	    /* skip leading spaces */
	    while (isspace(*tmp))
		++tmp;

	    if (tmpb == NULL)               /* mark beginning token */
		tmpb = tmp;
	    if (*tmp == '\0')               /* end of line with no token */
		break;

            /* skip token */
	    while (*tmp && !isspace(*tmp))
		++tmp;
	}

	/*
	 * We disallow shells:
	 *    - without a full specified path;
	 *    - when we are not logging in and the #args != 1
	 *                                         (unlikely a simple shell)
	 */

	D(("shell so far = %s, arg_no = %d", tmpb, arg_no));
	if (tmpb != NULL && tmpb[0] == '/'    /* something (full path) */
	    && ( login || arg_no == 1 )       /* login, or single arg shells */
	    ) {

	    use_default = 0;                  /* we will use this shell */
	    D(("commited to using user's shell"));
	    if (command) {
		arg_no += 2;                  /* will append "-c" "command" */
	    }

	    /* allocate an array of pointers long enough */

	    D(("building array of size %d", 2+arg_no));
	    args = (const char **) calloc(2+arg_no, sizeof(const char *));
	    if (args == NULL)
		return NULL;

	    /* get a string long enough for all the arguments */

	    D(("an array of size %d chars", 2+strlen(tmpb)
				   + ( command ? 4:0 )));
	    line = (char *) malloc(2+strlen(tmpb)
				   + ( command ? 4:0 ));
	    if (line == NULL) {
		free(args);
		return NULL;
	    }

	    /* fill array - tmpb points to start of first non-space char */

	    line[0] = '-';
	    strcpy(line+1, tmpb);

	    /* append " -c" to line? */
	    if (command) {
		strcat(line, " -c");
	    }

	    D(("complete command: %s [+] %s", line, command));

	    tmp = strtok(line, " \t");
	    D(("command path=%s", line+1));
	    args[0] = line+1;

	    if (login) {               /* standard procedure for login shell */
		D(("argv[0]=%s", line));
		args[i=1] = line;
	    } else {                 /* not a login shell -- for use with su */
		D(("argv[0]=%s", line+1));
		args[i=1] = line+1;
	    }

	    while ((tmp = strtok(NULL, " \t"))) {
		D(("adding argument %d: %s",i,tmp));
		args[++i] = tmp;
	    }
	    if (command) {
		D(("appending command [%s]", command));
		args[++i] = command;
	    }
	    D(("terminating args with NULL"));
	    args[++i] = NULL;
	    D(("list completed."));

	}
    }

    /* should we use the default shell instead of specific one? */

    if (use_default && !login) {
	int last_arg;

	D(("selecting default shell"));
	last_arg = command ? 5:3;

	args = (const char **) calloc(last_arg--, sizeof(const char *));
	if (args == NULL) {
	    return NULL;
	}
	args[1] = DEFAULT_SHELL;      /* mapped to argv[0] (NOT login shell) */
	args[0] = args[1];            /* path to program */
	if (command) {
	    args[2] = "-c";           /* should perform command and exit */
	    args[3] = command;        /* the desired command */
	}
	args[last_arg] = NULL;        /* terminate list of args */
    }

    D(("returning"));
    return (char * const *)args;               /* return argument list */
}
