Example #1
0
static void
send_login_html (CockpitWebResponse *response,
                 CockpitHandlerData *ws)
{
  static const gchar *marker = "<head>";

  CockpitWebFilter *filter;
  GBytes *environment;
  GError *error = NULL;
  GBytes *bytes;

  GBytes *url_bytes = NULL;
  CockpitWebFilter *filter2 = NULL;
  const gchar *url_root = NULL;
  gchar *base;

  environment = build_environment (ws->os_release);
  filter = cockpit_web_inject_new (marker, environment, 1);
  g_bytes_unref (environment);
  cockpit_web_response_add_filter (response, filter);
  g_object_unref (filter);

  url_root = cockpit_web_response_get_url_root (response);
  if (url_root)
    base = g_strdup_printf ("<base href=\"%s/\">", url_root);
  else
    base = g_strdup ("<base href=\"/\">");

  url_bytes = g_bytes_new_take (base, strlen(base));
  filter2 = cockpit_web_inject_new (marker, url_bytes, 1);
  g_bytes_unref (url_bytes);
  cockpit_web_response_add_filter (response, filter2);
  g_object_unref (filter2);

  cockpit_web_response_set_cache_type (response, COCKPIT_WEB_RESPONSE_NO_CACHE);

  bytes = cockpit_web_response_negotiation (ws->login_html, NULL, NULL, NULL, &error);
  if (error)
    {
      g_message ("%s", error->message);
      cockpit_web_response_error (response, 500, NULL, NULL);
      g_error_free (error);
    }
  else if (!bytes)
    {
      cockpit_web_response_error (response, 404, NULL, NULL);
    }
  else
    {
      /* The login Content-Security-Policy allows the page to have inline <script> and <style> tags. */
      cockpit_web_response_headers (response, 200, "OK", -1,
                                    "Content-Security-Policy",
                                    "default-src 'self' 'unsafe-inline'; connect-src 'self' ws: wss:",
                                    NULL);
      if (cockpit_web_response_queue (response, bytes))
        cockpit_web_response_complete (response);

      g_bytes_unref (bytes);
    }
}
Example #2
0
static void
handle_resource (CockpitHandlerData *data,
                 CockpitWebService *service,
                 const gchar *path,
                 GHashTable *headers,
                 CockpitWebResponse *response)
{
  gchar *where;

  where = cockpit_web_response_pop_path (response);
  if (where && (where[0] == '@' || where[0] == '$') && where[1] != '\0')
    {
      if (service)
        {
          cockpit_channel_response_serve (service, headers, response, where,
                                          cockpit_web_response_get_path (response));
        }
      else if (g_str_has_suffix (path, ".html"))
        {
          send_login_html (response, data);
        }
      else
        {
          cockpit_web_response_error (response, 401, NULL, NULL);
        }
    }
  else
    {
      cockpit_web_response_error (response, 404, NULL, NULL);
    }

  g_free (where);
}
void
cockpit_channel_response_open (CockpitWebService *service,
                               GHashTable *in_headers,
                               CockpitWebResponse *response,
                               JsonObject *open)
{
  CockpitTransport *transport;
  WebSocketDataType data_type;
  GHashTable *headers;
  const gchar *content_type;
  const gchar *content_disposition;

  /* Parse the external */
  if (!cockpit_web_service_parse_external (open, &content_type, &content_disposition, NULL))
    {
      cockpit_web_response_error (response, 400, NULL, "Bad channel request");
      return;
    }

  transport = cockpit_web_service_ensure_transport (service, open);
  if (!transport)
    {
      cockpit_web_response_error (response, 502, NULL, "Failed to open channel transport");
      return;
    }

  headers = cockpit_web_server_new_table ();

  if (content_disposition)
    g_hash_table_insert (headers, g_strdup ("Content-Disposition"), g_strdup (content_disposition));

  if (!json_object_has_member (open, "binary"))
    json_object_set_string_member (open, "binary", "raw");

  if (!content_type)
    {
      if (!cockpit_web_service_parse_binary (open, &data_type))
        g_return_if_reached ();
      if (data_type == WEB_SOCKET_DATA_TEXT)
        content_type = "text/plain";
      else
        content_type = "application/octet-stream";
    }
  g_hash_table_insert (headers, g_strdup ("Content-Type"), g_strdup (content_type));

  /* We shouldn't need to send this part further */
  json_object_remove_member (open, "external");

  cockpit_channel_response_create (service, response, transport, NULL, headers, open);
  g_hash_table_unref (headers);
}
Example #4
0
/**
 * cockpit_web_response_error:
 * @self: the response
 * @headers: headers to include or NULL
 * @error: the error
 *
 * Send an error message with a basic HTML page containing
 * the error.
 */
