From b459de225baf4838eb40f7b3f018888ec4e0ea71 Mon Sep 17 00:00:00 2001 From: Jonas Smedegaard Date: Wed, 29 Dec 2004 17:35:52 +0000 Subject: Add source for suexec binary. --- apache/mksuexec/Makefile | 5 + apache/mksuexec/suexec.c | 620 ++++++++++++++++++++++++++++++++++++++++++ apache/mksuexec/suexec.c.diff | 11 + apache/mksuexec/suexec.h | 107 ++++++++ 4 files changed, 743 insertions(+) create mode 100644 apache/mksuexec/Makefile create mode 100644 apache/mksuexec/suexec.c create mode 100644 apache/mksuexec/suexec.c.diff create mode 100644 apache/mksuexec/suexec.h (limited to 'apache/mksuexec') diff --git a/apache/mksuexec/Makefile b/apache/mksuexec/Makefile new file mode 100644 index 0000000..2e1bd79 --- /dev/null +++ b/apache/mksuexec/Makefile @@ -0,0 +1,5 @@ +suexec-home: + gcc -lm -lcrypt -ldb -ldl -I/usr/include/apache-1.3 -DAP_DOC_ROOT=/home -o suexec-home suexec.c + +suexec2-prefork-home: + gcc -lm -lcrypt -ldb -ldl -I/usr/include/apache2 -I/usr/include/apr-0 -DAP_DOC_ROOT=/home -o suexec-prefork-home suexec.c diff --git a/apache/mksuexec/suexec.c b/apache/mksuexec/suexec.c new file mode 100644 index 0000000..af67ad5 --- /dev/null +++ b/apache/mksuexec/suexec.c @@ -0,0 +1,620 @@ +/* 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. + */ + +/* + * 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 "apr.h" +#include "ap_config.h" +#include "suexec.h" + +#define AP_DOC_ROOT "/home" + +#include +#include +#include +#include +#include +#if APR_HAVE_UNISTD_H +#include +#endif + +#include +#include +#include + +#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(SUNOS4) +extern char *sys_errlist[]; +#define strerror(x) sys_errlist[(x)] +#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[] = +{ + /* variable name starts with */ + "HTTP_", + "SSL_", + + /* variable name is */ + "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_REMOTE_USER=", + "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_SIGNATURE=", + "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; + + /* While cleaning the environment, the environment should be clean. + * (e.g. malloc() may get the name of a file for writing debugging info. + * Bad news if MALLOC_DEBUG_FILE is set to /etc/passwd. Sprintf() may be + * susceptible to bad locale settings....) + * (from PR 2790) + */ + char **envp = environ; + char *empty_ptr = NULL; + + environ = &empty_ptr; /* VERY safe environment */ + + 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 = envp; *ep && cidx < AP_ENVBUF-1; ep++) { + 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 */ + + /* + * Start with a "clean" environment + */ + clean_env(); + + 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 */ + + /* + * 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); +} diff --git a/apache/mksuexec/suexec.c.diff b/apache/mksuexec/suexec.c.diff new file mode 100644 index 0000000..48adf9e --- /dev/null +++ b/apache/mksuexec/suexec.c.diff @@ -0,0 +1,11 @@ +--- suexec.c.orig 2004-08-23 17:07:18.000000000 +0200 ++++ suexec.c 2004-12-29 00:30:26.000000000 +0100 +@@ -33,6 +33,8 @@ + #include "ap_config.h" + #include "suexec.h" + ++#define AP_DOC_ROOT "/home" ++ + #include + #include + #include diff --git a/apache/mksuexec/suexec.h b/apache/mksuexec/suexec.h new file mode 100644 index 0000000..e76a3bb --- /dev/null +++ b/apache/mksuexec/suexec.h @@ -0,0 +1,107 @@ +/* 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. + */ + +/* + * suexec.h -- user-definable variables for the suexec wrapper code. + * (See README.configure on how to customize these variables.) + */ + + +#ifndef _SUEXEC_H +#define _SUEXEC_H + +/* + * Include ap_config_layout so we can work out where the default htdocsdir + * and logsdir are. + */ +#include "ap_config_layout.h" + +/* + * HTTPD_USER -- Define as the username under which Apache normally + * runs. This is the only user allowed to execute + * this program. + */ +#ifndef AP_HTTPD_USER +#define AP_HTTPD_USER "www" +#endif + +/* + * UID_MIN -- Define this as the lowest UID allowed to be a target user + * for suEXEC. For most systems, 500 or 100 is common. + */ +#ifndef AP_UID_MIN +#define AP_UID_MIN 100 +#endif + +/* + * GID_MIN -- Define this as the lowest GID allowed to be a target group + * for suEXEC. For most systems, 100 is common. + */ +#ifndef AP_GID_MIN +#define AP_GID_MIN 100 +#endif + +/* + * USERDIR_SUFFIX -- Define to be the subdirectory under users' + * home directories where suEXEC access should + * be allowed. All executables under this directory + * will be executable by suEXEC as the user so + * they should be "safe" programs. If you are + * using a "simple" UserDir directive (ie. one + * without a "*" in it) this should be set to + * the same value. suEXEC will not work properly + * in cases where the UserDir directive points to + * a location that is not the same as the user's + * home directory as referenced in the passwd file. + * + * If you have VirtualHosts with a different + * UserDir for each, you will need to define them to + * all reside in one parent directory; then name that + * parent directory here. IF THIS IS NOT DEFINED + * PROPERLY, ~USERDIR CGI REQUESTS WILL NOT WORK! + * See the suEXEC documentation for more detailed + * information. + */ +#ifndef AP_USERDIR_SUFFIX +#define AP_USERDIR_SUFFIX "public_html" +#endif + +/* + * LOG_EXEC -- Define this as a filename if you want all suEXEC + * transactions and errors logged for auditing and + * debugging purposes. + */ +#ifndef AP_LOG_EXEC +#define AP_LOG_EXEC DEFAULT_EXP_LOGFILEDIR "/suexec_log" /* Need me? */ +#endif + +/* + * DOC_ROOT -- Define as the DocumentRoot set for Apache. This + * will be the only hierarchy (aside from UserDirs) + * that can be used for suEXEC behavior. + */ +#ifndef AP_DOC_ROOT +#define AP_DOC_ROOT DEFAULT_EXP_HTDOCSDIR +#endif + +/* + * SAFE_PATH -- Define a safe PATH environment to pass to CGI executables. + * + */ +#ifndef AP_SAFE_PATH +#define AP_SAFE_PATH "/usr/local/bin:/usr/bin:/bin" +#endif + +#endif /* _SUEXEC_H */ -- cgit v1.2.3