/* ====================================================================
 *
 * FrontPage SUID Stub Executable
 *
 * Copyright (c) 1995-2000 Microsoft Corporation -- All Rights Reserved.
 *
 * NO WARRANTIES. Microsoft expressly disclaims any warranty for this code and
 * information. This code and information and any related documentation is
 * provided "as is" without warranty of any kind, either express or implied,
 * including, without limitation, the implied warranties or merchantability,
 * fitness for a particular purpose, or noninfringement. The entire risk
 * arising out of use or performance of this code and information remains with
 * you.
 *
 * NO LIABILITY FOR DAMAGES. In no event shall Microsoft or its suppliers be
 * liable for any damages whatsoever (including, without limitation, damages
 * for loss of business profits, business interruption, loss of business
 * information, or any other pecuniary loss) arising out of the use of or
 * inability to use this Microsoft product, even if Microsoft has been advised
 * of the possibility of such damages. Because some states/jurisdictions do not
 * allow the exclusion or limitation of liability for consequential or
 * incidental damages, the above limitation may not apply to you.
 *
 * Version 5.0.0.0
 */


/*
 * User configurable items.  We will not run the server extensions with any
 * UID/GID less than LOWEST_VALID_UID/LOWEST_VALID_GID.
 */

#if defined(RS6000)
#define _ALL_SOURCE
#endif

#if defined(RS6000) | defined(UWARE7)
int initgroups (char *, int);
#endif

#if defined(MIPS_LINUX)
#define _GNU_SOURCE
#endif
#if defined(LINUX) || defined(MIPS_LINUX)
#define LOWEST_VALID_UID 15
#else
#define LOWEST_VALID_UID 11
#endif

#if defined(HPUX) || defined(IRIX) || defined(SUNOS4)
#define LOWEST_VALID_GID 20
#else
#if defined(SCO)
#define LOWEST_VALID_GID 24
#else
#define LOWEST_VALID_GID 21   /* Solaris, AIX, Alpha, Bsdi, etc. */
#endif
#endif

#if defined(UWARE7)
#define Vstat stat32
#define Vlstat lstat32
int lstat32 (const char *, struct stat *);
int stat32 (const char *, struct stat *);
#else
#define Vstat stat
#define Vlstat lstat
#endif 

#define CLEAN_PATH "PATH=/usr/bin:/bin"

static struct SaveEnvVars
{
    const char* szVar;
    int         iLen;
} gSafeEnvVars[] =
{
    { "AUTH_TYPE=", 0 },
    { "CONTENT_LENGTH=", 0 },
    { "CONTENT_TYPE=", 0 },
    { "DATE_GMT=", 0 },
    { "DATE_LOCAL=", 0 },
    { "DOCUMENT_NAME=", 0 },
    { "DOCUMENT_PATH_INFO=", 0 },
    { "DOCUMENT_ROOT=", 0 },
    { "DOCUMENT_URI=", 0 },
    { "FILEPATH_INFO=", 0 },
#ifdef ALPHAOSF
    { "FP_USE_FLOCK=", 0 },
#endif
    { "FRONTPAGE_TEST_INSTALL=", 0 },
    { "GATEWAY_INTERFACE=", 0 },
    { "HTTP_", 0 },
    { "LAST_MODIFIED=", 0 },
    { "PATH_INFO=", 0 },
    { "PATH_TRANSLATED=", 0 },
    { "QUERY_STRING=", 0 },
    { "QUERY_STRING_UNESCAPED=", 0 },
    { "REDIRECT_QUERY_STRING=", 0 },
    { "REDIRECT_STATUS=", 0 },
    { "REDIRECT_URL=", 0 },
    { "REMOTE_ADDR=", 0 },
    { "REMOTE_HOST=", 0 },
    { "REMOTE_IDENT=", 0 },
    { "REMOTE_PORT=", 0 },
    { "REMOTE_USER=", 0 },
    { "REQUEST_METHOD=", 0 },
    { "SCRIPT_FILENAME=", 0 },
    { "SCRIPT_NAME=", 0 },
    { "SCRIPT_URI=", 0 },
    { "SCRIPT_URL=", 0 },
    { "SERVER_ADMIN=", 0 },
    { "SERVER_NAME=", 0 },
    { "SERVER_PORT=", 0 },
    { "SERVER_PROTOCOL=", 0 },
    { "SERVER_SOFTWARE=", 0 },
    { "TZ=", 0 },
    { "USER_NAME=", 0 },
    { 0, 0 }
};

