Ejemplo n.º 1
0
static int cgi_create_env(server *srv, connection *con, plugin_data *p, handler_ctx *hctx, buffer *cgi_handler) {
	pid_t pid;

	int to_cgi_fds[2];
	int from_cgi_fds[2];
	struct stat st;
	UNUSED(p);

#ifndef __WIN32

	if (!buffer_string_is_empty(cgi_handler)) {
		/* stat the exec file */
		if (-1 == (stat(cgi_handler->ptr, &st))) {
			log_error_write(srv, __FILE__, __LINE__, "sbss",
					"stat for cgi-handler", cgi_handler,
					"failed:", strerror(errno));
			return -1;
		}
	}

	if (pipe_cloexec(to_cgi_fds)) {
		log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno));
		return -1;
	}

	if (pipe_cloexec(from_cgi_fds)) {
		close(to_cgi_fds[0]);
		close(to_cgi_fds[1]);
		log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno));
		return -1;
	}

	/* fork, execve */
	switch (pid = fork()) {
	case 0: {
		/* child */
		char **args;
		int argc;
		int i = 0;
		char_array env;
		char *c;
		const char *s;
		http_cgi_opts opts = { 0, 0, NULL, NULL };

		/* move stdout to from_cgi_fd[1] */
		dup2(from_cgi_fds[1], STDOUT_FILENO);
	      #ifndef FD_CLOEXEC
		close(from_cgi_fds[1]);
		/* not needed */
		close(from_cgi_fds[0]);
	      #endif

		/* move the stdin to to_cgi_fd[0] */
		dup2(to_cgi_fds[0], STDIN_FILENO);
	      #ifndef FD_CLOEXEC
		close(to_cgi_fds[0]);
		/* not needed */
		close(to_cgi_fds[1]);
	      #endif

		/* create environment */
		env.ptr = NULL;
		env.size = 0;
		env.used = 0;

		http_cgi_headers(srv, con, &opts, cgi_env_add, &env);

		/* for valgrind */
		if (NULL != (s = getenv("LD_PRELOAD"))) {
			cgi_env_add(&env, CONST_STR_LEN("LD_PRELOAD"), s, strlen(s));
		}

		if (NULL != (s = getenv("LD_LIBRARY_PATH"))) {
			cgi_env_add(&env, CONST_STR_LEN("LD_LIBRARY_PATH"), s, strlen(s));
		}
#ifdef __CYGWIN__
		/* CYGWIN needs SYSTEMROOT */
		if (NULL != (s = getenv("SYSTEMROOT"))) {
			cgi_env_add(&env, CONST_STR_LEN("SYSTEMROOT"), s, strlen(s));
		}
#endif

		if (env.size == env.used) {
			env.size += 16;
			env.ptr = realloc(env.ptr, env.size * sizeof(*env.ptr));
		}

		env.ptr[env.used] = NULL;

		/* set up args */
		argc = 3;
		args = malloc(sizeof(*args) * argc);
		force_assert(args);
		i = 0;

		if (!buffer_string_is_empty(cgi_handler)) {
			args[i++] = cgi_handler->ptr;
		}
		args[i++] = con->physical.path->ptr;
		args[i  ] = NULL;

		/* search for the last / */
		if (NULL != (c = strrchr(con->physical.path->ptr, '/'))) {
			/* handle special case of file in root directory */
			const char* physdir = (c == con->physical.path->ptr) ? "/" : con->physical.path->ptr;

			/* temporarily shorten con->physical.path to directory without terminating '/' */
			*c = '\0';
			/* change to the physical directory */
			if (-1 == chdir(physdir)) {
				log_error_write(srv, __FILE__, __LINE__, "ssb", "chdir failed:", strerror(errno), con->physical.path);
			}
			*c = '/';
		}

		/* we don't need the client socket */
		for (i = 3; i < 256; i++) {
			if (i != srv->errorlog_fd) close(i);
		}

		/* exec the cgi */
		execve(args[0], args, env.ptr);

		/* most log files may have been closed/redirected by this point,
		 * though stderr might still point to lighttpd.breakage.log */
		perror(args[0]);
		_exit(1);
	}
	case -1:
		/* error */
		log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed:", strerror(errno));
		close(from_cgi_fds[0]);
		close(from_cgi_fds[1]);
		close(to_cgi_fds[0]);
		close(to_cgi_fds[1]);
		return -1;
	default: {
		/* parent process */

		close(from_cgi_fds[1]);
		close(to_cgi_fds[0]);

		/* register PID and wait for them asynchronously */

		hctx->pid = pid;
		hctx->fd = from_cgi_fds[0];
		hctx->fde_ndx = -1;

		++srv->cur_fds;

		if (0 == con->request.content_length) {
			close(to_cgi_fds[1]);
		} else {
			/* there is content to send */
			if (-1 == fdevent_fcntl_set_nb(srv->ev, to_cgi_fds[1])) {
				log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno));
				close(to_cgi_fds[1]);
				cgi_connection_close(srv, hctx);
				return -1;
			}

			if (0 != cgi_write_request(srv, hctx, to_cgi_fds[1])) {
				close(to_cgi_fds[1]);
				cgi_connection_close(srv, hctx);
				return -1;
			}

			++srv->cur_fds;
		}

		fdevent_register(srv->ev, hctx->fd, cgi_handle_fdevent, hctx);
		if (-1 == fdevent_fcntl_set_nb(srv->ev, hctx->fd)) {
			log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno));
			cgi_connection_close(srv, hctx);
			return -1;
		}
		fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);

		break;
	}
	}

	return 0;
