Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

884 lines (713 sloc) 19.719 kb
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* Cherokee
*
* Authors:
* Alvaro Lopez Ortega <alvaro@alobbs.com>
*
* Copyright (C) 2001-2014 Alvaro Lopez Ortega
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
#include "common-internal.h"
#include "source_interpreter.h"
#include "util.h"
#include "connection-protected.h"
#include "thread.h"
#include "bogotime.h"
#include "spawner.h"
#include "logger_writer.h"
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#define ENTRIES "source,src,interpreter"
#define DEFAULT_TIMEOUT 10
#define GRNAM_BUF_LEN 8192
#define MAX_SPAWN_FAILS_IN_A_ROW 5
static void interpreter_free (void *src);
#define source_is_unresponsive(src) \
(cherokee_bogonow_now > (src)->spawning_since + (src)->timeout)
ret_t
cherokee_source_interpreter_new (cherokee_source_interpreter_t **src)
{
CHEROKEE_NEW_STRUCT(n, source_interpreter);
cherokee_source_init (SOURCE(n));
cherokee_buffer_init (&n->interpreter);
cherokee_buffer_init (&n->change_user_name);
cherokee_buffer_init (&n->chroot);
n->custom_env = NULL;
n->custom_env_len = 0;
n->env_inherited = true;
n->debug = false;
n->pid = -1;
n->timeout = DEFAULT_TIMEOUT;
n->change_user = -1;
n->change_group = -1;
n->spawn_type = spawn_unknown;
n->spawning_since = 0;
n->spawning_since_fails = 0;
n->last_connect = 0;
SOURCE(n)->type = source_interpreter;
SOURCE(n)->free = (cherokee_func_free_t)interpreter_free;
CHEROKEE_MUTEX_INIT (&n->launching_mutex, NULL);
n->launching = false;
*src = n;
return ret_ok;
}
static void
free_custom_env (void *ptr)
{
cuint_t i;
cherokee_source_interpreter_t *src = ptr;
for (i=0; src->custom_env[i] != NULL; i++) {
free (src->custom_env[i]);
}
free (src->custom_env);
}
static void
kill_pid (cherokee_source_interpreter_t *src)
{
if (src->pid <= 0)
return;
TRACE(ENTRIES, "Killing %s, pid=%d\n", src->interpreter.buf, src->pid);
kill (src->pid, SIGTERM);
}
static void
interpreter_free (void *ptr)
{
cherokee_source_interpreter_t *src = ptr;
/* Only frees its stuff, the rest will be freed by
* cherokee_source_t.
*/
kill_pid (src);
cherokee_buffer_mrproper (&src->interpreter);
cherokee_buffer_mrproper (&src->change_user_name);
cherokee_buffer_mrproper (&src->chroot);
if (src->custom_env)
free_custom_env (src);
CHEROKEE_MUTEX_DESTROY (&src->launching_mutex);
}
static char *
find_next_stop (char *p)
{
char *s;
char *w;
s = strchr (p, '/');
w = strchr (p, ' ');
if ((s == NULL) && (w == NULL))
return NULL;
if (w == NULL)
return s;
if (s == NULL)
return w;
return (w > s) ? s : w;
}
static ret_t
check_interpreter_full (cherokee_buffer_t *fullpath, cherokee_buffer_t *chroot_dir)
{
ret_t ret = ret_error;
int re;
struct stat inter;
char *p;
char tmp;
const char *end;
cherokee_buffer_t completepath;
cherokee_buffer_init (&completepath);
if (chroot_dir->len > 0) {
/* Chroot and relative path, it doesn't make sense */
if (fullpath->len == 0 || fullpath->buf[0] != '/')
goto done;
cherokee_buffer_add_buffer (&completepath, chroot_dir);
}
cherokee_buffer_add_buffer(&completepath, fullpath);
end = completepath.buf + completepath.len;
p = find_next_stop (completepath.buf + 1);
if (p == NULL)
goto done;
while (p <= end) {
/* Set a temporal end */
tmp = *p;
*p = '\0';
/* Does the file exist? */
re = cherokee_stat (completepath.buf, &inter);
if ((re == 0) &&
(! S_ISDIR(inter.st_mode)))
{
*p = tmp;
ret = ret_ok;
goto done;
}
*p = tmp;
/* Exit if already reached the end */
if (p >= end)
break;
/* Find the next position */
p = find_next_stop (p+1);
if (p == NULL)
p = (char *)end;
}
done:
cherokee_buffer_mrproper(&completepath);
return ret;
}
static ret_t
check_interpreter_path (cherokee_buffer_t *partial_path, cherokee_buffer_t *chroot_dir)
{
ret_t ret;
char *p;
char *colon;
char *path;
cherokee_buffer_t fullpath;
p = getenv("PATH");
if (p == NULL)
return ret_error;
path = strdup (p);
if (path == NULL)
return ret_error;
cherokee_buffer_init (&fullpath);
p = path;
do {
colon = strchr(p, ':');
if (colon != NULL)
*colon = '\0';
cherokee_buffer_clean (&fullpath);
cherokee_buffer_add (&fullpath, p, strlen(p));
cherokee_buffer_add_char (&fullpath, '/');
cherokee_buffer_add_buffer (&fullpath, partial_path);
ret = check_interpreter_full (&fullpath, chroot_dir);
if (ret == ret_ok)
goto done;
if (colon == NULL)
break;
p = colon + 1;
} while (true);
ret = ret_not_found;
done:
cherokee_buffer_mrproper (&fullpath);
free (path);
return ret;
}
static ret_t
command_has_env_variables (cherokee_buffer_t *command)
{
int n = 0;
cherokee_boolean_t equal = false;
while (n < command->len) {
if (command->buf[n] == '=') {
equal = true;
} else if (command->buf[n] == ' ') {
return (equal)? ret_error : ret_ok;
}
n++;
}
return ret_ok;
}
static ret_t
check_interpreter (cherokee_source_interpreter_t *src)
{
ret_t ret;
if (src->interpreter.buf[0] == '/') {
ret = check_interpreter_full (&src->interpreter, &src->chroot);
} else {
ret = check_interpreter_path (&src->interpreter, &src->chroot);
}
return ret;
}
static ret_t
replace_environment_variables (cherokee_buffer_t *buf)
{
ret_t ret;
char *dollar;
char *p;
char *val;
cherokee_buffer_t tmp;
cherokee_buffer_init (&tmp);
do {
/* Find $
*/
dollar = strchr (buf->buf, '$');
if (dollar == NULL) {
goto out;
}
/* End of the variable name
*/
p = dollar + 1;
while ((*p == '_') ||
((*p >= '0') && (*p <= '9')) ||
((*p >= 'A') && (*p <= 'Z')) ||
((*p >= 'a') && (*p <= 'z')))
{
p++;
}
/* Call getenv
*/
cherokee_buffer_clean (&tmp);
cherokee_buffer_add (&tmp, dollar, p-dollar);
val = getenv (tmp.buf + 1);
/* Replacement
*/
if (val != NULL) {
ret = cherokee_buffer_replace_string (buf, tmp.buf, tmp.len, val, strlen(val));
if (unlikely (ret != ret_ok)) goto out;
} else {
cherokee_buffer_remove_string (buf, tmp.buf, tmp.len);
}
} while (true);
ret = ret_ok;
out:
cherokee_buffer_mrproper (&tmp);
return ret;
}
static ret_t
add_env (cherokee_source_interpreter_t *src, cherokee_buffer_t *env, cherokee_buffer_t *val_orig)
{
ret_t ret;
int entry_len;
char *entry;
cherokee_buffer_t *val = NULL;
/* Replace $ENVs
*/
ret = cherokee_buffer_dup (val_orig, &val);
if (unlikely (ret != ret_ok)) goto error;
ret = replace_environment_variables (val);
if (unlikely (ret != ret_ok)) goto error;
/* Build the env entry
*/
entry_len = env->len + val->len + 2;
entry = (char *) malloc (entry_len);
if (unlikely (entry == NULL)) {
ret = ret_nomem;
goto error;
}
snprintf (entry, entry_len, "%s=%s", env->buf, val->buf);
TRACE(ENTRIES, "Adding env: %s\n", entry);
/* Add it into the env array
*/
if (src->custom_env_len == 0) {
src->custom_env = malloc (sizeof (char *) * 2);
} else {
src->custom_env = realloc (src->custom_env, (src->custom_env_len + 2) * sizeof (char *));
}
if (unlikely (src->custom_env == NULL)) {
ret = ret_nomem;
free (entry);
goto error;
}
src->custom_env_len += 1;
src->custom_env[src->custom_env_len - 1] = entry;
src->custom_env[src->custom_env_len] = NULL;
cherokee_buffer_free (val);
return ret_ok;
error:
cherokee_buffer_free (val);
return ret;
}
ret_t
cherokee_source_interpreter_configure (cherokee_source_interpreter_t *src,
cherokee_config_node_t *conf,
int prio)
{
ret_t ret;
int val;
cherokee_list_t *i, *j;
cherokee_config_node_t *child;
/* Configure the base class
*/
ret = cherokee_source_configure (SOURCE(src), conf);
if (ret != ret_ok)
return ret;
/* Interpreter parameters
*/
cherokee_config_node_foreach (i, conf) {
child = CONFIG_NODE(i);
if (equal_buf_str (&child->key, "interpreter")) {
cherokee_buffer_add_buffer (&src->interpreter, &child->val);
} else if (equal_buf_str (&child->key, "debug")) {
ret = cherokee_atob (child->val.buf, &src->debug);
if (ret != ret_ok) return ret_error;
} else if (equal_buf_str (&child->key, "timeout")) {
ret = cherokee_atoi (child->val.buf, &val);
if (ret != ret_ok) return ret_error;
src->timeout = val;
} else if (equal_buf_str (&child->key, "user")) {
struct passwd pwd;
char tmp[1024];
cherokee_buffer_add_buffer (&src->change_user_name, &child->val);
ret = cherokee_getpwnam_uid (child->val.buf, &pwd, tmp, sizeof(tmp));
if ((ret != ret_ok) || (pwd.pw_dir == NULL)) {
LOG_CRITICAL (CHEROKEE_ERROR_SRC_INTER_NO_USER, child->val.buf, prio);
return ret_error;
}
src->change_user = pwd.pw_uid;
if (src->change_group == -1) {
src->change_group = pwd.pw_gid;
}
} else if (equal_buf_str (&child->key, "group")) {
struct group grp;
char tmp[GRNAM_BUF_LEN];
ret = cherokee_getgrnam_gid (child->val.buf, &grp, tmp, sizeof(tmp));
if (ret != ret_ok) {
LOG_CRITICAL (CHEROKEE_ERROR_SRC_INTER_NO_GROUP, conf->val.buf, prio);
return ret_error;
}
src->change_group = grp.gr_gid;
} else if (equal_buf_str (&child->key, "chroot")) {
cherokee_buffer_add_buffer (&src->chroot, &child->val);
} else if (equal_buf_str (&child->key, "env")) {
cherokee_config_node_foreach (j, child) {
cherokee_config_node_t *child2 = CONFIG_NODE(j);
ret = add_env (src, &child2->key, &child2->val);
if (ret != ret_ok) return ret;
}
} else if (equal_buf_str (&child->key, "env_inherited")) {
/* Handled later on */
}
}
/* Inherited Environment
*/
ret = cherokee_config_node_get (conf, "env_inherited", &child);
if (ret == ret_ok) {
ret = cherokee_atob (child->val.buf, &src->env_inherited);
if (ret != ret_ok) return ret_error;
} else {
src->env_inherited = (src->custom_env_len == 0);
}
/* Sanity check
*/
if (cherokee_buffer_is_empty (&src->interpreter)) {
LOG_CRITICAL (CHEROKEE_ERROR_SRC_INTER_EMPTY_INTERPRETER, prio);
return ret_error;
}
ret = command_has_env_variables (&src->interpreter);
if (ret != ret_ok) {
LOG_CRITICAL (CHEROKEE_ERROR_SRC_INTER_ENV_IN_COMMAND, prio, src->interpreter.buf);
return ret_error;
}
ret = check_interpreter (src);
if (ret != ret_ok) {
LOG_ERROR (CHEROKEE_ERROR_SRC_INTER_NO_INTERPRETER, src->interpreter.buf, prio);
return ret_error;
}
return ret_ok;
}
static ret_t
_spawn_shm (cherokee_source_interpreter_t *src,
cherokee_logger_writer_t *error_writer)
{
ret_t ret;
char **envp;
char *empty_envp[] = {NULL};
/* Sanity check
*/
if (cherokee_buffer_is_empty (&src->interpreter))
return ret_not_found;
/* Maybe set a custom enviroment variable set
*/
envp = (src->custom_env) ? src->custom_env : empty_envp;
/* If a user isn't specified, use the same one..
*/
if (src->change_user == -1) {
src->change_user = getuid();
src->change_group = getgid();
}
/* Invoke the spawn mechanism
*/
ret = cherokee_spawner_spawn (&src->interpreter,
&src->change_user_name,
src->change_user,
src->change_group,
&src->chroot,
src->env_inherited,
envp,
error_writer,
&src->pid);
switch (ret) {
case ret_ok:
break;
case ret_eagain:
return ret_eagain;
default:
return ret_error;
}
return ret_ok;
}
static ret_t
_spawn_local (cherokee_source_interpreter_t *src,
cherokee_logger_writer_t *error_writer)
{
int re;
int tmp_fd;
char **envp;
const char *argv[] = {"sh", "-c", NULL, NULL};
int child = -1;
char *empty_envp[] = {NULL};
cherokee_buffer_t tmp;
/* Initialise the buffers
*/
cherokee_buffer_init (&tmp);
/* If there is a previous instance running, kill it
*/
kill_pid (src);
/* Maybe set a custom enviroment variable set
*/
envp = (src->custom_env) ? src->custom_env : empty_envp;
/* Execute the FastCGI server
*/
cherokee_buffer_add_va (&tmp, "exec %s", src->interpreter.buf);
TRACE (ENTRIES, "Spawn: /bin/sh -c \"exec %s\"\n", src->interpreter.buf);
#ifndef _WIN32
child = fork();
#endif
switch (child) {
case 0:
/* Change user if requested
*/
if (! cherokee_buffer_is_empty (&src->change_user_name)) {
initgroups (src->change_user_name.buf, src->change_user);
}
if (src->change_group != -1) {
setgid (src->change_group);
}
if (src->change_user != -1) {
setuid (src->change_user);
}
/* Reset signals
*/
cherokee_reset_signals();
/* Redirect/Close stderr and stdout
*/
if (! src->debug) {
cherokee_fd_close (STDOUT_FILENO);
cherokee_fd_close (STDERR_FILENO);
if ((error_writer != NULL) &&
(error_writer->fd != -1))
{
dup2 (error_writer->fd, STDOUT_FILENO);
dup2 (error_writer->fd, STDERR_FILENO);
}
else {
tmp_fd = cherokee_open ("/dev/null", O_WRONLY, 0700);
if (tmp_fd != -1) {
dup2 (tmp_fd, STDOUT_FILENO);
dup2 (tmp_fd, STDERR_FILENO);
}
}
}
if (src->chroot.len > 0) {
re = chroot(src->chroot.buf);
if (re < 0) {
LOG_ERROR (CHEROKEE_ERROR_SRC_INTER_CHROOT, src->chroot.buf);
exit(1);
}
}
argv[2] = (char *)tmp.buf;
if (src->env_inherited) {
do {
re = execv ("/bin/sh", (char **)argv);
} while (errno == EINTR);
} else {
do {
re = execve ("/bin/sh", (char **)argv, envp);
} while (errno == EINTR);
}
if (re < 0) {
LOG_ERROR (CHEROKEE_ERROR_SRC_INTER_SPAWN, tmp.buf);
exit (1);
}
exit ((re == 0) ? 0 : 1);
case -1:
goto error;
default:
src->pid = child;
sleep (1);
break;
}
cherokee_buffer_mrproper (&tmp);
return ret_ok;
error:
cherokee_buffer_mrproper (&tmp);
return ret_error;
}
ret_t
cherokee_source_interpreter_spawn (cherokee_source_interpreter_t *src,
cherokee_logger_writer_t *error_writer)
{
ret_t ret;
/* Sanity check
*/
if (cherokee_buffer_is_empty (&src->interpreter)) {
return ret_not_found;
}
/* Try with SHM first
*/
if ((src->spawn_type == spawn_shm) ||
(src->spawn_type == spawn_unknown))
{
ret = _spawn_shm (src, error_writer);
if (ret == ret_ok) {
if (src->spawn_type == spawn_unknown) {
src->spawn_type = spawn_shm;
}
return ret_ok;
} else if (ret == ret_eagain) {
return ret_eagain;
}
if (src->spawn_type == spawn_shm) {
return ret_error;
}
}
/* No luck, go 'local' then..
*/
if (src->spawn_type == spawn_unknown) {
src->spawn_type = spawn_local;
}
ret = _spawn_local (src, error_writer);
if (ret != ret_ok) {
return ret;
}
/* Since we managed to spawn something, clear any current address
* in the source so that it can retry connecting from the top.
*/
src->source.addr_current = NULL;
return ret_ok;
}
ret_t
cherokee_source_interpreter_connect_polling (cherokee_source_interpreter_t *src,
cherokee_socket_t *socket,
cherokee_connection_t *conn)
{
int re;
ret_t ret;
int unlocked;
int kill_prev;
/* Connect
*/
ret = cherokee_source_connect (SOURCE(src), socket);
switch (ret) {
case ret_ok:
/* connected */
if (src->spawning_since != 0) {
src->spawning_since = 0;
src->spawning_since_fails = 0;
}
src->last_connect = cherokee_bogonow_now;
TRACE (ENTRIES, "Connected successfully fd=%d\n", socket->socket);
return ret_ok;
case ret_eagain:
/* wait for the fd */
ret = cherokee_thread_deactive_to_polling (CONN_THREAD(conn),
conn, SOCKET_FD(socket),
FDPOLL_MODE_WRITE, false);
if (ret != ret_ok) {
return ret_error;
}
return ret_eagain;
case ret_deny:
case ret_error:
/* reset by peer: spawn process? */
TRACE (ENTRIES, "Connection refused (closing fd=%d)\n", socket->socket);
cherokee_socket_close (socket);
break;
default:
cherokee_socket_close (socket);
RET_UNKNOWN(ret);
return ret_error;
}
/* Spawn a new process
*/
unlocked = CHEROKEE_MUTEX_TRY_LOCK (&src->launching_mutex);
if (unlocked) {
cherokee_connection_sleep (conn, 1000);
return ret_eagain;
}
if (src->spawning_since == 0) {
cherokee_logger_writer_t *error_writer = NULL;
/* Check re-try limit */
if (src->spawning_since_fails >= MAX_SPAWN_FAILS_IN_A_ROW) {
TRACE (ENTRIES, "Failed to launch the interpreter %d consecutive times. Giving up now.\n",
src->spawning_since_fails);
src->spawning_since_fails = 0;
ret = ret_error;
goto out;
}
/* Kill prev (unresponsive) interpreter? */
kill_prev = ((src->pid > 0) && (source_is_unresponsive(src)));
if (! kill_prev) {
src->pid = -1;
}
/* Spawn */
ret = cherokee_virtual_server_get_error_log (CONN_VSRV(conn), &error_writer);
if (ret != ret_ok) {
ret = ret_error;
goto out;
}
ret = cherokee_source_interpreter_spawn (src, error_writer);
switch (ret) {
case ret_ok:
src->spawning_since = cherokee_bogonow_now;
ret = ret_eagain;
goto out;
case ret_eagain:
cherokee_connection_sleep (conn, 1000);
ret = ret_eagain;
goto out;
default:
if (src->interpreter.buf) {
TRACE (ENTRIES, "Couldn't spawn: %s\n", src->interpreter.buf);
} else {
TRACE (ENTRIES, "No interpreter to be spawned %s", "\n");
}
ret = ret_error;
goto out;
}
SHOULDNT_HAPPEN;
ret = ret_error;
goto out;
}
/* Is the launching process death?
*/
if (src->pid > 0) {
re = kill (src->pid, 0);
if ((re != 0) && (errno == ESRCH)) {
/* It's death */
TRACE (ENTRIES, "PID %d is already death\n", src->pid);
src->pid = -1;
src->spawning_since = 0;
src->spawning_since_fails += 1;
ret = ret_eagain;
goto out;
}
}
/* Is it unresponsive?
*/
if (source_is_unresponsive(src)) {
src->spawning_since = 0;
src->spawning_since_fails += 1;
ret = ret_eagain;
goto out;
}
/* Spawning on-going
*/
cherokee_connection_sleep (conn, 1000);
ret = ret_eagain;
out:
/* Raise conn's timeout? */
if ((src->spawning_since != 0) &&
(src->spawning_since + src->timeout > conn->timeout))
{
conn->timeout = src->spawning_since + src->timeout + 1;
}
CHEROKEE_MUTEX_UNLOCK (&src->launching_mutex);
return ret;
}
Jump to Line
Something went wrong with that request. Please try again.