| /* 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. |
| * |
| *********************************************************************** |
| * |
| * |
| */ |
| |
| #include "apr.h" |
| #include "ap_config.h" |
| #include "suexec.h" |
| |
| #include <sys/param.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <string.h> |
| #include <time.h> |
| #if APR_HAVE_UNISTD_H |
| #include <unistd.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <stdarg.h> |
| #include <stdlib.h> |
| #if APR_HAVE_FCNTL_H |
| #include <fcntl.h> |
| #endif |
| |
| #ifdef HAVE_PWD_H |
| #include <pwd.h> |
| #endif |
| |
| #ifdef HAVE_GRP_H |
| #include <grp.h> |
| #endif |
| |
| #ifdef AP_LOG_SYSLOG |
| #include <syslog.h> |
| #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; |
| |
| #ifdef AP_LOG_SYSLOG |
| /* Syslog support. */ |
| #if !defined(AP_LOG_FACILITY) && defined(LOG_AUTHPRIV) |
| #define AP_LOG_FACILITY LOG_AUTHPRIV |
| #elif !defined(AP_LOG_FACILITY) |
| #define AP_LOG_FACILITY LOG_AUTH |
| #endif |
| |
| static int log_open; |
| #else |
| /* Non-syslog support. */ |
| static FILE *log = NULL; |
| #endif |
| |
| static const char *const safe_env_lst[] = |
| { |
| /* variable name starts with */ |
| "SSL_", |
| /* "HTTP_" is handled specially in clean_env() */ |
| |
| /* variable name is */ |
| "AUTH_TYPE=", |
| "CONTENT_LENGTH=", |
| "CONTENT_TYPE=", |
| "CONTEXT_DOCUMENT_ROOT=", |
| "CONTEXT_PREFIX=", |
| "DATE_GMT=", |
| "DATE_LOCAL=", |
| "DOCUMENT_ARGS=", |
| "DOCUMENT_NAME=", |
| "DOCUMENT_PATH_INFO=", |
| "DOCUMENT_ROOT=", |
| "DOCUMENT_URI=", |
| "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_ERROR_NOTES=", |
| "REDIRECT_HANDLER=", |
| "REDIRECT_QUERY_STRING=", |
| "REDIRECT_REMOTE_USER=", |
| "REDIRECT_SCRIPT_FILENAME=", |
| "REDIRECT_STATUS=", |
| "REDIRECT_URL=", |
| "REQUEST_METHOD=", |
| "REQUEST_SCHEME=", |
| "REQUEST_URI=", |
| "SCRIPT_FILENAME=", |
| "SCRIPT_NAME=", |
| "SCRIPT_URI=", |
| "SCRIPT_URL=", |
| "SERVER_ADDR=", |
| "SERVER_ADMIN=", |
| "SERVER_NAME=", |
| "SERVER_PORT=", |
| "SERVER_PROTOCOL=", |
| "SERVER_SIGNATURE=", |
| "SERVER_SOFTWARE=", |
| "UNIQUE_ID=", |
| "USER_NAME=", |
| "TZ=", |
| NULL |
| }; |
| |
| static void log_err(const char *fmt,...) |
| __attribute__((format(printf,1,2))); |
| static void log_no_err(const char *fmt,...) |
| __attribute__((format(printf,1,2))); |
| static void err_output(int is_error, const char *fmt, va_list ap) |
| __attribute__((format(printf,2,0))); |
| |
| static void err_output(int is_error, const char *fmt, va_list ap) |
| { |
| #if defined(AP_LOG_SYSLOG) |
| if (!log_open) { |
| openlog("suexec", LOG_PID, AP_LOG_FACILITY); |
| log_open = 1; |
| } |
| |
| vsyslog(is_error ? LOG_ERR : LOG_INFO, fmt, ap); |
| #elif defined(AP_LOG_EXEC) |
| time_t timevar; |
| struct tm *lt; |
| |
| if (!log) { |
| #if defined(_LARGEFILE64_SOURCE) && HAVE_FOPEN64 |
| if ((log = fopen64(AP_LOG_EXEC, "a")) == NULL) { |
| #else |
| if ((log = fopen(AP_LOG_EXEC, "a")) == NULL) { |
| #endif |
| fprintf(stderr, "suexec failure: could not open log file\n"); |
| perror("fopen"); |
| exit(1); |
| } |
| } |
| |
| if (is_error) { |
| fprintf(stderr, "suexec policy violation: see suexec log for more " |
| "details\n"); |
| } |
| |
| 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(1, fmt, ap); /* 1 == is_error */ |
| va_end(ap); |
| #endif /* AP_LOG_EXEC */ |
| return; |
| } |
| |
| static void log_no_err(const char *fmt,...) |
| { |
| #ifdef AP_LOG_EXEC |
| va_list ap; |
| |
| va_start(ap, fmt); |
| err_output(0, fmt, ap); /* 0 == !is_error */ |
| va_end(ap); |
| #endif /* AP_LOG_EXEC */ |
| return; |
| } |
| |
| static void 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++; |
| |
| for (ep = envp; *ep && cidx < AP_ENVBUF-1; ep++) { |
| if (strncmp(*ep, "HTTP_", 5) == 0) { |
| if (strncmp(*ep + 5, "PROXY=", 6) == 0) { |
| /* |
| * HTTP_PROXY is treated as alias for http_proxy by some |
| * programs. |
| */ |
| } |
| else { |
| /* Other HTTP_* are safe */ |
| cleanenv[cidx] = *ep; |
| cidx++; |
| } |
| continue; |
| } |
| 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; |
| } |
| |
| /* Converts name (uid/gid) to long, returning -1 on error. */ |
| static long safe_strtol(const char *name) |
| { |
| char *endp; |
| long rv; |
| |
| errno = 0; |
| rv = strtol(name, &endp, 10); |
| if (errno || *endp != '\0') |
| rv = -1; |
| |
| return rv; |
| } |
| |
| 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", (unsigned long)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 |
| #if defined(AP_LOG_SYSLOG) |
| fprintf(stderr, " -D AP_LOG_SYSLOG\n"); |
| #elif defined(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 { |
| long parsed_uid = safe_strtol(target_uname); |
| uid_t target_uid = parsed_uid; |
| |
| /* uid_t may be signed or unsigned and may be smaller than |
| * long; try to catch long->(u)int conversion surprises and |
| * avoid calling getpwuid with a negative value though it |
| * should be safe anyway. */ |
| if (parsed_uid < 0 || target_uid < 0 || target_uid != parsed_uid |
| || (pw = getpwuid(target_uid)) == 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 { |
| long parsed_gid = safe_strtol(target_gname); |
| gid_t target_gid = parsed_gid; |
| |
| /* See above on long to uid_t conversion. */ |
| if (parsed_gid < 0 || target_gid < 0 || target_gid != parsed_gid |
| || (gr = getgrgid(target_gid)) == 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", (unsigned long)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", (unsigned long)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", |
| (unsigned long)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", |
| (unsigned long)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", |
| (unsigned long)uid, (unsigned long)gid, |
| (unsigned long)dir_info.st_uid, (unsigned long)dir_info.st_gid, |
| (unsigned long)prg_info.st_uid, (unsigned long)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 */ |
| { |
| 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); |
| } |