#else
	return -1;
#endif
}
Ejemplo n.º 2
0
static int cgi_create_env(server *srv, connection *con, plugin_data *p, buffer *cgi_handler) {
	pid_t pid;

	int to_cgi_fds[2];
	int from_cgi_fds[2];
	int from_cgi_err_fds[2];
	struct stat st;

#ifndef _WIN32

	if (cgi_handler && cgi_handler->used > 1) {
		/* stat the exec file */
		if (-1 == (stat(cgi_handler->ptr, &st))) {
			log_error_write(srv, __FILE__, __LINE__, "sbss",
					"stat for cgi-handler", cgi_handler,
					"failed:", strerror(errno));
			return -1;
		}
	}

	if (pipe(to_cgi_fds)) {
		log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno));
		return -1;
	}

	if (pipe(from_cgi_fds)) {
		close(to_cgi_fds[0]); close(to_cgi_fds[1]);
		log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno));
		return -1;
	}

	if (pipe(from_cgi_err_fds)) {
		close(to_cgi_fds[0]); close(to_cgi_fds[1]);
		close(from_cgi_fds[0]); close(from_cgi_fds[1]);
		log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno));
		return -1;
	}

	/* fork, execve */
	switch (pid = fork()) {
	case 0: {
		/* child */
		char **args;
		int argc;
		int i = 0;
		char buf[32];
		size_t n;
		char_array env;
		char *c;
		const char *s;
		server_socket *srv_sock = con->srv_socket;

		/* move stdout to from_cgi_fd[1] */
		close(STDOUT_FILENO);
		dup2(from_cgi_fds[1], STDOUT_FILENO);
		close(from_cgi_fds[1]);
		/* not needed */
		close(from_cgi_fds[0]);

		/* move stderr to from_cgi_err_fd[1] */
		close(STDERR_FILENO);
		dup2(from_cgi_err_fds[1], STDERR_FILENO);
		close(from_cgi_err_fds[1]);
		/* not needed */
		close(from_cgi_err_fds[0]);

		/* move the stdin to to_cgi_fd[0] */
		close(STDIN_FILENO);
		dup2(to_cgi_fds[0], STDIN_FILENO);
		close(to_cgi_fds[0]);
		/* not needed */
		close(to_cgi_fds[1]);

		/* create environment */
		env.ptr = NULL;
		env.size = 0;
		env.used = 0;

		cgi_env_add(&env, CONST_STR_LEN("SERVER_SOFTWARE"), CONST_STR_LEN(PACKAGE_NAME"/"PACKAGE_VERSION));

		s = sock_addr_to_p(srv, &srv_sock->addr);
		cgi_env_add(&env, CONST_STR_LEN("SERVER_ADDR"), s, strlen(s));
		/* !!! careful: s maybe reused for SERVER_NAME !!! */

		if (!buffer_is_empty(con->server_name)) {
			size_t len = con->server_name->used - 1;
			char *colon = strchr(con->server_name->ptr, ':');
			if (colon) len = colon - con->server_name->ptr;

			cgi_env_add(&env, CONST_STR_LEN("SERVER_NAME"), con->server_name->ptr, len);
		} else {
			/* use SERVER_ADDR */
			cgi_env_add(&env, CONST_STR_LEN("SERVER_NAME"), s, strlen(s));
		}
		cgi_env_add(&env, CONST_STR_LEN("GATEWAY_INTERFACE"), CONST_STR_LEN("CGI/1.1"));

		s = get_http_version_name(con->request.http_version);

		cgi_env_add(&env, CONST_STR_LEN("SERVER_PROTOCOL"), s, strlen(s));

		LI_ltostr(buf, sock_addr_get_port(&srv_sock->addr));
		cgi_env_add(&env, CONST_STR_LEN("SERVER_PORT"), buf, strlen(buf));

		s = get_http_method_name(con->request.http_method);
		cgi_env_add(&env, CONST_STR_LEN("REQUEST_METHOD"), s, strlen(s));

		if (!buffer_is_empty(con->request.pathinfo)) {
			cgi_env_add(&env, CONST_STR_LEN("PATH_INFO"), CONST_BUF_LEN(con->request.pathinfo));
		}
		cgi_env_add(&env, CONST_STR_LEN("REDIRECT_STATUS"), CONST_STR_LEN("200"));
		if (!buffer_is_empty(con->uri.query)) {
			cgi_env_add(&env, CONST_STR_LEN("QUERY_STRING"), CONST_BUF_LEN(con->uri.query));
		} else {
			/* set a empty QUERY_STRING */
			cgi_env_add(&env, CONST_STR_LEN("QUERY_STRING"), CONST_STR_LEN(""));
		}
		if (!buffer_is_empty(con->request.orig_uri)) {
			cgi_env_add(&env, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(con->request.orig_uri));
		}

		s = sock_addr_to_p(srv, &con->dst_addr);
		cgi_env_add(&env, CONST_STR_LEN("REMOTE_ADDR"), s, strlen(s));

		LI_ltostr(buf, sock_addr_get_port(&con->dst_addr));
		cgi_env_add(&env, CONST_STR_LEN("REMOTE_PORT"), buf, strlen(buf));

		if (!buffer_is_empty(con->authed_user)) {
			cgi_env_add(&env, CONST_STR_LEN("REMOTE_USER"),
				    CONST_BUF_LEN(con->authed_user));
		}

#ifdef USE_OPENSSL
		if (srv_sock->is_ssl) {
			cgi_env_add(&env, CONST_STR_LEN("HTTPS"), CONST_STR_LEN("on"));
		}
#endif

		/* request.content_length < SSIZE_MAX, see request.c */
		if (con->request.content_length > 0) {
			LI_ltostr(buf, con->request.content_length);
			cgi_env_add(&env, CONST_STR_LEN("CONTENT_LENGTH"), buf, strlen(buf));
		}
		cgi_env_add(&env, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(con->physical.path));
		cgi_env_add(&env, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path));
		cgi_env_add(&env, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(con->physical.doc_root));

		/* for valgrind */
		if (NULL != (s = getenv("LD_PRELOAD"))) {
			cgi_env_add(&env, CONST_STR_LEN("LD_PRELOAD"), s, strlen(s));
		}

		if (NULL != (s = getenv("LD_LIBRARY_PATH"))) {
			cgi_env_add(&env, CONST_STR_LEN("LD_LIBRARY_PATH"), s, strlen(s));
		}
#ifdef __CYGWIN__
		/* CYGWIN needs SYSTEMROOT */
		if (NULL != (s = getenv("SYSTEMROOT"))) {
			cgi_env_add(&env, CONST_STR_LEN("SYSTEMROOT"), s, strlen(s));
		}
#endif

		for (n = 0; n < con->request.headers->used; n++) {
			data_string *ds;

			ds = (data_string *)con->request.headers->data[n];

			if (ds->value->used && ds->key->used) {
				size_t j;

				buffer_reset(p->tmp_buf);

				if (0 != strcasecmp(ds->key->ptr, "CONTENT-TYPE")) {
					buffer_copy_string_len(p->tmp_buf, CONST_STR_LEN("HTTP_"));
					p->tmp_buf->used--; /* strip \0 after HTTP_ */
				}

				buffer_prepare_append(p->tmp_buf, ds->key->used + 2);

				for (j = 0; j < ds->key->used - 1; j++) {
					char cr = '_';
					if (light_isalpha(ds->key->ptr[j])) {
						/* upper-case */
						cr = ds->key->ptr[j] & ~32;
					} else if (light_isdigit(ds->key->ptr[j])) {
						/* copy */
						cr = ds->key->ptr[j];
					}
					p->tmp_buf->ptr[p->tmp_buf->used++] = cr;
				}
				p->tmp_buf->ptr[p->tmp_buf->used++] = '\0';

				cgi_env_add(&env, CONST_BUF_LEN(p->tmp_buf), CONST_BUF_LEN(ds->value));
			}
		}

		for (n = 0; n < con->environment->used; n++) {
			data_string *ds;

			ds = (data_string *)con->environment->data[n];

			if (ds->value->used && ds->key->used) {
				size_t j;

				buffer_reset(p->tmp_buf);

				buffer_prepare_append(p->tmp_buf, ds->key->used + 2);

				for (j = 0; j < ds->key->used - 1; j++) {
					char cr = '_';
					if (light_isalpha(ds->key->ptr[j])) {
						/* upper-case */
						cr = ds->key->ptr[j] & ~32;
					} else if (light_isdigit(ds->key->ptr[j])) {
						/* copy */
						cr = ds->key->ptr[j];
					}
					p->tmp_buf->ptr[p->tmp_buf->used++] = cr;
				}
				p->tmp_buf->ptr[p->tmp_buf->used++] = '\0';

				cgi_env_add(&env, CONST_BUF_LEN(p->tmp_buf), CONST_BUF_LEN(ds->value));
			}
		}

		if (env.size == env.used) {
			env.size += 16;
			env.ptr = realloc(env.ptr, env.size * sizeof(*env.ptr));
		}

		env.ptr[env.used] = NULL;

		/* set up args */
		argc = 3;
		args = malloc(sizeof(*args) * argc);
		i = 0;

		if (cgi_handler && cgi_handler->used > 1) {
			args[i++] = cgi_handler->ptr;
		}
		args[i++] = con->physical.path->ptr;
		args[i++] = NULL;

		/* search for the last / */
		if (NULL != (c = strrchr(con->physical.path->ptr, '/'))) {
			*c = '\0';

			/* change to the physical directory */
			if (-1 == chdir(con->physical.path->ptr)) {
				log_error_write(srv, __FILE__, __LINE__, "ssb", "chdir failed:", strerror(errno), con->physical.path);
			}
			*c = '/';
		}

		/* we don't need the client socket */
		for (i = 3; i < 256; i++) {
			close(i);
		}

		/* exec the cgi */
		execve(args[0], args, env.ptr);

		/* */
		SEGFAULT("execve(%s) failed: %s", args[0], strerror(errno));
		break;
	}
	case -1:
		/* error */
		ERROR("fork() failed: %s", strerror(errno));
		close(to_cgi_fds[0]); close(to_cgi_fds[1]);
		close(from_cgi_fds[0]); close(from_cgi_fds[1]);
		close(from_cgi_err_fds[0]); close(from_cgi_err_fds[1]);
		return -1;
		break;
	default: {
		cgi_session *sess;
		/* father */

		close(from_cgi_fds[1]);
		close(from_cgi_err_fds[1]);
		close(to_cgi_fds[0]);

		/* register PID and wait for them asyncronously */
		con->mode = p->id;
		buffer_reset(con->physical.path);

		sess = cgi_session_init();

		sess->remote_con = con;
		sess->pid = pid;

		assert(sess->sock);

		sess->sock->fd = from_cgi_fds[0];
		sess->sock->type = IOSOCKET_TYPE_PIPE;
		sess->sock_err->fd = from_cgi_err_fds[0];
		sess->sock_err->type = IOSOCKET_TYPE_PIPE;
		sess->wb_sock->fd = to_cgi_fds[1];
		sess->wb_sock->type = IOSOCKET_TYPE_PIPE;

		if (-1 == fdevent_fcntl_set(srv->ev, sess->sock)) {
			log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno));

			cgi_session_free(sess);

			return -1;
		}

		if (-1 == fdevent_fcntl_set(srv->ev, sess->sock_err)) {
			log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno));

			cgi_session_free(sess);

			return -1;
		}

		con->plugin_ctx[p->id] = sess;

		fdevent_register(srv->ev, sess->sock, cgi_handle_fdevent, sess);
		fdevent_event_add(srv->ev, sess->sock, FDEVENT_IN);

		fdevent_register(srv->ev, sess->sock_err, cgi_handle_err_fdevent, sess);
		fdevent_event_add(srv->ev, sess->sock_err, FDEVENT_IN);

		sess->state = CGI_STATE_READ_RESPONSE_HEADER;

		break;
	}
	}

	return 0;
