/* Copyright 1999-2004 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Portions of this code are under this license:
 *
 * Copyright (c) 1980, 1991 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "httpd.h"

/*---------------------------------------------------------------*/

#ifdef NEED_HASHBANG_EMUL

#undef execle
#undef execve

static const char **hashbang(const char *filename, char * const *argv);


/* Historically, a list of arguments on the stack was often treated as
 * being equivalent to an array (since they already were "contiguous"
 * on the stack, and the arguments were pushed in the correct order).
 * On today's processors, this is not necessarily equivalent, because
 * often arguments are padded or passed partially in registers,
 * or the stack direction is backwards.
 * To be on the safe side, we copy the argument list to our own
 * local argv[] array. The va_arg logic makes sure we do the right thing.
 * XXX: malloc() is used because we expect to be overlaid soon.
 */
int ap_execle(const char *filename, const char *argv0, ...)
{
    va_list adummy;
    char **envp;
    char **argv;
    int argc, ret;

    /* First pass: Count arguments on stack */
    va_start(adummy, argv0);
    for (argc = 1; va_arg(adummy, char *) != NULL; ++argc) {
	continue;
    }
    va_end(adummy);

    if ((argv = (char **) malloc((argc + 2) * sizeof(*argv))) == NULL) {
	fprintf(stderr, "Ouch!  Out of memory in ap_execle()!\n");
	return -1;
    }

    /* Pass two --- copy the argument strings into the result space */
    va_start(adummy, argv0);
    argv[0] = (char *)argv0;
    for (argc = 1; (argv[argc] = va_arg(adummy, char *)) != NULL; ++argc) {
	continue;
    }
    envp = va_arg(adummy, char **);
    va_end(adummy);

    ret = ap_execve(filename, argv, envp);
    free(argv);

    return ret;
}

/* Count number of entries in vector "args", including the trailing NULL entry
 */
static int
count_args(char * const *args)
{
    int i;
    for (i = 0; args[i] != NULL; ++i) {
	continue;
    }
    return i+1;
}

/* Emulate the execve call, respecting a #!/interpreter line if present.
 * On "real" unixes, the kernel does this.
 * We have to fiddle with the argv array to make it work on platforms
 * which don't support the "hashbang" interpreter line by default.
 */
int ap_execve(const char *filename, char * const argv[],
	      char * const envp[])
{
    char **script_argv;
    extern char **environ;

    if (envp == NULL) {
	envp = (char * const *) environ;
    }

    /* Try to execute the file directly first: */
    execve(filename, argv, envp);

    /* Still with us? Then something went seriously wrong.
     * From the (linux) man page:
     * EACCES The file is not a regular file.
     * EACCES Execute permission is denied for the file.
     * EACCES Search  permission  is denied on a component of the path prefix.
     * EPERM  The file system is mounted noexec.
     * EPERM  The file system is mounted nosuid and the file  has an SUID
     *        or SGID bit set.
     * E2BIG  The argument list is too big.
     * ENOEXEC The magic number in the file is incorrect.
     * EFAULT filename  points  outside  your  accessible address space.
     * ENAMETOOLONG filename is too long.
     * ENOENT The file does not exist.
     * ENOMEM Insufficient kernel memory was available.
     * ENOTDIR A component of the path prefix is not a  directory.
     * ELOOP  filename contains a circular reference (i.e., via a symbolic link)
     */

    if (errno == ENOEXEC) {
	/* Probably a script.
	 * Have a look; if there's a "#!" header then try to emulate
	 * the feature found in all modern OS's:
	 * Interpret the line following the #! as a command line
	 * in shell style.
	 */
	if ((script_argv = (char **)hashbang(filename, argv)) != NULL) {

	    /* new filename is the interpreter to call */
	    filename = script_argv[0];

	    /* Restore argv[0] as on entry */
	    if (argv[0] != NULL) {
		script_argv[0] = argv[0];
	    }

	    execve(filename, script_argv, envp);

	    free(script_argv);
	}
	/*
	 * Script doesn't start with a hashbang line!
	 * So, try to have the default shell execute it.
	 * For this, the size of argv must be increased by one
	 * entry: the shell's name. The remaining args are appended.
	 */
	else {
	    int i = count_args(argv) + 1;   /* +1 for leading SHELL_PATH */

	    if ((script_argv = malloc(sizeof(*script_argv) * i)) == NULL) {
		fprintf(stderr, "Ouch!  Out of memory in ap_execve()!\n");
		return -1;
	    }

	    script_argv[0] = SHELL_PATH;

	    while (i > 0) {
		script_argv[i] = argv[i-1];
		--i;
	    }

	    execve(SHELL_PATH, script_argv, envp);

	    free(script_argv);
	}
    }
    return -1;
}