void
cockpit_web_response_gerror (CockpitWebResponse *self,
                             GHashTable *headers,
                             GError *error)
{
  int code;

  g_return_if_fail (COCKPIT_IS_WEB_RESPONSE (self));

  if (g_error_matches (error,
                       COCKPIT_ERROR, COCKPIT_ERROR_AUTHENTICATION_FAILED))
    code = 401;
  else if (g_error_matches (error,
                       COCKPIT_ERROR, COCKPIT_ERROR_PERMISSION_DENIED))
    code = 403;
  else if (g_error_matches (error,
                            G_IO_ERROR, G_IO_ERROR_INVALID_DATA))
    code = 400;
  else if (g_error_matches (error,
                            G_IO_ERROR, G_IO_ERROR_NO_SPACE))
    code = 413;
  else
    code = 500;

  cockpit_web_response_error (self, code, headers, "%s", error->message);
}
Example #5
0
gboolean
cockpit_handler_deauthorize (CockpitWebServer *server,
                             CockpitWebServerRequestType reqtype,
                             const gchar *path,
                             GHashTable *headers,
                             GBytes *input,
                             CockpitWebResponse *response,
                             CockpitHandlerData *ws)
{
  CockpitWebService *service;
  CockpitCreds *creds;
  const gchar *body;
  GBytes *bytes;

  service = cockpit_auth_check_cookie (ws->auth, headers);
  if (!service)
    {
      cockpit_web_response_error (response, 401, NULL, "Unauthorized");
      return TRUE;
    }

  /* Poison the creds, so they no longer work for new reauthorization */
  creds = cockpit_web_service_get_creds (service);
  cockpit_creds_poison (creds);
  g_object_unref (service);

  body ="<html><head><title>Deauthorized</title></head>"
    "<body>Deauthorized</body></html>";

  bytes = g_bytes_new_static (body, strlen (body));
  cockpit_web_response_content (response, NULL, bytes, NULL);
  g_bytes_unref (bytes);
  return TRUE;
}
static gboolean
on_httpstream_control (CockpitTransport *transport,
                       const gchar *command,
                       const gchar *channel,
                       JsonObject *options,
                       GBytes *message,
                       CockpitChannelResponse *chesp)
{
  gint64 status;
  const gchar *reason;

  if (!channel || !g_str_equal (channel, chesp->channel))
    return FALSE; /* not handled */

  if (g_str_equal (command, "response"))
    {
      if (parse_httpstream_response (chesp, options, &status, &reason))
        {
          if (!ensure_headers (chesp, status, reason))
            g_return_val_if_reached (FALSE);
        }
      else
        {
          cockpit_web_response_error (chesp->response, 500, NULL, NULL);
        }
      return TRUE;
    }

  return on_transport_control (transport, command, channel, options, message, chesp);
}
Example #7
0
gboolean
cockpit_handler_resource (CockpitWebServer *server,
                          const gchar *path,
                          GHashTable *headers,
                          CockpitWebResponse *response,
                          CockpitHandlerData *ws)
{
  CockpitWebService *service;

  if (g_str_has_prefix (path, "/cockpit/static/"))
    {
      cockpit_web_response_file (response, path + 16, TRUE, ws->static_roots);
      return TRUE;
    }

  service = cockpit_auth_check_cookie (ws->auth, headers);
  if (service)
    {
      cockpit_web_service_resource (service, headers, response);
      g_object_unref (service);
    }
  else if (g_str_equal (path, "/") || g_str_has_suffix (path, ".html"))
    {
      send_login_html (response, ws);
    }
  else
    {
      cockpit_web_response_error (response, 401, NULL, NULL);
    }

  return TRUE;
}
Example #8
0
static void
handle_shell (CockpitHandlerData *data,
              CockpitWebService *service,
              const gchar *path,
              GHashTable *headers,
              CockpitWebResponse *response)
{
  gboolean valid;
  const gchar *shell_path;

  /* Check if a valid path for a shell to be served at */
  valid = g_str_equal (path, "/") ||
          g_str_has_prefix (path, "/@") ||
          strspn (path + 1, COCKPIT_RESOURCE_PACKAGE_VALID) == strcspn (path + 1, "/");

  if (g_str_has_prefix (path, "/@/") || g_str_has_prefix (path, "//"))
    valid = FALSE;

  if (!valid)
    {
      cockpit_web_response_error (response, 404, NULL, NULL);
    }
  else if (service)
    {
      shell_path = cockpit_conf_string ("WebService", "Shell");
      cockpit_channel_response_serve (service, headers, response, NULL,
                                      shell_path ? shell_path : cockpit_ws_shell_component);
    }
  else
    {
      send_login_html (response, data);
    }
}
Example #9
0
gboolean
cockpit_handler_default (CockpitWebServer *server,
                         const gchar *path,
                         GHashTable *headers,
                         CockpitWebResponse *response,
                         CockpitHandlerData *data)
{
  CockpitWebService *service;
  const gchar *remainder = NULL;
  gboolean resource;

  path = cockpit_web_response_get_path (response);
  g_return_val_if_fail (path != NULL, FALSE);

  resource = g_str_has_prefix (path, "/cockpit/") ||
             g_str_has_prefix (path, "/cockpit+") ||
             g_str_equal (path, "/cockpit");

  // Check for auth
  service = cockpit_auth_check_cookie (data->auth, path, headers);

  /* Stuff in /cockpit or /cockpit+xxx */
  if (resource)
    {
      cockpit_web_response_skip_path (response);
      remainder = cockpit_web_response_get_path (response);

      if (!remainder)
        {
          cockpit_web_response_error (response, 404, NULL, NULL);
          return TRUE;
        }
      else if (g_str_has_prefix (remainder, "/static/"))
        {
          cockpit_branding_serve (service, response, path, remainder + 8,
                                  data->os_release, data->branding_roots);
          return TRUE;
        }
    }

  if (resource)
    {
      if (g_str_equal (remainder, "/login"))
        {
          handle_login (data, service, path, headers, response);
        }
      else
        {
          handle_resource (data, service, path, headers, response);
        }
    }
  else
    {
      handle_shell (data, service, path, headers, response);
    }

  return TRUE;
}
Example #10
0
static gboolean
on_httpstream_recv (CockpitTransport *transport,
                    const gchar *channel,
                    GBytes *payload,
                    CockpitChannelResponse *chesp)
{
  GError *error = NULL;
  JsonObject *object;
  gint64 status;
  const gchar *reason;

  if (!channel || !g_str_equal (channel, chesp->channel))
    return FALSE;

  g_return_val_if_fail (cockpit_web_response_get_state (chesp->response) == COCKPIT_WEB_RESPONSE_READY, FALSE);

  /* First response payload message is meta data, then switch to actual data */
  g_signal_handler_disconnect (chesp->transport, chesp->transport_recv);
  chesp->transport_recv = g_signal_connect (chesp->transport, "recv", G_CALLBACK (on_transport_recv), chesp);

  object = cockpit_json_parse_bytes (payload, &error);
  if (error)
    {
      g_warning ("%s: couldn't parse http-stream1 header payload: %s", chesp->logname, error->message);
      cockpit_web_response_error (chesp->response, 500, NULL, NULL);
      g_error_free (error);
      return TRUE;
    }

  if (parse_httpstream_response (chesp, object, &status, &reason))
    {
      if (!ensure_headers (chesp, status, reason))
        g_return_val_if_reached (FALSE);
    }
  else
    {
      cockpit_web_response_error (chesp->response, 500, NULL, NULL);
    }

  json_object_unref (object);
  return TRUE;
}
Example #11
0
static gboolean
cockpit_web_server_default_handle_resource (CockpitWebServer *self,
                                            const gchar *path,
                                            GHashTable *headers,
                                            CockpitWebResponse *response)
{
  if (self->document_roots)
    cockpit_web_response_file (response, path, FALSE, (const gchar **)self->document_roots);
  else
    cockpit_web_response_error (response, 404, NULL, NULL);
  return TRUE;
}
Example #12
0
static void
send_login_html (CockpitWebResponse *response,
                 CockpitHandlerData *ws)
{
  GHashTable *headers = NULL;
  GList *l, *output = NULL;
  gchar *login_html;
  GMappedFile *file;
  GError *error = NULL;
  GBytes *body = NULL;
  gsize length;

  login_html = g_build_filename (ws->static_roots[0], "login.html", NULL);
  file = g_mapped_file_new (login_html, FALSE, &error);
  if (file == NULL)
    {
      g_warning ("%s: %s", login_html, error->message);
      cockpit_web_response_error (response, 500, NULL, NULL);
      g_clear_error (&error);
      goto out;
    }

  body = g_mapped_file_get_bytes (file);
  output = cockpit_template_expand (body, substitute_environment, ws->os_release);

  length = 0;
  for (l = output; l != NULL; l = g_list_next (l))
    length += g_bytes_get_size (l->data);

  headers = cockpit_web_server_new_table ();
  g_hash_table_insert (headers, g_strdup ("Content-Type"), g_strdup ("text/html; charset=utf8"));

  cockpit_web_response_headers_full (response, 200, "OK", length, headers);

  for (l = output; l != NULL; l = g_list_next (l))
    {
      if (!cockpit_web_response_queue (response, l->data))
        break;
    }
  if (l == NULL)
    cockpit_web_response_complete (response);

out:
  g_list_free_full (output, (GDestroyNotify)g_bytes_unref);
  if (headers)
    g_hash_table_unref (headers);
  g_free (login_html);
  if (body)
    g_bytes_unref (body);
  if (file)
    g_mapped_file_unref (file);
}
Example #13
0
static void
respond_with_error (const gchar *original_path,
                    const gchar *path,
                    GIOStream *io_stream,
                    GHashTable *headers,
                    guint status,
                    const gchar *message)
{
  CockpitWebResponse *response;

  response = cockpit_web_response_new (io_stream, original_path, path, NULL, headers);
  cockpit_web_response_error (response, status, NULL, "%s", message);
  g_object_unref (response);
}
static void
respond_with_error (const gchar *original_path,
                    const gchar *path,
                    GIOStream *io_stream,
                    gboolean for_tls_proxy,
                    GHashTable *headers,
                    guint status,
                    const gchar *message)
{
  CockpitWebResponse *response;

  response = cockpit_web_response_new (io_stream, original_path, path, NULL, headers,
                                       for_tls_proxy ? COCKPIT_WEB_RESPONSE_FOR_TLS_PROXY : COCKPIT_WEB_RESPONSE_NONE);
  cockpit_web_response_error (response, status, NULL, "%s", message);
  g_object_unref (response);
}
Example #15
0
gboolean
cockpit_handler_login (CockpitWebServer *server,
                       CockpitWebServerRequestType reqtype,
                       const gchar *path,
                       GHashTable *headers,
                       GBytes *input,
                       CockpitWebResponse *response,
                       CockpitHandlerData *ws)
{
  CockpitWebService *service;
  gchar *remote_peer = NULL;
  GIOStream *io_stream;
  LoginResponse *lr;

  lr = g_new0 (LoginResponse, 1);
  lr->response = g_object_ref (response);
  lr->headers = cockpit_web_server_new_table ();

  if (reqtype == COCKPIT_WEB_SERVER_REQUEST_GET)
    {
      service = cockpit_auth_check_cookie (ws->auth, headers);
      if (service == NULL)
        {
          cockpit_web_response_error (response, 401, NULL, NULL);
          login_response_free (lr);
        }
      else
        {
          cockpit_web_service_modules (service, "localhost", on_login_modules, lr);
          g_object_unref (service);
          /* no response yet */
        }
    }
  else if (reqtype == COCKPIT_WEB_SERVER_REQUEST_POST)
    {
      io_stream = cockpit_web_response_get_stream (response);
      remote_peer = get_remote_address (io_stream);
      cockpit_auth_login_async (ws->auth, headers, input, remote_peer,
                                on_login_complete, lr);
      g_free (remote_peer);
      /* no response yet */
    }

  return TRUE;
}
Example #16
0
static void
process_delayed_reply (CockpitRequest *request,
                       const gchar *path,
                       GHashTable *headers)
{
  CockpitWebResponse *response;
  const gchar *host;
  const gchar *body;
  GBytes *bytes;
  gsize length;
  gchar *url;

  g_assert (request->delayed_reply > 299);

  response = cockpit_web_response_new (request->io, NULL, NULL, headers);
  g_signal_connect_data (response, "done", G_CALLBACK (on_web_response_done),
                         g_object_ref (request->web_server), (GClosureNotify)g_object_unref, 0);

  if (request->delayed_reply == 301)
    {
      body = "<html><head><title>Moved</title></head>"
        "<body>Please use TLS</body></html>";
      host = g_hash_table_lookup (headers, "Host");
      url = g_strdup_printf ("https://%s%s",
                             host != NULL ? host : "", path);
      length = strlen (body);
      cockpit_web_response_headers (response, 301, "Moved Permanently", length,
                                    "Content-Type", "text/html",
                                    "Location", url,
                                    NULL);
      g_free (url);
      bytes = g_bytes_new_static (body, length);
      if (cockpit_web_response_queue (response, bytes))
        cockpit_web_response_complete (response);
      g_bytes_unref (bytes);
    }
  else
    {
      cockpit_web_response_error (response, request->delayed_reply, NULL, NULL);
    }

  g_object_unref (response);
}
Example #17
0
static gboolean
mock_http_qs (CockpitWebResponse *response)
{
  const gchar *qs;
  GBytes *bytes;

  qs = cockpit_web_response_get_query (response);
  if (!qs)
    {
      cockpit_web_response_error (response, 400, NULL, "No query string");
    }
  else
    {
      bytes = g_bytes_new (qs, strlen (qs));
      cockpit_web_response_content (response, NULL, bytes, NULL);
      g_bytes_unref (bytes);
    }

  return TRUE;
}
Example #18
0
gboolean
cockpit_handler_resource (CockpitWebService *server,
                          CockpitWebServerRequestType reqtype,
                          const gchar *path,
                          GHashTable *headers,
                          GBytes *input,
                          CockpitWebResponse *response,
                          CockpitHandlerData *ws)
{
  CockpitWebService *service;

  service = cockpit_auth_check_cookie (ws->auth, headers);
  if (service)
    {
      cockpit_web_service_resource (service, response);
      g_object_unref (service);
    }
  else
    {
      cockpit_web_response_error (response, 401, NULL, NULL);
    }

  return TRUE;
}
Example #19
0
/**
 * cockpit_web_response_file:
 * @response: the response
 * @path: escaped path, or NULL to get from response
 * @roots: directories to look for file in
 *
 * Serve a file from disk as an HTTP response.
 */
