summaryrefslogtreecommitdiff
path: root/src/http.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/http.c')
-rw-r--r--src/http.c481
1 files changed, 481 insertions, 0 deletions
diff --git a/src/http.c b/src/http.c
new file mode 100644
index 0000000..64d4706
--- /dev/null
+++ b/src/http.c
@@ -0,0 +1,481 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+
+/*
+ * Copyright (C) 2004-2008 Red Hat, Inc.
+ *
+ * Caja is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Caja 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: Alexander Larsson <[email protected]>
+ *
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <X11/Xlib.h>
+
+#ifdef HAVE_DBUS_1_1
+#include <dbus/dbus.h>
+#endif
+
+#include <mateconf/mateconf-client.h>
+
+#include <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_SELINUX
+#include <selinux/selinux.h>
+#endif
+
+#include "user_share-common.h"
+#include "user_share-private.h"
+#include "http.h"
+
+/* From avahi-common/domain.h */
+#define AVAHI_LABEL_MAX 64
+
+#ifdef HAVE_DBUS_1_1
+static char *dbus_session_id;
+#endif
+
+static pid_t httpd_pid = 0;
+
+static int
+get_port (void)
+{
+ int sock;
+ struct sockaddr_in addr;
+ int reuse;
+ socklen_t len;
+
+ sock = socket (PF_INET, SOCK_STREAM, 0);
+ if (sock < 0) {
+ return -1;
+ }
+
+ memset (&addr, 0, sizeof (addr));
+ addr.sin_port = 0;
+ addr.sin_addr.s_addr = INADDR_ANY;
+
+ reuse = 1;
+ setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (reuse));
+ if (bind (sock, (struct sockaddr *)&addr, sizeof (addr)) == -1) {
+ close (sock);
+ return -1;
+ }
+
+ len = sizeof (addr);
+ if (getsockname (sock, (struct sockaddr *)&addr, &len) == -1) {
+ close (sock);
+ return -1;
+ }
+
+#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__)
+ /* XXX This exposes a potential race condition, but without this,
+ * httpd will not start on the above listed platforms due to the fact
+ * that SO_REUSEADDR is also needed when Apache binds to the listening
+ * socket. At this time, Apache does not support that socket option.
+ */
+ close (sock);
+#endif
+ return ntohs (addr.sin_port);
+}
+
+static char *
+truncate_name (const char *name)
+{
+ const char *end;
+
+ end = g_utf8_find_prev_char (name, name + 64);
+ g_assert (end != NULL);
+ return g_strndup (name, end - name);
+}
+
+static char *
+get_share_name (void)
+{
+ static char *name = NULL;
+ const char *host_name;
+ char *str;
+
+ if (name == NULL) {
+ host_name = g_get_host_name ();
+ if (strcmp (host_name, "localhost") == 0) {
+ /* Translators: The %s will get filled in with the user name
+ of the user, to form a genitive. If this is difficult to
+ translate correctly so that it will work correctly in your
+ language, you may use something equivalent to
+ "Public files of %s", or leave out the %s altogether.
+ In the latter case, please put "%.0s" somewhere in the string,
+ which will match the user name string passed by the C code,
+ but not put the user name in the final string. This is to
+ avoid the warning that msgfmt might otherwise generate. */
+ name = g_strdup_printf (_("%s's public files"), g_get_user_name ());
+ } else {
+ /* Translators: This is similar to the string before, only it
+ has the hostname in it too. */
+ name = g_strdup_printf (_("%s's public files on %s"),
+ g_get_user_name (),
+ host_name);
+ }
+
+ }
+ /* And truncate */
+ if (strlen (name) < AVAHI_LABEL_MAX)
+ return name;
+
+ str = truncate_name (name);
+ g_free (name);
+ name = str;
+
+ return name;
+}
+
+#ifdef HAVE_DBUS_1_1
+static void
+init_dbus() {
+ /* The only use we make of D-BUS is to fetch the session BUS ID so we can export
+ * it via mDNS, so we connect and then immediately disconnect. If we were using
+ * the D-BUS session BUS for something persistent, the following code should use
+ * dbus_bus_get() and skip the shutdown. (Avahi uses the D-BUS _system_ bus
+ * internally.)
+ */
+
+ DBusError derror;
+ DBusConnection *connection;
+
+ dbus_error_init(&derror);
+
+ connection = dbus_bus_get_private(DBUS_BUS_SESSION, &derror);
+ if (connection == NULL) {
+ g_printerr("Failed to connect to session bus: %s", derror.message);
+ dbus_error_free(&derror);
+ return;
+ }
+
+ dbus_session_id = dbus_bus_get_id(connection, &derror);
+ if (dbus_session_id == NULL) {
+ /* This can happen if the D-BUS library has been upgraded to 1.1, but the
+ * user's session hasn't yet been restarted
+ */
+ g_printerr("Failed to get session BUS ID: %s", derror.message);
+ dbus_error_free(&derror);
+ }
+
+ dbus_connection_set_exit_on_disconnect(connection, FALSE);
+ dbus_connection_close(connection);
+ dbus_connection_unref(connection);
+}
+#endif
+
+static void
+ensure_conf_dir (void)
+{
+ char *dirname;
+
+ dirname = g_build_filename (g_get_user_config_dir (), "user-share", NULL);
+ g_mkdir_with_parents (dirname, 0755);
+ g_free (dirname);
+}
+
+static void
+httpd_child_setup (gpointer user_data)
+{
+#ifdef HAVE_SELINUX
+ char *mycon;
+ /* If selinux is enabled, avoid transitioning to the httpd_t context,
+ as this normally means you can't read the users homedir. */
+ if (is_selinux_enabled()) {
+ if (getcon (&mycon) < 0) {
+ abort ();
+ }
+ if (setexeccon (mycon) < 0)
+ abort ();
+ freecon (mycon);
+ }
+#endif
+}
+
+static const char *known_httpd_locations [] = {
+ HTTPD_PROGRAM,
+ "/usr/sbin/httpd",
+ "/usr/sbin/httpd2",
+ "/usr/sbin/apache2",
+ NULL
+};
+
+static char*
+get_httpd_program ()
+{
+ int i;
+
+ for (i = 0; known_httpd_locations[i]; i++) {
+ if (known_httpd_locations[i][0] == '\0') {
+ /* empty string as first element, happens when
+ * configured --with-httpd=auto */
+ continue;
+ }
+ if (g_file_test (known_httpd_locations[i], G_FILE_TEST_IS_EXECUTABLE)
+ && ! g_file_test (known_httpd_locations[i], G_FILE_TEST_IS_DIR)) {
+ return g_strdup (known_httpd_locations[i]);
+ }
+ }
+ return NULL;
+}
+
+static const char *known_httpd_modules_locations [] = {
+ HTTPD_MODULES_PATH,
+ "/etc/httpd/modules",
+ "/usr/lib/apache2/modules",
+ NULL
+};
+
+static gchar*
+get_httpd_modules_path ()
+{
+ int i;
+
+ for (i = 0; known_httpd_modules_locations[i]; i++) {
+ if (known_httpd_modules_locations[i][0] == '\0') {
+ /* empty string as first element, happens when
+ * configured --with-httpd=auto */
+ continue;
+ }
+ if (g_file_test (known_httpd_modules_locations[i], G_FILE_TEST_IS_EXECUTABLE)
+ && g_file_test (known_httpd_modules_locations[i], G_FILE_TEST_IS_DIR)) {
+ return g_strdup (known_httpd_modules_locations[i]);
+ }
+ }
+ return NULL;
+}
+
+static GRegex *version_regex = NULL;
+
+static char*
+get_httpd_config (const char *httpd_program)
+{
+ gchar *standard_output;
+ gchar *cmd_line;
+ GMatchInfo *match_info;
+ gchar *version_number = NULL;
+ gchar *config;
+
+ cmd_line = g_strdup_printf ("%s -v", httpd_program);
+ if (! g_spawn_command_line_sync (cmd_line, &standard_output, NULL, NULL, NULL)) {
+ g_free (cmd_line);
+ return NULL;
+ }
+ g_free (cmd_line);
+
+ if (version_regex == NULL) {
+ version_regex = g_regex_new ("\\d\\.\\d", 0, 0, NULL);
+ }
+
+ if (g_regex_match (version_regex, standard_output, 0, &match_info)) {
+ while (g_match_info_matches (match_info)) {
+ version_number = g_match_info_fetch (match_info, 0);
+ break;
+ }
+ g_match_info_free (match_info);
+ g_free (standard_output);
+ } else {
+ /* Failed to parse httpd version number */
+ g_warning ("Could not parse '%s' as a version for httpd", standard_output);
+ g_free (standard_output);
+ /* assume it is 2.2 */
+ version_number = g_strdup ("2.2");
+ }
+
+ config = g_strdup_printf (HTTPD_CONFIG_TEMPLATE, version_number);
+ g_free (version_number);
+
+ return config;
+}
+
+static gboolean
+spawn_httpd (int port, pid_t *pid_out)
+{
+ char *free1, *free2, *free3, *free4, *free5, *free6, *free7, *free8, *free9;
+ gboolean res;
+ char *argv[10];
+ char *env[10];
+ int i;
+ gint status;
+ char *pid_filename;
+ char *pidfile;
+ GError *error;
+ gboolean got_pidfile;
+ MateConfClient *client;
+ char *str;
+ char *public_dir;
+
+ public_dir = lookup_public_dir ();
+ ensure_conf_dir ();
+
+ i = 0;
+ free1 = argv[i++] = get_httpd_program ();
+ if (argv[0] == NULL) {
+ fprintf (stderr, "error finding httpd server\n");
+ return FALSE;
+ }
+
+ argv[i++] = "-f";
+ free2 = argv[i++] = get_httpd_config (argv[0]);
+ argv[i++] = "-C";
+ free3 = argv[i++] = g_strdup_printf ("Listen %d", port);
+
+ client = mateconf_client_get_default ();
+ str = mateconf_client_get_string (client,
+ FILE_SHARING_REQUIRE_PASSWORD, NULL);
+
+ if (str && strcmp (str, "never") == 0) {
+ /* Do nothing */
+ } else if (str && strcmp (str, "on_write") == 0){
+ argv[i++] = "-D";
+ argv[i++] = "RequirePasswordOnWrite";
+ } else {
+ /* always, or safe fallback */
+ argv[i++] = "-D";
+ argv[i++] = "RequirePasswordAlways";
+ }
+ g_free (str);
+ g_object_unref (client);
+
+ argv[i] = NULL;
+
+ i = 0;
+ free4 = env[i++] = g_strdup_printf ("HOME=%s", g_get_home_dir());
+ free5 = env[i++] = g_strdup_printf ("XDG_PUBLICSHARE_DIR=%s", public_dir);
+ free6 = env[i++] = g_strdup_printf ("XDG_CONFIG_HOME=%s", g_get_user_config_dir ());
+ free7 = env[i++] = g_strdup_printf ("GUS_SHARE_NAME=%s", get_share_name ());
+ free8 = env[i++] = g_strdup_printf ("GUS_LOGIN_LABEL=%s", "Please log in as the user guest");
+ free9 = env[i++] = g_strdup_printf ("HTTP_MODULES_PATH=%s",get_httpd_modules_path ());
+ env[i++] = "LANG=C";
+ env[i] = NULL;
+
+ pid_filename = g_build_filename (g_get_user_config_dir (), "user-share", "pid", NULL);
+
+ /* Remove pid file before spawning to avoid races with child and old pidfile */
+ unlink (pid_filename);
+
+ error = NULL;
+ res = g_spawn_sync (g_get_home_dir(),
+ argv, env, 0,
+ httpd_child_setup, NULL,
+ NULL, NULL,
+ &status,
+ &error);
+ g_free (free1);
+ g_free (free2);
+ g_free (free3);
+ g_free (free4);
+ g_free (free5);
+ g_free (free6);
+ g_free (free7);
+ g_free (free8);
+ g_free (free9);
+ g_free (public_dir);
+
+ if (!res) {
+ fprintf (stderr, "error spawning httpd: %s\n",
+ error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ if (status != 0) {
+ g_free (pid_filename);
+ return FALSE;
+ }
+
+ got_pidfile = FALSE;
+ error = NULL;
+ for (i = 0; i < 5; i++) {
+ if (error != NULL)
+ g_error_free (error);
+ error = NULL;
+ if (g_file_get_contents (pid_filename, &pidfile, NULL, &error)) {
+ got_pidfile = TRUE;
+ *pid_out = atoi (pidfile);
+ g_free (pidfile);
+ break;
+ }
+ sleep (1);
+ }
+
+ g_free (pid_filename);
+
+ if (!got_pidfile) {
+ fprintf (stderr, "error opening httpd pidfile: %s\n", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void
+kill_httpd (void)
+{
+ if (httpd_pid != 0) {
+ kill (httpd_pid, SIGTERM);
+
+ /* Allow child time to die, we can't waitpid, because its
+ not a direct child */
+ sleep (1);
+ }
+ httpd_pid = 0;
+}
+
+void
+http_up (void)
+{
+ guint port;
+
+ port = get_port ();
+ if (!spawn_httpd (port, &httpd_pid)) {
+ fprintf (stderr, "spawning httpd failed\n");
+ }
+}
+
+void
+http_down (void)
+{
+ kill_httpd ();
+}
+
+gboolean
+http_init (void)
+{
+#ifdef HAVE_DBUS_1_1
+ init_dbus();
+#endif
+
+ return TRUE;
+}
+
+pid_t
+http_get_pid (void)
+{
+ return httpd_pid;
+}