/*---------------------------------------------------------------*/

/*
 * From: peter@zeus.dialix.oz.au (Peter Wemm)
 * (taken from tcsh)
 * If exec() fails look first for a #! [word] [word] ....
 * If it is, splice the header into the argument list and retry.
 * Return value: the original argv array (sans argv[0]), with the
 * script's argument list prepended.
 * XXX: malloc() is used so that everything can be free()ed after a failure.
 */
#define HACKBUFSZ 1024		/* Max chars in #! vector */
#define HACKVECSZ 128		/* Max words in #! vector */
static const char **hashbang(const char *filename, char * const *argv)
{
    char lbuf[HACKBUFSZ];
    char *sargv[HACKVECSZ];
    const char **newargv;
    char *p, *ws;
    int fd;
    int sargc = 0;
    int i, j;
#ifdef WIN32
    int fw = 0;			/* found at least one word */
    int first_word = 0;
#endif /* WIN32 */

    if ((fd = open(filename, O_RDONLY)) == -1) {
	return NULL;
    }

    if (read(fd, (char *) lbuf, 2) != 2
	|| lbuf[0] != '#' || lbuf[1] != '!'
	|| read(fd, (char *) lbuf, HACKBUFSZ) <= 0) {
	close(fd);
	return NULL;
    }

    close(fd);

    ws = NULL;			/* word started = 0 */

    for (p = lbuf; p < &lbuf[HACKBUFSZ];) {
	switch (*p) {
	case ' ':
	case '\t':
#ifdef NEW_CRLF
	case '\r':
#endif /*NEW_CRLF */
	    if (ws) {		/* a blank after a word.. save it */
		*p = '\0';
#ifndef WIN32
		if (sargc < HACKVECSZ - 1) {
		    sargv[sargc++] = ws;
		}
		ws = NULL;
#else /* WIN32 */
		if (sargc < HACKVECSZ - 1) {
		    sargv[sargc] = first_word ? NULL : hb_subst(ws);
		    if (sargv[sargc] == NULL) {
			sargv[sargc] = ws;
		    }
		    sargc++;
		}
		ws = NULL;
		fw = 1;
		first_word = 1;
#endif /* WIN32 */
	    }
	    p++;
	    continue;

	case '\0':		/* Whoa!! what the hell happened */
	    return NULL;

	case '\n':		/* The end of the line. */
	    if (
#ifdef WIN32
		   fw ||
#endif /* WIN32 */
		   ws) {	/* terminate the last word */
		*p = '\0';
#ifndef WIN32
		if (sargc < HACKVECSZ - 1) {
		    sargv[sargc++] = ws;
		}
#else /* WIN32 */
		if (sargc < HACKVECSZ - 1) {	/* deal with the 1-word case */
		    sargv[sargc] = first_word ? NULL : hb_subst(ws);
		    if (sargv[sargc] == NULL) {
			sargv[sargc] = ws;
		    }
		    sargc++;
		}
#endif /* !WIN32 */
		sargv[sargc] = NULL;
	    }
	    /* Count number of entries in the old argv vector */
	    for (i = 0; argv[i] != NULL; ++i) {
		continue;
	    }
	    ++i;

	    newargv = (const char **) malloc((p - lbuf + 1)
                      + (i + sargc + 1) * sizeof(*newargv));
	    if (newargv == NULL) {
		fprintf(stderr, "Ouch!  Out of memory in hashbang()!\n");
		return NULL;
	    }
	    ws = &((char *) newargv)[(i + sargc + 1) * sizeof(*newargv)];

	    /* Copy entries to allocated memory */
	    for (j = 0; j < sargc; ++j) {
		newargv[j] = strcpy(ws, sargv[j]);
		ws += strlen(ws) + 1;	/* skip trailing '\0' */
	    }
	    newargv[sargc] = filename;

	    /* Append the old array. The old argv[0] is skipped. */
	    if (i > 1) {
		memcpy(&newargv[sargc + 1], &argv[1],
                       (i - 1) * sizeof(*newargv));
	    }
	
	    newargv[sargc + i] = NULL;

	    ws = NULL;

	    return newargv;

	default:
	    if (!ws) {		/* Start a new word? */
		ws = p;
	    }
	    p++;
	    break;
	}
    }
    return NULL;
}
#else
extern void ap_execve_is_not_here(void);
void ap_execve_is_not_here(void) {}
#endif /* NEED_HASHBANG_EMUL */