/*
 * End of user configurable items
 */


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
#if !defined(bsdi) && !defined(hpux) && !defined(sun) && !defined(linux) && !defined(SCO5) && !defined(UWARE7) && !defined(FREEBSD)
#include <sys/mode.h>
#endif

#if defined(sun) || defined(bsdi) || defined(sgi) || defined(SCO5) || defined(UWARE7) || defined(FREEBSD) || defined(linux)
extern const char ** environ;
#endif
extern int errno;


#ifndef TRUE
#define TRUE 1
#endif

#ifndef FALSE
#define FALSE 0
#endif

#ifndef MAXPATHLEN
#define MAXPATHLEN 1024
#endif
#if (MAXPATHLEN < 1024)
#undef MAXPATHLEN
#define MAXPATHLEN 1024
#endif

#define KEYLEN 128                  /* Should be a multiple of sizeof(int) */


#define FPKEYDIR "/usr/local/frontpage/version5.0/apache-fp"
#define KEYFILE  "/usr/local/frontpage/version5.0/apache-fp/suidkey.%d"
#define FPDIR    "/usr/local/frontpage/version5.0/exes"

/* Legal modules */
#define SHTML    "/_vti_bin/shtml.exe"
#define FPCOUNT  "/_vti_bin/fpcount.exe"
#define AUTHOR   "/_vti_bin/_vti_aut/author.exe" 
#define ADMIN    "/_vti_bin/_vti_adm/admin.exe" 
#define ADMINCGI "/_vti_bin/_vti_adm/fpadmcgi.exe" 


/*
 * Something is not quite right - give up
 */
void die(const char *msg)
{
    char timebuf[26];
    time_t t = time(0);
    strcpy(timebuf, ctime(&t));
    timebuf[24] = '\0';
    fprintf(stderr, "[%s] %s\n", timebuf, msg);
    printf("Content-Type: text/html\n\n<HTML>*-*-* :-| :^| :-/ :-( 8-( *-*-*\n<ul>\n<li>status=1\n<li>osstatus=0\n<li>msg=FrontPage security violation.\n<li>osmsg=\n</ul>\n");
    exit(0);
}

/*
 * Remove any variable that is not known to be a standard CGI or OS
 * environment variable.  Also, sanitizes the PATH.
 */
static void CleanEnvironment() 
{
    const char** pp;
    const char** ppi;
    struct SaveEnvVars* pOkEnv;

    for (ppi = pp = environ;  *pp;  pp++)
    {
        /*
         * Inefficient linear lookup; could be improved with binary search.
         */
        for (pOkEnv = gSafeEnvVars;  pOkEnv->szVar;  pOkEnv++)
        {
            int iLen = pOkEnv->iLen;
            if (!iLen)
                pOkEnv->iLen = iLen = strlen(pOkEnv->szVar);

            if (strncmp(pOkEnv->szVar, *pp, iLen) == 0)
                break;
        }

        if (!strncmp(*pp, "PATH=", 5))
            *ppi++ = CLEAN_PATH;
        else if (pOkEnv->szVar)
            *ppi++ = *pp;
    }

    *ppi = 0;
}