void
cockpit_web_response_file (CockpitWebResponse *response,
                           const gchar *escaped,
                           gboolean cache_forever,
                           const gchar **roots)
{
  const gchar *cache_control;
  GError *error = NULL;
  gchar *unescaped = NULL;
  gchar *path = NULL;
  GMappedFile *file = NULL;
  const gchar *root;
  GBytes *body;

  g_return_if_fail (COCKPIT_IS_WEB_RESPONSE (response));

  if (!escaped)
    escaped = cockpit_web_response_get_path (response);

  g_return_if_fail (escaped != NULL);

  /* Someone is trying to escape the root directory, or access hidden files? */
  unescaped = g_uri_unescape_string (escaped, NULL);
  if (strstr (unescaped, "/.") || strstr (unescaped, "../") || strstr (unescaped, "//"))
    {
      g_debug ("%s: invalid path request", escaped);
      cockpit_web_response_error (response, 404, NULL, "Not Found");
      goto out;
    }

again:
  root = *(roots++);
  if (root == NULL)
    {
      cockpit_web_response_error (response, 404, NULL, "Not Found");
      goto out;
    }

  g_free (path);
  path = g_build_filename (root, unescaped, NULL);

  if (g_file_test (path, G_FILE_TEST_IS_DIR))
    {
      cockpit_web_response_error (response, 403, NULL, "Directory Listing Denied");
      goto out;
    }

  /* As a double check of above behavior */
  g_assert (path_has_prefix (path, root));

  g_clear_error (&error);
  file = g_mapped_file_new (path, FALSE, &error);
  if (file == NULL)
    {
      if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT) ||
          g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NAMETOOLONG))
        {
          g_debug ("%s: file not found in root: %s", escaped, root);
          goto again;
        }
      else if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_PERM) ||
               g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_ACCES) ||
               g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_ISDIR))
        {
          cockpit_web_response_error (response, 403, NULL, "Access denied");
          goto out;
        }
      else
        {
          g_warning ("%s: %s", path, error->message);
          cockpit_web_response_error (response, 500, NULL, "Internal server error");
          goto out;
        }
    }

  body = g_mapped_file_get_bytes (file);

  cache_control = cache_forever ? "max-age=31556926, public" : NULL;
  cockpit_web_response_headers (response, 200, "OK", g_bytes_get_size (body),
                                "Cache-Control", cache_control,
                                NULL);

  if (cockpit_web_response_queue (response, body))
    cockpit_web_response_complete (response);

  g_bytes_unref (body);

