/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. * *********************************************************************** * *
*/
staticvoid clean_env(void)
{ 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(123);
}
cleanenv[cidx] = strdup("PATH=" AP_SAFE_PATH); if (cleanenv[cidx] == NULL) {
log_err("failed to malloc memory for environment\n"); exit(124);
}
cidx++;
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 *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();
/* * 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: (%lu)\n", (unsignedlong)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 #ifdefined(AP_LOG_SYSLOG)
fprintf(stderr, " -D AP_LOG_SYSLOG\n"); #elifdefined(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);
}
} else { if ((gr = getgrgid(atoi(target_gname))) == NULL) {
log_err("invalid target group id: (%s)\n", target_gname); exit(106);
}
}
gid = gr->gr_gid; if ((actual_gname = strdup(gr->gr_name)) == NULL) {
log_err("failed to alloc memory\n"); exit(125);
}
#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); if (actual_uname == NULL || target_homedir == NULL) {
log_err("failed to alloc memory\n"); exit(126);
}
/* * Log the transaction here to be sure we have an open log * before we setuid().
*/
log_no_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 (%lu/%s)\n", (unsignedlong)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 (%lu/%s)\n", (unsignedlong)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/initgroups (%lu: %s): %s\n",
(unsignedlong)gid, cmd, strerror(errno)); exit(109);
}
/* * setuid() to the target user. Error out on fail.
*/ if ((setuid(uid)) != 0) {
log_err("failed to setuid (%lu: %s): %s\n",
(unsignedlong)uid, cmd, strerror(errno)); exit(110);
}
/* * Get the current working directory, as well as the proper * document root (dependent 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 (%lu/%lu) mismatch " "with directory (%lu/%lu) or program (%lu/%lu)\n",
(unsignedlong)uid, (unsignedlong)gid,
(unsignedlong)dir_info.st_uid, (unsignedlong)dir_info.st_gid,
(unsignedlong)prg_info.st_uid, (unsignedlong)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. */ #ifdef AP_LOG_SYSLOG if (log_open) {
closelog();
log_open = 0;
} #else if (log != NULL) { #if APR_HAVE_FCNTL_H /* * ask fcntl(2) to set the FD_CLOEXEC flag on the log file, * so it'll be automagically closed if the exec() call succeeds.
*/
fflush(log);
setbuf(log, NULL); if ((fcntl(fileno(log), F_SETFD, FD_CLOEXEC) == -1)) {
log_err("error: can't set close-on-exec flag"); exit(122);
} #else /* * In this case, exec() errors won't be logged because we have already * dropped privileges and won't be able to reopen the log file.
*/
fclose(log);
log = NULL; #endif
} #endif
/* * Execute the command, replacing our image with its own.
*/ #ifdef NEED_HASHBANG_EMUL /* We need the #! emulation when we want to execute scripts */
{ externchar **environ;
/* * (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);
}
¤ Dauer der Verarbeitung: 0.18 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.