int main(int argc, char **argv)
{
    struct passwd* pw = 0;
    const char* szFpUserName;
    const char* szFpExe = getenv("FPEXE");
    const char* szFpUid = getenv("FPUID");
    const char* szFpGid = getenv("FPGID");
    const char* szFpFd  = getenv("FPFD");
    const char* szFpDir = getenv("FPEXEDIR");
    char* pEnd;
    char* pDir;
    uid_t iFpUid;
    uid_t iFpGid;
    uid_t iBinUid;
    int iFpFd;
    int iKeyFd;
    int iCount;
    char szKeyFile[MAXPATHLEN];
    char szWork[MAXPATHLEN];
    char inpKey[KEYLEN];
    char refKey[KEYLEN];
    struct stat fs;
    
    /*
     * Fall back to default location for the real FrontPage executables.
     * if FPEXEDIR is not set in the environment.
     */
    if (!szFpDir)
        szFpDir = FPDIR;

    /*
     * Assure that this program was actually SUID'd to root
     */
    if (geteuid())
        /*
         * User recovery:  Make sure fpexe is setuid to root
         */
        die("FrontPage SUID Error: not running as root");

    /*
     * Assure that the user the web server runs as is a valid user
     */
    if (!getpwuid(getuid()))
        /*
         * User recovery:  Make sure that the web server user is in /etc/passwd
         */
        die("FrontPage SUID Error: invalid uid");

    /*
     * Assure that we have the proper arguments (passed in the environment)
     */
    if (!szFpExe || !szFpUid || !szFpGid || !szFpFd || !szFpDir)
        /*
         * User recovery:  Make sure fpexe is run from patched Apache server
         */
        die("FrontPage SUID Error: invalid environment arguments");

    /*
     * Validate the arguments
     */
    if (strcmp(szFpExe, SHTML) != 0   &&
        strcmp(szFpExe, FPCOUNT) != 0 &&
        strcmp(szFpExe, AUTHOR) != 0  &&
        strcmp(szFpExe, ADMIN) != 0  &&
        strcmp(szFpExe, ADMINCGI) != 0)
        /*
         * User recovery:  Make sure fpexe is only invoked to run FrontPage
         * server extension programs.
         */
        die("FrontPage SUID Error: target program violation");

    if (strlen(szFpExe) + strlen(szFpDir) + 1 > MAXPATHLEN)
        die("FrontPage SUID Error: path too long");
    strcpy(szWork, szFpDir);
    strcat(szWork, szFpExe);

    iFpUid = strtol(szFpUid, &pEnd, 10);
    if (!pEnd || *pEnd)
        iFpUid = 0;
    if (iFpUid < LOWEST_VALID_UID || !(pw = getpwuid(iFpUid)))
        /*
         * User recovery:  Make sure FrontPage user ids are above minimum
         */
        die("FrontPage SUID Error: invalid target uid");
    szFpUserName = strdup(pw->pw_name);

    iFpGid = strtol(szFpGid, &pEnd, 10);
    if (!pEnd || *pEnd)
        iFpGid = 0;
    if (iFpGid < LOWEST_VALID_GID || !getgrgid(iFpGid))
        /*
         * User recovery:  Make sure FrontPage group ids are above minimum
         */
        die("FrontPage SUID Error: invalid target gid");

    iFpFd = strtol(szFpFd, &pEnd, 10);
    if (!pEnd || *pEnd)
        iFpFd = -1;
    if (iFpFd < 0)
        /*
         * User recovery:  Make sure fpexe is run from patched Apache server
         */
        die("FrontPage SUID Error: invalid key file descriptor");

    /*
     * Read the key from our server.  And, while we're still root and have
     * access, read the key from the master key file.  Verify the key matches.
     */
    if (Vlstat(FPKEYDIR, &fs) == -1 ||
        (fs.st_mode & (S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) || fs.st_uid ||
        !(S_ISDIR(fs.st_mode)))
        /*
         * User recovery is: set directory to be owned by by root with
         * permissions rwx--x--x.
         */
        die("FrontPage SUID Error: key file directory is insecure");

#if defined(sun) && !defined(__SVR4)
    sprintf(szKeyFile, KEYFILE, (int)getpgrp(0));
#else
    sprintf(szKeyFile, KEYFILE, (int)getpgrp());
#endif
    if (Vstat(szKeyFile, &fs) == -1 ||
        (fs.st_mode & (S_IRWXG | S_IRWXO)) || fs.st_uid)
        /*
         * User recovery is:  Make sure the key file is properly protected
         * (owned by root, permissions r**------), restart patched Apache
         * server.
         */
        die("FrontPage SUID Error: key file security violation");
    
    iKeyFd = open(szKeyFile, O_RDONLY);
    if (iKeyFd < 0)
        /*
         * User recovery is:  Make sure fpexe is run from patched Apache
         * server, restart the patched Apache server.
         */
        die("FrontPage SUID Error: could not open key file" );
    iCount = read(iKeyFd, refKey, sizeof(refKey));
    close(iKeyFd);
    if (iCount != sizeof(refKey))
        /*
         * User recovery is:  Make sure fpexe is run from patched Apache
         * server, restart the patched Apache server.
         */
        die("FrontPage SUID Error: could not read valid key from key file");

    iCount = read(iFpFd, inpKey, sizeof(inpKey));
    close(iFpFd);
    if (iCount != sizeof(inpKey))
        /*
         * User recovery is:  Make sure fpexe is run from patched Apache server
         */
        die("FrontPage SUID Error: could not read valid input key");

    if (memcmp(inpKey, refKey, sizeof(refKey)) != 0)
        /*
         * User recovery is:  Make sure fpexe is run from patched Apache server
         */
        die("FrontPage SUID Error: key security violation");

    /*
     * Change user and group IDs to be the indicated user
     */
    if (setgid(iFpGid) == -1 || initgroups(szFpUserName, iFpGid) == -1)
        /*
         * User recovery:  Make sure user is properly registered in 
         * /etc/passwd and /etc/group.
         */
        die("FrontPage SUID Error: setgid() failed");
    
    if (setuid(iFpUid) == -1)
        /*
         * User recovery:  Make sure user is properly registered in
         * /etc/passwd.
         */
        die("FrontPage SUID Error: setuid() failed");

    /*
     * Validate the target directory.
     */
    iBinUid = 0;
    if (pw = getpwnam("bin"))
        iBinUid = pw->pw_uid;

    pDir = strrchr(szWork, '/');
    *pDir = 0;
    if (Vlstat(szWork, &fs) == -1 || (fs.st_mode & (S_IWGRP | S_IWOTH)) ||
                                    (fs.st_uid != iBinUid && fs.st_uid != 0) ||
                                    !(S_ISDIR(fs.st_mode)))
        /*
         * User recovery is: make sure FrontPage exe programs are available,
         * set directory to be owned by bin or root and have permissions
         * rwx*-x*-x.
         */
        die("FrontPage SUID Error: target directory not found or insecure");

    *pDir = '/';

    /*
     * Validate the target program
     */
    if (Vstat(szWork, &fs) == -1 || ((fs.st_mode & (S_IWGRP | S_IWOTH)) ||
                                    (fs.st_mode & (S_ISUID | S_ISGID)) ||
                                    (fs.st_uid != iBinUid && fs.st_uid != 0)))
        /*
         * User recovery is: make sure FrontPage exe programs are available,
         * set programs to be owned by bin or root and have permissions
         * rwx*-x*-x.
         */
        die("FrontPage SUID Error: target program not found or insecure");

    *pDir = '/';

    /*
     * Make sure the environment contains no unsafe values.
     */
    CleanEnvironment();

    /*
     * Run the specified program.
     */
    argv[0] = szWork;
    umask(022);
    execv(argv[0], argv);

    /*
     * We should never get here.  Exit with error.
     */
    return (1);
}