out:
  g_free (unescaped);
  g_clear_error (&error);
  g_free (path);
  if (file)
    g_mapped_file_unref (file);
}
Example #20
0
void
cockpit_channel_response_serve (CockpitWebService *service,
                                GHashTable *in_headers,
                                CockpitWebResponse *response,
                                const gchar *where,
                                const gchar *path)
{
  CockpitChannelResponse *chesp = NULL;
  CockpitTransport *transport = NULL;
  CockpitCacheType cache_type = COCKPIT_WEB_RESPONSE_CACHE_PRIVATE;
  const gchar *host = NULL;
  const gchar *pragma;
  gchar *quoted_etag = NULL;
  GHashTable *out_headers = NULL;
  gchar *val = NULL;
  gboolean handled = FALSE;
  GHashTableIter iter;
  const gchar *checksum;
  JsonObject *object = NULL;
  JsonObject *heads;
  gchar *channel = NULL;
  gchar *language = NULL;
  gpointer key;
  gpointer value;

  g_return_if_fail (COCKPIT_IS_WEB_SERVICE (service));
  g_return_if_fail (in_headers != NULL);
  g_return_if_fail (COCKPIT_IS_WEB_RESPONSE (response));
  g_return_if_fail (path != NULL);

  if (where == NULL)
    {
      host = "localhost";
    }
  else if (where[0] == '@')
    {
      host = where + 1;
    }
  else if (where[0] == '$')
    {
      quoted_etag = g_strdup_printf ("\"%s\"", where);
      cache_type = COCKPIT_WEB_RESPONSE_CACHE_FOREVER;
      pragma = g_hash_table_lookup (in_headers, "Pragma");

      if ((!pragma || !strstr (pragma, "no-cache")) &&
          (g_strcmp0 (g_hash_table_lookup (in_headers, "If-None-Match"), where) == 0 ||
           g_strcmp0 (g_hash_table_lookup (in_headers, "If-None-Match"), quoted_etag) == 0))
        {
          cockpit_web_response_headers (response, 304, "Not Modified", 0, "ETag", quoted_etag, NULL);
          cockpit_web_response_complete (response);
          handled = TRUE;
          goto out;
        }

      transport = cockpit_web_service_find_transport (service, where + 1);
      if (!transport)
        goto out;

      host = cockpit_web_service_get_host (service, transport);
      if (!host)
        {
          g_warn_if_reached ();
          goto out;
        }
    }
  else
    {
      goto out;
    }

  cockpit_web_response_set_cache_type (response, cache_type);
  object = cockpit_transport_build_json ("command", "open",
                                         "payload", "http-stream1",
                                         "internal", "packages",
                                         "method", "GET",
                                         "host", host,
                                         "path", path,
                                         "binary", "raw",
                                         NULL);

  if (!transport)
    {
      transport = cockpit_web_service_ensure_transport (service, object);
      if (!transport)
        goto out;
    }

  if (where)
    {
      /*
       * Maybe send back a redirect to the checksum url. We only do this if actually
       * accessing a file, and not a some sort of data like '/checksum', or a root path
       * like '/'
       */
      if (where[0] == '@' && strchr (path, '.'))
        {
          checksum = cockpit_web_service_get_checksum (service, transport);
          if (checksum)
            {
              handled = redirect_to_checksum_path (service, response, checksum, path);
              goto out;
            }
        }
    }

  out_headers = cockpit_web_server_new_table ();

  channel = cockpit_web_service_unique_channel (service);
  json_object_set_string_member (object, "channel", channel);

  if (quoted_etag)
    {
      /*
       * If we have a checksum, then use it as an ETag. It is intentional that
       * a cockpit-bridge version could (in the future) override this.
       */
      g_hash_table_insert (out_headers, g_strdup ("ETag"), quoted_etag);
      quoted_etag = NULL;
    }

  heads = json_object_new ();

  g_hash_table_iter_init (&iter, in_headers);
  while (g_hash_table_iter_next (&iter, &key, &value))
    {
      val = NULL;

      if (g_ascii_strcasecmp (key, "Host") == 0 ||
          g_ascii_strcasecmp (key, "Cookie") == 0 ||
          g_ascii_strcasecmp (key, "Referer") == 0 ||
          g_ascii_strcasecmp (key, "Connection") == 0 ||
          g_ascii_strcasecmp (key, "Pragma") == 0 ||
          g_ascii_strcasecmp (key, "Cache-Control") == 0 ||
          g_ascii_strcasecmp (key, "User-Agent") == 0 ||
          g_ascii_strcasecmp (key, "Accept-Charset") == 0 ||
          g_ascii_strcasecmp (key, "Accept-Ranges") == 0 ||
          g_ascii_strcasecmp (key, "Content-Length") == 0 ||
          g_ascii_strcasecmp (key, "Content-MD5") == 0 ||
          g_ascii_strcasecmp (key, "Content-Range") == 0 ||
          g_ascii_strcasecmp (key, "Range") == 0 ||
          g_ascii_strcasecmp (key, "TE") == 0 ||
          g_ascii_strcasecmp (key, "Trailer") == 0 ||
          g_ascii_strcasecmp (key, "Upgrade") == 0 ||
          g_ascii_strcasecmp (key, "Transfer-Encoding") == 0)
        continue;

      json_object_set_string_member (heads, key, value);
      g_free (val);
    }

  /* Parse the language out of the CockpitLang cookie */
  language = cockpit_web_server_parse_cookie (in_headers, "CockpitLang");
  if (language)
    json_object_set_string_member (heads, "Accept-Language", language);

  json_object_set_string_member (heads, "Host", host);
  json_object_set_object_member (object, "headers", heads);

  chesp = cockpit_channel_response_create (service, response, transport,
                                           cockpit_web_response_get_path (response),
                                           out_headers, object);

  if (!where)
    chesp->inject = cockpit_channel_inject_new (service, path);

  handled = TRUE;

out:
  g_free (language);
  if (object)
    json_object_unref (object);
  g_free (quoted_etag);
  if (out_headers)
    g_hash_table_unref (out_headers);
  g_free (channel);

  if (!handled)
    cockpit_web_response_error (response, 404, NULL, NULL);
}
Example #21
0
/**
 * cockpit_web_response_file:
 * @response: the response
 * @path: escaped path, or NULL to get from response
 * @roots: directories to look for file in
 *
 * Serve a file from disk as an HTTP response.
 */