#else
	return -1;
#endif
}
Ejemplo n.º 3
0
static int cgi_create_env(server *srv, connection *con, plugin_data *p, buffer *cgi_handler) {
	pid_t pid;

#ifdef HAVE_IPV6
	char b2[INET6_ADDRSTRLEN + 1];
#endif

	int to_cgi_fds[2];
	int from_cgi_fds[2];
	struct stat st;

#ifndef __WIN32

	if (cgi_handler->used > 1) {
		/* stat the exec file */
		if (-1 == (stat(cgi_handler->ptr, &st))) {
			log_error_write(srv, __FILE__, __LINE__, "sbss",
					"stat for cgi-handler", cgi_handler,
					"failed:", strerror(errno));
			return -1;
		}
	}

	if (pipe(to_cgi_fds)) {
		log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno));
		return -1;
	}

	if (pipe(from_cgi_fds)) {
		log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno));
		return -1;
	}

	/* fork, execve */
	switch (pid = fork()) {
	case 0: {
		/* child */
		char **args;
		int argc;
		int i = 0;
		char buf[32];
		size_t n;
		char_array env;
		char *c;
		const char *s;
		server_socket *srv_sock = con->srv_socket;

		/* move stdout to from_cgi_fd[1] */
		close(STDOUT_FILENO);
		dup2(from_cgi_fds[1], STDOUT_FILENO);
		close(from_cgi_fds[1]);
		/* not needed */
		close(from_cgi_fds[0]);

		/* move the stdin to to_cgi_fd[0] */
		close(STDIN_FILENO);
		dup2(to_cgi_fds[0], STDIN_FILENO);
		close(to_cgi_fds[0]);
		/* not needed */
		close(to_cgi_fds[1]);

		/* HACK:
		 * this is not nice, but it works
		 *
		 * we feed the stderr of the CGI to our errorlog, if possible
		 */
		if (srv->errorlog_mode == ERRORLOG_FILE) {
			close(STDERR_FILENO);
			dup2(srv->errorlog_fd, STDERR_FILENO);
		}

		/* create environment */
		env.ptr = NULL;
		env.size = 0;
		env.used = 0;

		cgi_env_add(&env, CONST_STR_LEN("SERVER_SOFTWARE"), CONST_STR_LEN(PACKAGE_NAME"/"PACKAGE_VERSION));

		if (!buffer_is_empty(con->server_name)) {
			cgi_env_add(&env, CONST_STR_LEN("SERVER_NAME"), CONST_BUF_LEN(con->server_name));
		} else {
#ifdef HAVE_IPV6
			s = inet_ntop(srv_sock->addr.plain.sa_family,
				      srv_sock->addr.plain.sa_family == AF_INET6 ?
				      (const void *) &(srv_sock->addr.ipv6.sin6_addr) :
				      (const void *) &(srv_sock->addr.ipv4.sin_addr),
				      b2, sizeof(b2)-1);
#else
			s = inet_ntoa(srv_sock->addr.ipv4.sin_addr);
#endif
			cgi_env_add(&env, CONST_STR_LEN("SERVER_NAME"), s, strlen(s));
		}
		cgi_env_add(&env, CONST_STR_LEN("GATEWAY_INTERFACE"), CONST_STR_LEN("CGI/1.1"));

		s = get_http_version_name(con->request.http_version);

		cgi_env_add(&env, CONST_STR_LEN("SERVER_PROTOCOL"), s, strlen(s));

		ltostr(buf,
#ifdef HAVE_IPV6
			ntohs(srv_sock->addr.plain.sa_family == AF_INET6 ? srv_sock->addr.ipv6.sin6_port : srv_sock->addr.ipv4.sin_port)
#else
			ntohs(srv_sock->addr.ipv4.sin_port)
#endif
			);
		cgi_env_add(&env, CONST_STR_LEN("SERVER_PORT"), buf, strlen(buf));

#ifdef HAVE_IPV6
		s = inet_ntop(srv_sock->addr.plain.sa_family,
			      srv_sock->addr.plain.sa_family == AF_INET6 ?
			      (const void *) &(srv_sock->addr.ipv6.sin6_addr) :
			      (const void *) &(srv_sock->addr.ipv4.sin_addr),
			      b2, sizeof(b2)-1);
#else
		s = inet_ntoa(srv_sock->addr.ipv4.sin_addr);
#endif
		cgi_env_add(&env, CONST_STR_LEN("SERVER_ADDR"), s, strlen(s));

		s = get_http_method_name(con->request.http_method);
		cgi_env_add(&env, CONST_STR_LEN("REQUEST_METHOD"), s, strlen(s));

		if (!buffer_is_empty(con->request.pathinfo)) {
			cgi_env_add(&env, CONST_STR_LEN("PATH_INFO"), CONST_BUF_LEN(con->request.pathinfo));
		}
		cgi_env_add(&env, CONST_STR_LEN("REDIRECT_STATUS"), CONST_STR_LEN("200"));
		if (!buffer_is_empty(con->uri.query)) {
			cgi_env_add(&env, CONST_STR_LEN("QUERY_STRING"), CONST_BUF_LEN(con->uri.query));
		}
		if (!buffer_is_empty(con->request.orig_uri)) {
			cgi_env_add(&env, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(con->request.orig_uri));
		}


#ifdef HAVE_IPV6
		s = inet_ntop(con->dst_addr.plain.sa_family,
			      con->dst_addr.plain.sa_family == AF_INET6 ?
			      (const void *) &(con->dst_addr.ipv6.sin6_addr) :
			      (const void *) &(con->dst_addr.ipv4.sin_addr),
			      b2, sizeof(b2)-1);
#else
		s = inet_ntoa(con->dst_addr.ipv4.sin_addr);
#endif
		cgi_env_add(&env, CONST_STR_LEN("REMOTE_ADDR"), s, strlen(s));

		ltostr(buf,
#ifdef HAVE_IPV6
			ntohs(con->dst_addr.plain.sa_family == AF_INET6 ? con->dst_addr.ipv6.sin6_port : con->dst_addr.ipv4.sin_port)
#else
			ntohs(con->dst_addr.ipv4.sin_port)
#endif
			);
		cgi_env_add(&env, CONST_STR_LEN("REMOTE_PORT"), buf, strlen(buf));

		if (!buffer_is_empty(con->authed_user)) {
			cgi_env_add(&env, CONST_STR_LEN("REMOTE_USER"),
				    CONST_BUF_LEN(con->authed_user));
		}

		/* request.content_length < SSIZE_MAX, see request.c */
		ltostr(buf, con->request.content_length);
		cgi_env_add(&env, CONST_STR_LEN("CONTENT_LENGTH"), buf, strlen(buf));
		cgi_env_add(&env, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(con->physical.path));
		cgi_env_add(&env, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path));
		cgi_env_add(&env, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(con->physical.doc_root));

		/* for valgrind */
		if (NULL != (s = getenv("LD_PRELOAD"))) {
			cgi_env_add(&env, CONST_STR_LEN("LD_PRELOAD"), s, strlen(s));
		}

		if (NULL != (s = getenv("LD_LIBRARY_PATH"))) {
			cgi_env_add(&env, CONST_STR_LEN("LD_LIBRARY_PATH"), s, strlen(s));
		}
#ifdef __CYGWIN__
		/* CYGWIN needs SYSTEMROOT */
		if (NULL != (s = getenv("SYSTEMROOT"))) {
			cgi_env_add(&env, CONST_STR_LEN("SYSTEMROOT"), s, strlen(s));
		}
#endif

		for (n = 0; n < con->request.headers->used; n++) {
			data_string *ds;

			ds = (data_string *)con->request.headers->data[n];

			if (ds->value->used && ds->key->used) {
				size_t j;

				buffer_reset(p->tmp_buf);

				if (0 != strcasecmp(ds->key->ptr, "CONTENT-TYPE")) {
					buffer_copy_string(p->tmp_buf, "HTTP_");
					p->tmp_buf->used--; /* strip \0 after HTTP_ */
				}

				buffer_prepare_append(p->tmp_buf, ds->key->used + 2);

				for (j = 0; j < ds->key->used - 1; j++) {
					char cr = '_';
					if (light_isalpha(ds->key->ptr[j])) {
						/* upper-case */
						cr = ds->key->ptr[j] & ~32;
					} else if (light_isdigit(ds->key->ptr[j])) {
						/* copy */
						cr = ds->key->ptr[j];
					}
					p->tmp_buf->ptr[p->tmp_buf->used++] = cr;
				}
				p->tmp_buf->ptr[p->tmp_buf->used++] = '\0';

				cgi_env_add(&env, CONST_BUF_LEN(p->tmp_buf), CONST_BUF_LEN(ds->value));
			}
		}

		for (n = 0; n < con->environment->used; n++) {
			data_string *ds;

			ds = (data_string *)con->environment->data[n];

			if (ds->value->used && ds->key->used) {
				size_t j;

				buffer_reset(p->tmp_buf);

				buffer_prepare_append(p->tmp_buf, ds->key->used + 2);

				for (j = 0; j < ds->key->used - 1; j++) {
					p->tmp_buf->ptr[p->tmp_buf->used++] =
						isalpha((unsigned char)ds->key->ptr[j]) ?
						toupper((unsigned char)ds->key->ptr[j]) : '_';
				}
				p->tmp_buf->ptr[p->tmp_buf->used++] = '\0';

				cgi_env_add(&env, CONST_BUF_LEN(p->tmp_buf), CONST_BUF_LEN(ds->value));
			}
		}

		if (env.size == env.used) {
			env.size += 16;
			env.ptr = realloc(env.ptr, env.size * sizeof(*env.ptr));
		}

		env.ptr[env.used] = NULL;

		/* set up args */
		argc = 3;
		args = malloc(sizeof(*args) * argc);
		i = 0;

		if (cgi_handler->used > 1) {
			args[i++] = cgi_handler->ptr;
		}
		args[i++] = con->physical.path->ptr;
		args[i++] = NULL;

		/* search for the last / */
		if (NULL != (c = strrchr(con->physical.path->ptr, '/'))) {
			*c = '\0';

			/* change to the physical directory */
			if (-1 == chdir(con->physical.path->ptr)) {
				log_error_write(srv, __FILE__, __LINE__, "ssb", "chdir failed:", strerror(errno), con->physical.path);
			}
			*c = '/';
		}

		/* we don't need the client socket */
		for (i = 3; i < 256; i++) {
			if (i != srv->errorlog_fd) close(i);
		}

		/* exec the cgi */
		execve(args[0], args, env.ptr);

		log_error_write(srv, __FILE__, __LINE__, "sss", "CGI failed:", strerror(errno), args[0]);

		/* */
		SEGFAULT();
		break;
	}
	case -1:
		/* error */
		log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed:", strerror(errno));
		break;
	default: {
		handler_ctx *hctx;
		/* father */

		close(from_cgi_fds[1]);
		close(to_cgi_fds[0]);

		if (con->request.content_length) {
			chunkqueue *cq = con->request_content_queue;
			chunk *c;

			assert(chunkqueue_length(cq) == (off_t)con->request.content_length);

			/* there is content to send */
			for (c = cq->first; c; c = cq->first) {
				int r = 0;

				/* copy all chunks */
				switch(c->type) {
				case FILE_CHUNK:

					if (c->file.mmap.start == MAP_FAILED) {
						if (-1 == c->file.fd &&  /* open the file if not already open */
						    -1 == (c->file.fd = open(c->file.name->ptr, O_RDONLY))) {
							log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno));

							close(from_cgi_fds[0]);
							close(to_cgi_fds[1]);
							return -1;
						}

						c->file.mmap.length = c->file.length;

						if (MAP_FAILED == (c->file.mmap.start = mmap(0,  c->file.mmap.length, PROT_READ, MAP_SHARED, c->file.fd, 0))) {
							log_error_write(srv, __FILE__, __LINE__, "ssbd", "mmap failed: ",
									strerror(errno), c->file.name,  c->file.fd);

							close(from_cgi_fds[0]);
							close(to_cgi_fds[1]);
							return -1;
						}

						close(c->file.fd);
						c->file.fd = -1;

						/* chunk_reset() or chunk_free() will cleanup for us */
					}

					if ((r = write(to_cgi_fds[1], c->file.mmap.start + c->offset, c->file.length - c->offset)) < 0) {
						switch(errno) {
						case ENOSPC:
							con->http_status = 507;

							break;
						default:
							con->http_status = 403;
							break;
						}
					}
					break;
				case MEM_CHUNK:
					if ((r = write(to_cgi_fds[1], c->mem->ptr + c->offset, c->mem->used - c->offset - 1)) < 0) {
						switch(errno) {
						case ENOSPC:
							con->http_status = 507;

							break;
						default:
							con->http_status = 403;
							break;
						}
					}
					break;
				case UNUSED_CHUNK:
					break;
				}

				if (r > 0) {
					c->offset += r;
					cq->bytes_out += r;
				} else {
					break;
				}
				chunkqueue_remove_finished_chunks(cq);
			}
		}

		close(to_cgi_fds[1]);

		/* register PID and wait for them asyncronously */
		con->mode = p->id;
		buffer_reset(con->physical.path);

		hctx = cgi_handler_ctx_init();

		hctx->remote_conn = con;
		hctx->plugin_data = p;
		hctx->pid = pid;
		hctx->fd = from_cgi_fds[0];
		hctx->fde_ndx = -1;

		con->plugin_ctx[p->id] = hctx;

		fdevent_register(srv->ev, hctx->fd, cgi_handle_fdevent, hctx);
		fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);

		if (-1 == fdevent_fcntl_set(srv->ev, hctx->fd)) {
			log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno));

			fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
			fdevent_unregister(srv->ev, hctx->fd);

			log_error_write(srv, __FILE__, __LINE__, "sd", "cgi close:", hctx->fd);

			close(hctx->fd);

			cgi_handler_ctx_free(hctx);

			con->plugin_ctx[p->id] = NULL;

			return -1;
		}

		break;
	}
	}

	return 0;
