/*
* This file is part of Cockpit.
*
* Copyright (C) 2013 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see .
*/
#include "config.h"
#include "remotectl.h"
#include "cockpitcertificate.h"
#include "common/cockpitconf.h"
#include
#include
#include
#include
#include
#include
#include
#include
static int
locate_certificate (void)
{
GTlsCertificate *certificate = NULL;
gchar *path = NULL;
GError *error = NULL;
int ret = 1;
path = cockpit_certificate_locate (FALSE, &error);
if (path != NULL)
certificate = cockpit_certificate_load (path, &error);
if (certificate)
{
g_print ("certificate: %s\n", path);
g_object_unref (certificate);
ret = 0;
}
else
{
g_message ("%s", error->message);
g_error_free (error);
}
g_free (path);
return ret;
}
static int
set_cert_attributes (const gchar *path,
const gchar *user,
const gchar *group,
const gchar *selinux)
{
GError *error = NULL;
struct passwd *pwd = NULL;
struct group *gr = NULL;
gint status = 0;
mode_t mode;
int ret = 1;
const gchar *chcon_argv[] = {
PATH_CHCON,
"--type",
selinux,
"path-here",
NULL
};
if (!user)
user = "root";
if (g_strcmp0 (group, "") == 0)
group = NULL;
/* Resolve the user and group */
pwd = getpwnam (user);
if (pwd == NULL)
{
g_message ("couldn't lookup user: %s: %s", user, g_strerror (errno));
goto out;
}
if (group)
{
gr = getgrnam (group);
if (gr == NULL)
{
g_message ("couldn't lookup group: %s: %s", group, g_strerror (errno));
goto out;
}
}
/* If group specified then group readable */
mode = S_IRUSR | S_IWUSR;
if (gr)
mode |= S_IRGRP;
if (chmod (path, mode) < 0)
{
g_message ("couldn't set certificate permissions: %s: %s", path, g_strerror (errno));
goto out;
}
if (chown (path, pwd->pw_uid, gr ? gr->gr_gid : 0) < 0)
{
g_message ("couldn't set certificate ownership: %s: %s", path, g_strerror (errno));
goto out;
}
if (g_strcmp0 (selinux, "") == 0)
selinux = NULL;
if (selinux)
{
chcon_argv[3] = path;
g_spawn_sync (NULL, (gchar **)chcon_argv, NULL, G_SPAWN_DEFAULT, NULL, NULL, NULL, NULL, &status, &error);
if (!error)
g_spawn_check_exit_status (status, &error);
if (error)
{
g_message ("couldn't change SELinux type context '%s' for certificate: %s: %s",
selinux, path, error->message);
/* keep going, don't fail hard here */
}
}
ret = 0;
out:
g_clear_error (&error);
return ret;
}
static int
ensure_certificate (const gchar *user,
const gchar *group,
const gchar *selinux)
{
GError *error = NULL;
GTlsCertificate *certificate = NULL;
gchar *path = NULL;
gint ret = 1;
path = cockpit_certificate_locate (TRUE, &error);
if (path != NULL)
certificate = cockpit_certificate_load (path, &error);
if (!certificate)
{
g_message ("%s", error->message);
goto out;
}
ret = set_cert_attributes (path, user, group, selinux);
out:
g_clear_object (&certificate);
g_clear_error (&error);
g_free (path);
return ret;
}
static gint
cockpit_certificate_combine (gchar **pem_files,
const gchar *user,
const gchar *group,
const gchar *selinux)
{
const gchar * const* dirs = cockpit_conf_get_dirs ();
GError *error = NULL;
GTlsCertificate *certificate = NULL;
GString *combined = g_string_new ("");
gchar *cert_dir = NULL;
gchar *name = NULL;
gchar *spot = NULL;
gchar *target_name = NULL;
gchar *target_path = NULL;
gint ret = 1;
gint i;
cert_dir = g_build_filename (dirs[0], "cockpit", "ws-certs.d", NULL);
if (g_mkdir_with_parents (cert_dir, 0700) != 0)
{
g_message ("Error creating directory %s: %m", cert_dir);
goto out;
}
/* target file is named after the first file */
name = g_path_get_basename (pem_files[0]);
spot = strrchr (name, '.');
if (spot != NULL)
*spot = '\0';
target_name = g_strdup_printf ("%s.cert", name);
target_path = g_build_filename (cert_dir, target_name, NULL);
for (i = 0; pem_files[i] != NULL; i++)
{
gchar *data = NULL;
gsize length;
gboolean success = g_file_get_contents (pem_files[i], &data, &length, &error);
if (success)
g_string_append_printf (combined, "%s\n", data);
g_free (data);
if (!success)
goto out;
}
if (!g_file_set_contents (target_path, combined->str, combined->len, &error))
goto out;
g_debug ("Wrote to combined file %s", target_path);
certificate = cockpit_certificate_load (target_path, &error);
if (!certificate)
{
if (g_unlink (target_path) < 0)
g_message ("Failed to delete invalid certificate %s: %m", target_path);
goto out;
}
else
{
g_print ("generated combined certificate file: %s\n", target_path);
}
ret = set_cert_attributes (target_path, user, group, selinux);
out:
if (error)
{
g_message ("Error combining PEM files: %s", error->message);
g_clear_error (&error);
}
g_clear_object (&certificate);
g_string_free (combined, TRUE);
g_free (name);
g_free (target_path);
g_free (target_name);
g_free (cert_dir);
return ret;
}
int
cockpit_remotectl_certificate (int argc,
char *argv[])
{
GOptionContext *context;
GError *error = NULL;
gboolean ensure = FALSE;
gchar *selinux = NULL;
gchar **pem_files = NULL;
gchar *group = NULL;
gchar *user = NULL;
int ret = 1;
const GOptionEntry options[] = {
{ "ensure", 0, 0, G_OPTION_ARG_NONE, &ensure,
"Ensure that a certificate exists and can be loaded", NULL },
{ "user", 0, 0, G_OPTION_ARG_STRING, &user,
"The unix user that should own the certificate", "name" },
{ "group", 0, 0, G_OPTION_ARG_STRING, &group,
"The unix group that should read the certificate", "group" },
{ "selinux-type", 0, 0, G_OPTION_ARG_STRING, &selinux,
"The SELinux security context type for the certificate", "selinux" },
{ G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &pem_files,
"If provided the given files are combined into a single .cert file and placed in the correct location", "[PEM-FILES..]" },
{ NULL },
};
context = g_option_context_new (NULL);
g_option_context_add_main_entries (context, options, NULL);
g_option_context_set_help_enabled (context, TRUE);
g_set_prgname ("remotectl certificate");
if (!g_option_context_parse (context, &argc, &argv, &error))
{
g_message ("%s", error->message);
ret = 2;
}
else
{
g_set_prgname ("remotectl");
if (pem_files)
ret = cockpit_certificate_combine (pem_files, user, group, selinux);
else if (ensure)
ret = ensure_certificate (user, group, selinux);
else
ret = locate_certificate ();
}
g_option_context_free (context);
g_clear_error (&error);
g_free (selinux);
g_free (group);
g_free (user);
g_strfreev (pem_files);
return ret;
}