void
cockpit_web_response_file (CockpitWebResponse *response,
                           const gchar *escaped,
                           gboolean cache_forever,
                           const gchar **roots)
{
  const gchar *cache_control;
  GError *error = NULL;
  gchar *unescaped;
  char *path = NULL;
  gchar *built = NULL;
  GMappedFile *file = NULL;
  const gchar *root;
  GBytes *body;

  g_return_if_fail (COCKPIT_IS_WEB_RESPONSE (response));

  if (!escaped)
    escaped = cockpit_web_response_get_path (response);

  g_return_if_fail (escaped != NULL);

again:
  root = *(roots++);
  if (root == NULL)
    {
      cockpit_web_response_error (response, 404, NULL, "Not Found");
      goto out;
    }

  unescaped = g_uri_unescape_string (escaped, NULL);
  built = g_build_filename (root, unescaped, NULL);
  g_free (unescaped);

  path = realpath (built, NULL);
  g_free (built);

  if (path == NULL)
    {
      if (errno == ENOENT || errno == ENOTDIR || errno == ELOOP || errno == ENAMETOOLONG)
        {
          g_debug ("%s: file not found in root: %s", escaped, root);
          goto again;
        }
      else if (errno == EACCES)
        {
          cockpit_web_response_error (response, 403, NULL, "Access Denied");
          goto out;
        }
      else
        {
          g_warning ("%s: resolving path failed: %m", escaped);
          cockpit_web_response_error (response, 500, NULL, "Internal Server Error");
          goto out;
        }
    }

  /* Double check that realpath() did the right thing */
  g_return_if_fail (strstr (path, "../") == NULL);
  g_return_if_fail (!g_str_has_suffix (path, "/.."));

  /* Someone is trying to escape the root directory */
  if (!path_has_prefix (path, root) &&
      !path_has_prefix (path, cockpit_web_exception_escape_root))
    {
      g_debug ("%s: request tried to escape the root directory: %s: %s", escaped, root, path);
      cockpit_web_response_error (response, 404, NULL, "Not Found");
      goto out;
    }

  if (g_file_test (path, G_FILE_TEST_IS_DIR))
    {
      cockpit_web_response_error (response, 403, NULL, "Directory Listing Denied");
      goto out;
    }

  file = g_mapped_file_new (path, FALSE, &error);
  if (file == NULL)
    {
      if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_PERM) ||
          g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_ACCES) ||
          g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_ISDIR))
        {
          cockpit_web_response_error (response, 403, NULL, "Access denied");
          g_clear_error (&error);
          goto out;
        }
      else
        {
          g_warning ("%s: %s", path, error->message);
          cockpit_web_response_error (response, 500, NULL, "Internal server error");
          g_clear_error (&error);
          goto out;
        }
    }

  body = g_mapped_file_get_bytes (file);

  cache_control = cache_forever ? "max-age=31556926, public" : NULL;
  cockpit_web_response_headers (response, 200, "OK", g_bytes_get_size (body),
                                "Cache-Control", cache_control,
                                NULL);

  if (cockpit_web_response_queue (response, body))
    cockpit_web_response_complete (response);

  g_bytes_unref (body);

