/* ==================================================================== * The Apache Software License, Version 1.1 * * Copyright (c) 2000-2002 The Apache Software Foundation. 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. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Apache" and "Apache Software Foundation" must * not be used to endorse or promote products derived from this * software without prior written permission. For written * permission, please contact apache@apache.org. * * 5. Products derived from this software may not be called "Apache", * nor may "Apache" appear in their name, without prior written * permission of the Apache Software Foundation. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 APACHE SOFTWARE FOUNDATION OR * ITS 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. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * . */ /* * suexec.c -- "Wrapper" support program for suEXEC behaviour for Apache * *********************************************************************** * * NOTE! : DO NOT edit this code!!! Unless you know what you are doing, * editing this code might open up your system in unexpected * ways to would-be crackers. Every precaution has been taken * to make this code as safe as possible; alter it at your own * risk. * *********************************************************************** * * */ #include "ap_config.h" #include #include #include #include #include #if APR_HAVE_UNISTD_H #include #endif #include #include #include #include "suexec.h" #ifdef HAVE_PWD_H #include #endif #ifdef HAVE_GRP_H #include #endif /* *********************************************************************** * There is no initgroups() in QNX, so I believe this is safe :-) * Use cc -osuexec -3 -O -mf -DQNX suexec.c to compile. * * May 17, 1997. * Igor N. Kovalenko -- infoh@mail.wplus.net *********************************************************************** */ #if defined(NEED_INITGROUPS) int initgroups(const char *name, gid_t basegid) { /* QNX and MPE do not appear to support supplementary groups. */ return 0; } #endif #if defined(PATH_MAX) #define AP_MAXPATH PATH_MAX #elif defined(MAXPATHLEN) #define AP_MAXPATH MAXPATHLEN #else #define AP_MAXPATH 8192 #endif #define AP_ENVBUF 256 extern char **environ; static FILE *log = NULL; char *safe_env_lst[] = { "AUTH_TYPE", "CONTENT_LENGTH", "CONTENT_TYPE", "DATE_GMT", "DATE_LOCAL", "DOCUMENT_NAME", "DOCUMENT_PATH_INFO", "DOCUMENT_ROOT", "DOCUMENT_URI", "FILEPATH_INFO", "GATEWAY_INTERFACE", "HTTPS", "LAST_MODIFIED", "PATH_INFO", "PATH_TRANSLATED", "QUERY_STRING", "QUERY_STRING_UNESCAPED", "REMOTE_ADDR", "REMOTE_HOST", "REMOTE_IDENT", "REMOTE_PORT", "REMOTE_USER", "REDIRECT_QUERY_STRING", "REDIRECT_STATUS", "REDIRECT_URL", "REQUEST_METHOD", "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SCRIPT_URI", "SCRIPT_URL", "SERVER_ADMIN", "SERVER_NAME", "SERVER_ADDR", "SERVER_PORT", "SERVER_PROTOCOL", "SERVER_SOFTWARE", "UNIQUE_ID", "USER_NAME", "TZ", NULL }; static void err_output(const char *fmt, va_list ap) { #ifdef AP_LOG_EXEC time_t timevar; struct tm *lt; if (!log) { if ((log = fopen(AP_LOG_EXEC, "a")) == NULL) { fprintf(stderr, "failed to open log file\n"); perror("fopen"); exit(1); } } time(&timevar); lt = localtime(&timevar); fprintf(log, "[%d-%.2d-%.2d %.2d:%.2d:%.2d]: ", lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday, lt->tm_hour, lt->tm_min, lt->tm_sec); vfprintf(log, fmt, ap); fflush(log); #endif /* AP_LOG_EXEC */ return; } static void log_err(const char *fmt,...) { #ifdef AP_LOG_EXEC va_list ap; va_start(ap, fmt); err_output(fmt, ap); va_end(ap); #endif /* AP_LOG_EXEC */ return; } static void clean_env(void) { char pathbuf[512]; char **cleanenv; char **ep; int cidx = 0; int idx; if ((cleanenv = (char **) calloc(AP_ENVBUF, sizeof(char *))) == NULL) { log_err("failed to malloc memory for environment\n"); exit(120); } sprintf(pathbuf, "PATH=%s", AP_SAFE_PATH); cleanenv[cidx] = strdup(pathbuf); cidx++; for (ep = environ; *ep && cidx < AP_ENVBUF-1; ep++) { if (!strncmp(*ep, "HTTP_", 5) || !strncmp(*ep, "SSL_", 4)) { cleanenv[cidx] = *ep; cidx++; } else { for (idx = 0; safe_env_lst[idx]; idx++) { if (!strncmp(*ep, safe_env_lst[idx], strlen(safe_env_lst[idx]))) { cleanenv[cidx] = *ep; cidx++; break; } } } } cleanenv[cidx] = NULL; environ = cleanenv; } int main(int argc, char *argv[]) { int userdir = 0; /* ~userdir flag */ uid_t uid; /* user information */ gid_t gid; /* target group placeholder */ char *target_uname; /* target user name */ char *target_gname; /* target group name */ char *target_homedir; /* target home directory */ char *actual_uname; /* actual user name */ char *actual_gname; /* actual group name */ char *prog; /* name of this program */ char *cmd; /* command to be executed */ char cwd[AP_MAXPATH]; /* current working directory */ char dwd[AP_MAXPATH]; /* docroot working directory */ struct passwd *pw; /* password entry holder */ struct group *gr; /* group entry holder */ struct stat dir_info; /* directory info holder */ struct stat prg_info; /* program info holder */ prog = argv[0]; /* * Check existence/validity of the UID of the user * running this program. Error out if invalid. */ uid = getuid(); if ((pw = getpwuid(uid)) == NULL) { log_err("crit: invalid uid: (%ld)\n", uid); exit(102); } /* * See if this is a 'how were you compiled' request, and * comply if so. */ if ((argc > 1) && (! strcmp(argv[1], "-V")) && ((uid == 0) #ifdef _OSD_POSIX /* User name comparisons are case insensitive on BS2000/OSD */ || (! strcasecmp(AP_HTTPD_USER, pw->pw_name))) #else /* _OSD_POSIX */ || (! strcmp(AP_HTTPD_USER, pw->pw_name))) #endif /* _OSD_POSIX */ ) { #ifdef AP_DOC_ROOT fprintf(stderr, " -D AP_DOC_ROOT=\"%s\"\n", AP_DOC_ROOT); #endif #ifdef AP_GID_MIN fprintf(stderr, " -D AP_GID_MIN=%d\n", AP_GID_MIN); #endif #ifdef AP_HTTPD_USER fprintf(stderr, " -D AP_HTTPD_USER=\"%s\"\n", AP_HTTPD_USER); #endif #ifdef AP_LOG_EXEC fprintf(stderr, " -D AP_LOG_EXEC=\"%s\"\n", AP_LOG_EXEC); #endif #ifdef AP_SAFE_PATH fprintf(stderr, " -D AP_SAFE_PATH=\"%s\"\n", AP_SAFE_PATH); #endif #ifdef AP_SUEXEC_UMASK fprintf(stderr, " -D AP_SUEXEC_UMASK=%03o\n", AP_SUEXEC_UMASK); #endif #ifdef AP_UID_MIN fprintf(stderr, " -D AP_UID_MIN=%d\n", AP_UID_MIN); #endif #ifdef AP_USERDIR_SUFFIX fprintf(stderr, " -D AP_USERDIR_SUFFIX=\"%s\"\n", AP_USERDIR_SUFFIX); #endif exit(0); } /* * If there are a proper number of arguments, set * all of them to variables. Otherwise, error out. */ if (argc < 4) { log_err("too few arguments\n"); exit(101); } target_uname = argv[1]; target_gname = argv[2]; cmd = argv[3]; /* * Check to see if the user running this program * is the user allowed to do so as defined in * suexec.h. If not the allowed user, error out. */ #ifdef _OSD_POSIX /* User name comparisons are case insensitive on BS2000/OSD */ if (strcasecmp(AP_HTTPD_USER, pw->pw_name)) { log_err("user mismatch (%s instead of %s)\n", pw->pw_name, AP_HTTPD_USER); exit(103); } #else /*_OSD_POSIX*/ if (strcmp(AP_HTTPD_USER, pw->pw_name)) { log_err("user mismatch (%s instead of %s)\n", pw->pw_name, AP_HTTPD_USER); exit(103); } #endif /*_OSD_POSIX*/ /* * Check for a leading '/' (absolute path) in the command to be executed, * or attempts to back up out of the current directory, * to protect against attacks. If any are * found, error out. Naughty naughty crackers. */ if ((cmd[0] == '/') || (!strncmp(cmd, "../", 3)) || (strstr(cmd, "/../") != NULL)) { log_err("invalid command (%s)\n", cmd); exit(104); } /* * Check to see if this is a ~userdir request. If * so, set the flag, and remove the '~' from the * target username. */ if (!strncmp("~", target_uname, 1)) { target_uname++; userdir = 1; } /* * Error out if the target username is invalid. */ if (strspn(target_uname, "1234567890") != strlen(target_uname)) { if ((pw = getpwnam(target_uname)) == NULL) { log_err("invalid target user name: (%s)\n", target_uname); exit(105); } } else { if ((pw = getpwuid(atoi(target_uname))) == NULL) { log_err("invalid target user id: (%s)\n", target_uname); exit(121); } } /* * Error out if the target group name is invalid. */ if (strspn(target_gname, "1234567890") != strlen(target_gname)) { if ((gr = getgrnam(target_gname)) == NULL) { log_err("invalid target group name: (%s)\n", target_gname); exit(106); } gid = gr->gr_gid; actual_gname = strdup(gr->gr_name); } else { gid = atoi(target_gname); actual_gname = strdup(target_gname); } #ifdef _OSD_POSIX /* * Initialize BS2000 user environment */ { pid_t pid; int status; switch (pid = ufork(target_uname)) { case -1: /* Error */ log_err("failed to setup bs2000 environment for user %s: %s\n", target_uname, strerror(errno)); exit(150); case 0: /* Child */ break; default: /* Father */ while (pid != waitpid(pid, &status, 0)) ; /* @@@ FIXME: should we deal with STOP signals as well? */ if (WIFSIGNALED(status)) { kill (getpid(), WTERMSIG(status)); } exit(WEXITSTATUS(status)); } } #endif /*_OSD_POSIX*/ /* * Save these for later since initgroups will hose the struct */ uid = pw->pw_uid; actual_uname = strdup(pw->pw_name); target_homedir = strdup(pw->pw_dir); /* * Log the transaction here to be sure we have an open log * before we setuid(). */ log_err("uid: (%s/%s) gid: (%s/%s) cmd: %s\n", target_uname, actual_uname, target_gname, actual_gname, cmd); /* * Error out if attempt is made to execute as root or as * a UID less than AP_UID_MIN. Tsk tsk. */ if ((uid == 0) || (uid < AP_UID_MIN)) { log_err("cannot run as forbidden uid (%d/%s)\n", uid, cmd); exit(107); } /* * Error out if attempt is made to execute as root group * or as a GID less than AP_GID_MIN. Tsk tsk. */ if ((gid == 0) || (gid < AP_GID_MIN)) { log_err("cannot run as forbidden gid (%d/%s)\n", gid, cmd); exit(108); } /* * Change UID/GID here so that the following tests work over NFS. * * Initialize the group access list for the target user, * and setgid() to the target group. If unsuccessful, error out. */ if (((setgid(gid)) != 0) || (initgroups(actual_uname, gid) != 0)) { log_err("failed to setgid (%ld: %s)\n", gid, cmd); exit(109); } /* * setuid() to the target user. Error out on fail. */ if ((setuid(uid)) != 0) { log_err("failed to setuid (%ld: %s)\n", uid, cmd); exit(110); } /* * Get the current working directory, as well as the proper * document root (dependant upon whether or not it is a * ~userdir request). Error out if we cannot get either one, * or if the current working directory is not in the docroot. * Use chdir()s and getcwd()s to avoid problems with symlinked * directories. Yuck. */ if (getcwd(cwd, AP_MAXPATH) == NULL) { log_err("cannot get current working directory\n"); exit(111); } if (userdir) { if (((chdir(target_homedir)) != 0) || ((chdir(AP_USERDIR_SUFFIX)) != 0) || ((getcwd(dwd, AP_MAXPATH)) == NULL) || ((chdir(cwd)) != 0)) { log_err("cannot get docroot information (%s)\n", target_homedir); exit(112); } } else { if (((chdir(AP_DOC_ROOT)) != 0) || ((getcwd(dwd, AP_MAXPATH)) == NULL) || ((chdir(cwd)) != 0)) { log_err("cannot get docroot information (%s)\n", AP_DOC_ROOT); exit(113); } } if ((strncmp(cwd, dwd, strlen(dwd))) != 0) { log_err("command not in docroot (%s/%s)\n", cwd, cmd); exit(114); } /* * Stat the cwd and verify it is a directory, or error out. */ if (((lstat(cwd, &dir_info)) != 0) || !(S_ISDIR(dir_info.st_mode))) { log_err("cannot stat directory: (%s)\n", cwd); exit(115); } /* * Error out if cwd is writable by others. */ if ((dir_info.st_mode & S_IWOTH) || (dir_info.st_mode & S_IWGRP)) { log_err("directory is writable by others: (%s)\n", cwd); exit(116); } /* * Error out if we cannot stat the program. */ if (((lstat(cmd, &prg_info)) != 0) || (S_ISLNK(prg_info.st_mode))) { log_err("cannot stat program: (%s)\n", cmd); exit(117); } /* * Error out if the program is writable by others. */ if ((prg_info.st_mode & S_IWOTH) || (prg_info.st_mode & S_IWGRP)) { log_err("file is writable by others: (%s/%s)\n", cwd, cmd); exit(118); } /* * Error out if the file is setuid or setgid. */ if ((prg_info.st_mode & S_ISUID) || (prg_info.st_mode & S_ISGID)) { log_err("file is either setuid or setgid: (%s/%s)\n", cwd, cmd); exit(119); } /* * Error out if the target name/group is different from * the name/group of the cwd or the program. */ if ((uid != dir_info.st_uid) || (gid != dir_info.st_gid) || (uid != prg_info.st_uid) || (gid != prg_info.st_gid)) { log_err("target uid/gid (%ld/%ld) mismatch " "with directory (%ld/%ld) or program (%ld/%ld)\n", uid, gid, dir_info.st_uid, dir_info.st_gid, prg_info.st_uid, prg_info.st_gid); exit(120); } /* * Error out if the program is not executable for the user. * Otherwise, she won't find any error in the logs except for * "[error] Premature end of script headers: ..." */ if (!(prg_info.st_mode & S_IXUSR)) { log_err("file has no execute permission: (%s/%s)\n", cwd, cmd); exit(121); } #ifdef AP_SUEXEC_UMASK /* * umask() uses inverse logic; bits are CLEAR for allowed access. */ if ((~AP_SUEXEC_UMASK) & 0022) { log_err("notice: AP_SUEXEC_UMASK of %03o allows " "write permission to group and/or other\n", AP_SUEXEC_UMASK); } umask(AP_SUEXEC_UMASK); #endif /* AP_SUEXEC_UMASK */ clean_env(); /* * Be sure to close the log file so the CGI can't * mess with it. If the exec fails, it will be reopened * automatically when log_err is called. Note that the log * might not actually be open if AP_LOG_EXEC isn't defined. * However, the "log" cell isn't ifdef'd so let's be defensive * and assume someone might have done something with it * outside an ifdef'd AP_LOG_EXEC block. */ if (log != NULL) { fclose(log); log = NULL; } /* * Execute the command, replacing our image with its own. */ #ifdef NEED_HASHBANG_EMUL /* We need the #! emulation when we want to execute scripts */ { extern char **environ; ap_execve(cmd, &argv[3], environ); } #else /*NEED_HASHBANG_EMUL*/ execv(cmd, &argv[3]); #endif /*NEED_HASHBANG_EMUL*/ /* * (I can't help myself...sorry.) * * Uh oh. Still here. Where's the kaboom? There was supposed to be an * EARTH-shattering kaboom! * * Oh well, log the failure and error out. */ log_err("(%d)%s: exec failed (%s)\n", errno, strerror(errno), cmd); exit(255); }