#else
	return -1;
#endif
}
Ejemplo n.º 4
0
static int cgi_create_env(server *srv, connection *con, plugin_data *p, handler_ctx *hctx, buffer *cgi_handler) {
	char *args[3];
	int to_cgi_fds[2];
	int from_cgi_fds[2];
	int dfd = -1;
	UNUSED(p);

	if (!buffer_string_is_empty(cgi_handler)) {
		if (NULL == cgi_stat(srv, con, cgi_handler)) {
			log_error_write(srv, __FILE__, __LINE__, "sbss",
					"stat for cgi-handler", cgi_handler,
					"failed:", strerror(errno));
			return -1;
		}
	}

	if (pipe_cloexec(to_cgi_fds)) {
		log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno));
		return -1;
	}
	if (pipe_cloexec(from_cgi_fds)) {
		close(to_cgi_fds[0]);
		close(to_cgi_fds[1]);
		log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno));
		return -1;
	}
	fdevent_setfd_cloexec(to_cgi_fds[1]);
	fdevent_setfd_cloexec(from_cgi_fds[0]);

	{
		size_t i = 0;
		http_cgi_opts opts = { 0, 0, NULL, NULL };
		env_accum *env = &p->env;
		env->used = 0;
		env->oused = 0;

		/* create environment */

		http_cgi_headers(srv, con, &opts, cgi_env_add, env);

		/* for valgrind */
		if (p->env.ld_preload) {
			cgi_env_add(env, CONST_STR_LEN("LD_PRELOAD"), CONST_BUF_LEN(p->env.ld_preload));
		}
		if (p->env.ld_library_path) {
			cgi_env_add(env, CONST_STR_LEN("LD_LIBRARY_PATH"), CONST_BUF_LEN(p->env.ld_library_path));
		}
	      #ifdef __CYGWIN__
		/* CYGWIN needs SYSTEMROOT */
		if (p->env.systemroot) {
			cgi_env_add(env, CONST_STR_LEN("SYSTEMROOT"), CONST_BUF_LEN(p->env.systemroot));
		}
	      #endif

		if (env->esize <= env->oused) {
			env->esize = (env->oused + 1 + 0xf) & ~(0xfuL);
			env->eptr = realloc(env->eptr, env->esize * sizeof(*env->eptr));
			force_assert(env->eptr);
		}
		for (i = 0; i < env->oused; ++i) {
			env->eptr[i] = env->ptr + env->offsets[i];
		}
		env->eptr[env->oused] = NULL;

		/* set up args */
		i = 0;

		if (!buffer_string_is_empty(cgi_handler)) {
			args[i++] = cgi_handler->ptr;
		}
		args[i++] = con->physical.path->ptr;
		args[i  ] = NULL;
	}

	dfd = fdevent_open_dirname(con->physical.path->ptr, con->conf.follow_symlink);
	if (-1 == dfd) {
		log_error_write(srv, __FILE__, __LINE__, "ssb", "open dirname failed:", strerror(errno), con->physical.path);
	}

	hctx->pid = (dfd >= 0) ? fdevent_fork_execve(args[0], args, p->env.eptr, to_cgi_fds[0], from_cgi_fds[1], -1, dfd) : -1;

	if (-1 == hctx->pid) {
		/* log error with errno prior to calling close() (might change errno) */
		log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed:", strerror(errno));
		if (-1 != dfd) close(dfd);
		close(from_cgi_fds[0]);
		close(from_cgi_fds[1]);
		close(to_cgi_fds[0]);
		close(to_cgi_fds[1]);
		return -1;
	} else {
		if (-1 != dfd) close(dfd);
		close(from_cgi_fds[1]);
		close(to_cgi_fds[0]);

		hctx->fd = from_cgi_fds[0];

		++srv->cur_fds;

		cgi_pid_add(p, hctx->pid, hctx);

		if (0 == con->request.content_length) {
			close(to_cgi_fds[1]);
		} else {
			/* there is content to send */
			if (-1 == fdevent_fcntl_set_nb(srv->ev, to_cgi_fds[1])) {
				log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno));
				close(to_cgi_fds[1]);
				cgi_connection_close(srv, hctx);
				return -1;
			}

			if (0 != cgi_write_request(srv, hctx, to_cgi_fds[1])) {
				close(to_cgi_fds[1]);
				cgi_connection_close(srv, hctx);
				return -1;
			}

			++srv->cur_fds;
		}

		hctx->fdn = fdevent_register(srv->ev, hctx->fd, cgi_handle_fdevent, hctx);
		if (-1 == fdevent_fcntl_set_nb(srv->ev, hctx->fd)) {
			log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno));
			cgi_connection_close(srv, hctx);
			return -1;
		}
		fdevent_fdnode_event_set(srv->ev, hctx->fdn, FDEVENT_IN | FDEVENT_RDHUP);

		return 0;
	}
}