out:
  free (path);
  if (file)
    g_mapped_file_unref (file);
}
Example #22
0
static void
send_login_html (CockpitWebResponse *response,
                 CockpitHandlerData *ws,
                 GHashTable *headers)
{
  static const gchar *marker = "<meta insert_dynamic_content_here>";

  CockpitWebFilter *filter;
  GBytes *environment;
  GError *error = NULL;
  GBytes *bytes;

  GBytes *url_bytes = NULL;
  CockpitWebFilter *filter2 = NULL;
  const gchar *url_root = NULL;
  gchar *base;

  gchar *language = NULL;
  gchar **languages = NULL;
  GBytes *po_bytes;
  CockpitWebFilter *filter3 = NULL;

  environment = build_environment (ws->os_release);
  filter = cockpit_web_inject_new (marker, environment, 1);
  g_bytes_unref (environment);
  cockpit_web_response_add_filter (response, filter);
  g_object_unref (filter);

  url_root = cockpit_web_response_get_url_root (response);
  if (url_root)
    base = g_strdup_printf ("<base href=\"%s/\">", url_root);
  else
    base = g_strdup ("<base href=\"/\">");

  url_bytes = g_bytes_new_take (base, strlen(base));
  filter2 = cockpit_web_inject_new (marker, url_bytes, 1);
  g_bytes_unref (url_bytes);
  cockpit_web_response_add_filter (response, filter2);
  g_object_unref (filter2);

  cockpit_web_response_set_cache_type (response, COCKPIT_WEB_RESPONSE_NO_CACHE);

  if (ws->login_po_html)
    {
      language = cockpit_web_server_parse_cookie (headers, "CockpitLang");
      if (!language)
        {
          languages = cockpit_web_server_parse_languages (headers, NULL);
          language = languages[0];
        }

      po_bytes = cockpit_web_response_negotiation (ws->login_po_html, NULL, language, NULL, &error);
      if (error)
        {
          g_message ("%s", error->message);
          g_clear_error (&error);
        }
      else
        {
          filter3 = cockpit_web_inject_new (marker, po_bytes, 1);
          g_bytes_unref (po_bytes);
          cockpit_web_response_add_filter (response, filter3);
          g_object_unref (filter3);
        }
    }

  bytes = cockpit_web_response_negotiation (ws->login_html, NULL, NULL, NULL, &error);
  if (error)
    {
      g_message ("%s", error->message);
      cockpit_web_response_error (response, 500, NULL, NULL);
      g_error_free (error);
    }
  else if (!bytes)
    {
      cockpit_web_response_error (response, 404, NULL, NULL);
    }
  else
    {
      /* The login Content-Security-Policy allows the page to have inline <script> and <style> tags. */
      cockpit_web_response_headers (response, 200, "OK", -1,
                                    "Content-Type",
                                    "text/html",
                                    "Content-Security-Policy",
                                    "default-src 'self' 'unsafe-inline'; connect-src 'self' ws: wss:",
                                    NULL);
      if (cockpit_web_response_queue (response, bytes))
        cockpit_web_response_complete (response);

      g_bytes_unref (bytes);
    }

  g_strfreev (languages);
}
Example #23
0
static void
cockpit_channel_response_close (CockpitChannelResponse *chesp,
                                const gchar *problem)
{
  CockpitWebResponding state;

  /* Ensure no more signals arrive about our response */
  g_signal_handler_disconnect (chesp->transport, chesp->transport_recv);
  g_signal_handler_disconnect (chesp->transport, chesp->transport_control);
  g_signal_handler_disconnect (chesp->transport, chesp->transport_closed);

  /* The web response should not yet be complete */
  state = cockpit_web_response_get_state (chesp->response);

  if (problem == NULL)
    {
      /* Closed without any data */
      if (state == COCKPIT_WEB_RESPONSE_READY)
        {
          ensure_headers (chesp, 204, "OK");
          cockpit_web_response_complete (chesp->response);
          g_debug ("%s: no content in external channel", chesp->logname);
        }
      else if (state < COCKPIT_WEB_RESPONSE_COMPLETE)
        {
          g_message ("%s: truncated data in external channel", chesp->logname);
          cockpit_web_response_abort (chesp->response);
        }
      else
        {
          g_debug ("%s: completed serving external channel", chesp->logname);
        }
    }
  else if (state == COCKPIT_WEB_RESPONSE_READY)
    {
      if (g_str_equal (problem, "not-found"))
        {
          g_debug ("%s: not found", chesp->logname);
          cockpit_web_response_error (chesp->response, 404, NULL, NULL);
        }
      else if (g_str_equal (problem, "no-host") ||
               g_str_equal (problem, "no-cockpit") ||
               g_str_equal (problem, "unknown-hostkey") ||
               g_str_equal (problem, "authentication-failed"))
        {
          g_debug ("%s: remote server unavailable: %s", chesp->logname, problem);
          cockpit_web_response_error (chesp->response, 502, NULL, NULL);
        }
      else
        {
          g_message ("%s: external channel failed: %s", chesp->logname, problem);
          cockpit_web_response_error (chesp->response, 500, NULL, NULL);
        }
    }
  else
    {
      if (g_str_equal (problem, "disconnected"))
        g_debug ("%s: failure while serving external channel: %s", chesp->logname, problem);
      else
        g_message ("%s: failure while serving external channel: %s", chesp->logname, problem);
      cockpit_web_response_abort (chesp->response);
    }

  g_object_unref (chesp->response);
  g_object_unref (chesp->transport);
  g_hash_table_unref (chesp->headers);
  cockpit_channel_inject_free (chesp->inject);
  json_object_unref (chesp->open);
  g_free (chesp->channel);
  g_free (chesp);
}
Example #24
0
static void
send_index_response (CockpitWebResponse *response,
                     CockpitWebService *service,
                     JsonObject *modules)
{
  GHashTable *out_headers;
  GError *error = NULL;
  GMappedFile *file = NULL;
  GBytes *body = NULL;
  GBytes *prefix = NULL;
  GBytes *environ = NULL;
  GBytes *suffix = NULL;
  gchar *index_html;
  const gchar *needle;
  const gchar *data;
  const gchar *pos;
  gsize needle_len;
  gsize length;
  gsize offset;

  /*
   * Since the index file cannot be properly cached, it can change on
   * each request, so we include full environment information directly
   * rather than making the client do another round trip later.
   *
   * If the caller is already logged in, then this is included in the
   * environment.
   */

  index_html = g_build_filename (cockpit_ws_static_directory, "index.html", NULL);
  file = g_mapped_file_new (index_html, FALSE, &error);
  if (file == NULL)
    {
      g_warning ("%s: %s", index_html, error->message);
      cockpit_web_response_error (response, 500, NULL, NULL);
      g_clear_error (&error);
      goto out;
    }

  body = g_mapped_file_get_bytes (file);
  data = g_bytes_get_data (body, &length);

  needle = "cockpit_environment_info";
  pos = g_strstr_len (data, length, needle);
  if (!pos)
    {
      g_warning ("couldn't find 'cockpit_environment_info' string in index.html");
      cockpit_web_response_error (response, 500, NULL, NULL);
      goto out;
    }

  environ = build_environment (service, modules);

  offset = (pos - data);
  prefix = g_bytes_new_from_bytes (body, 0, offset);

  needle_len = strlen (needle);
  suffix = g_bytes_new_from_bytes (body, offset + needle_len,
                                   length - (offset + needle_len));

  out_headers = cockpit_web_server_new_table ();
  g_hash_table_insert (out_headers, g_strdup ("Content-Type"), g_strdup ("text/html; charset=utf8"));
  cockpit_web_response_content (response, out_headers, prefix, environ, suffix, NULL);
  g_hash_table_unref (out_headers);

out:
  g_free (index_html);
  if (prefix)
    g_bytes_unref (prefix);
  if (body)
    g_bytes_unref (body);
  if (environ)
    g_bytes_unref (environ);
  if (suffix)
    g_bytes_unref (suffix);
  if (file)
    g_mapped_file_unref (file);
}
Example #25
0
gboolean
cockpit_handler_external (CockpitWebServer *server,
                          const gchar *original_path,
                          const gchar *path,
                          GIOStream *io_stream,
                          GHashTable *headers,
                          GByteArray *input,
                          CockpitHandlerData *ws)
{
  CockpitWebResponse *response = NULL;
  CockpitWebService *service = NULL;
  const gchar *segment = NULL;
  JsonObject *open = NULL;
  const gchar *query = NULL;
  CockpitCreds *creds;
  const gchar *expected;
  const gchar *upgrade;
  guchar *decoded;
  GBytes *bytes;
  gsize length;
  gsize seglen;

  /* The path must start with /cockpit+xxx/channel/csrftoken? or similar */
  if (path && path[0])
    segment = strchr (path + 1, '/');
  if (!segment)
    return FALSE;
  if (!g_str_has_prefix (segment, "/channel/"))
    return FALSE;
  segment += 9;

  /* Make sure we are authenticated, otherwise 404 */
  service = cockpit_auth_check_cookie (ws->auth, path, headers);
  if (!service)
    return FALSE;

  creds = cockpit_web_service_get_creds (service);
  g_return_val_if_fail (creds != NULL, FALSE);

  expected = cockpit_creds_get_csrf_token (creds);
  g_return_val_if_fail (expected != NULL, FALSE);

  /* The end of the token */
  query = strchr (segment, '?');
  if (query)
    {
      seglen = query - segment;
      query += 1;
    }
  else
    {
      seglen = strlen (segment);
      query = "";
    }

  /* No such path is valid */
  if (strlen (expected) != seglen || memcmp (expected, segment, seglen) != 0)
    {
      g_message ("invalid csrf token");
      return FALSE;
    }

  decoded = g_base64_decode (query, &length);
  if (decoded)
    {
      bytes = g_bytes_new_take (decoded, length);
      if (!cockpit_transport_parse_command (bytes, NULL, NULL, &open))
        {
          open = NULL;
          g_message ("invalid external channel query");
        }
      g_bytes_unref (bytes);
    }

  if (!open)
    {
      response = cockpit_web_response_new (io_stream, original_path, path, NULL, headers);
      cockpit_web_response_error (response, 400, NULL, NULL);
      g_object_unref (response);
    }
  else
    {
      upgrade = g_hash_table_lookup (headers, "Upgrade");
      if (upgrade && g_ascii_strcasecmp (upgrade, "websocket") == 0)
        {
          cockpit_channel_socket_open (service, open, original_path, path, io_stream, headers, input);
        }
      else
        {
          response = cockpit_web_response_new (io_stream, original_path, path, NULL, headers);
          cockpit_channel_response_open (service, headers, response, open);
          g_object_unref (response);
        }
      json_object_unref (open);
    }

  g_object_unref (service);

  return TRUE;
}
Example #26
0
/**
 * cockpit_web_response_file:
 * @response: the response
 * @path: escaped path, or NULL to get from response
 * @roots: directories to look for file in
 *
 * Serve a file from disk as an HTTP response.
 */
void
cockpit_web_response_file (CockpitWebResponse *response,
                           const gchar *escaped,
                           const gchar **roots)
{
  const gchar *csp_header;
  GError *error = NULL;
  gchar *unescaped = NULL;
  gchar *path = NULL;
  GMappedFile *file = NULL;
  const gchar *root;
  GBytes *body;

  g_return_if_fail (COCKPIT_IS_WEB_RESPONSE (response));

  if (!escaped)
    escaped = cockpit_web_response_get_path (response);

  g_return_if_fail (escaped != NULL);

  /* Someone is trying to escape the root directory, or access hidden files? */
  unescaped = g_uri_unescape_string (escaped, NULL);
  if (strstr (unescaped, "/.") || strstr (unescaped, "../") || strstr (unescaped, "//"))
    {
      g_debug ("%s: invalid path request", escaped);
      cockpit_web_response_error (response, 404, NULL, "Not Found");
      goto out;
    }

again:
  root = *(roots++);
  if (root == NULL)
    {
      cockpit_web_response_error (response, 404, NULL, "Not Found");
      goto out;
    }

  g_free (path);
  path = g_build_filename (root, unescaped, NULL);

  if (g_file_test (path, G_FILE_TEST_IS_DIR))
    {
      cockpit_web_response_error (response, 403, NULL, "Directory Listing Denied");
      goto out;
    }

  /* As a double check of above behavior */
  g_assert (path_has_prefix (path, root));

  g_clear_error (&error);
  file = g_mapped_file_new (path, FALSE, &error);
  if (file == NULL)
    {
      if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT) ||
          g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NAMETOOLONG))
        {
          g_debug ("%s: file not found in root: %s", escaped, root);
          goto again;
        }
      else if (g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_PERM) ||
               g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_ACCES) ||
               g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_ISDIR))
        {
          cockpit_web_response_error (response, 403, NULL, "Access denied");
          goto out;
        }
      else
        {
          g_warning ("%s: %s", path, error->message);
          cockpit_web_response_error (response, 500, NULL, "Internal server error");
          goto out;
        }
    }

  body = g_mapped_file_get_bytes (file);

  /*
   * The default Content-Security-Policy for .html files allows
   * the site to have inline <script> and <style> tags. This code
   * is not used when serving resources once logged in, only for
   * static resources when we don't yet have a session.
   */

  csp_header = NULL;
  if (g_str_has_suffix (unescaped, ".html"))
    csp_header = "Content-Security-Policy";

  cockpit_web_response_headers (response, 200, "OK", g_bytes_get_size (body),
                                csp_header, "default-src 'self' 'unsafe-inline'; connect-src 'self' ws: wss:",
                                NULL);

  if (cockpit_web_response_queue (response, body))
    cockpit_web_response_complete (response);

  g_bytes_unref (body);

out:
  g_free (unescaped);
  g_clear_error (&error);
  g_free (path);
  if (file)
    g_mapped_file_unref (file);
}