NetworkManager/src/vpn-manager/nm-vpn-service.c
Dan Winship da8d79f1c5 core: drop all remaining core-internal error domains
A number of classes in core had their own error domains that aren't
really necessary.

In the case of NMDcbError, NMDhcpManagerError, NMDnsManagerError,
NMDnsmasqManagerError, NMPppManagerError, and NMSessionMonitorError,
most of the codes they defined weren't even being used, and at any
rate, the errors were always returned into contexts where they would
just have their message extracted and then get thrown away without
anyone ever looking at the domain or code. So all uses of those
domains can just be replaced with NM_MANAGER_ERROR_FAILED without any
loss of information.

NMAuthManagerError only had 1 error code, and it just indicated
"something went wrong", so it can be replaced with
NM_MANAGER_ERROR_FAILED without loss of information.
(nm-auth-manager.c has also been fixed to return
NM_MANAGER_ERROR_FAILED when the CheckAuthorization D-Bus call fails,
rather than returning whatever error domain/code the D-Bus call
returned.)

NMVpnManagerError used 2 of its 4 error codes, and they could actually
end up getting returned across D-Bus in some cases. But there are
NMManagerError codes that are semantically similar enough to make the
NMVpnManagerError ones unnecessary.
2014-10-22 08:29:10 -04:00

399 lines
12 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/* NetworkManager -- Network link manager
*
* This program 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Copyright (C) 2005 - 2014 Red Hat, Inc.
* Copyright (C) 2005 - 2008 Novell, Inc.
*/
#include <config.h>
#include <glib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>
#include "nm-vpn-service.h"
#include "nm-dbus-manager.h"
#include "nm-logging.h"
#include "nm-posix-signals.h"
#include "nm-vpn-manager.h"
#include "nm-glib-compat.h"
G_DEFINE_TYPE (NMVpnService, nm_vpn_service, G_TYPE_OBJECT)
typedef struct {
char *name;
char *dbus_service;
char *program;
char *namefile;
NMVpnConnection *active;
GSList *pending;
guint start_timeout;
gboolean service_running;
} NMVpnServicePrivate;
#define NM_VPN_SERVICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_VPN_SERVICE, NMVpnServicePrivate))
#define VPN_CONNECTION_GROUP "VPN Connection"
static gboolean start_pending_vpn (NMVpnService *self);
NMVpnService *
nm_vpn_service_new (const char *namefile, GError **error)
{
NMVpnService *self;
NMVpnServicePrivate *priv;
GKeyFile *kf;
g_return_val_if_fail (namefile != NULL, NULL);
g_return_val_if_fail (g_path_is_absolute (namefile), NULL);
kf = g_key_file_new ();
if (!g_key_file_load_from_file (kf, namefile, G_KEY_FILE_NONE, error)) {
g_key_file_free (kf);
return NULL;
}
self = (NMVpnService *) g_object_new (NM_TYPE_VPN_SERVICE, NULL);
priv = NM_VPN_SERVICE_GET_PRIVATE (self);
priv->namefile = g_strdup (namefile);
priv->dbus_service = g_key_file_get_string (kf, VPN_CONNECTION_GROUP, "service", error);
if (!priv->dbus_service)
goto error;
priv->program = g_key_file_get_string (kf, VPN_CONNECTION_GROUP, "program", error);
if (!priv->program)
goto error;
priv->name = g_key_file_get_string (kf, VPN_CONNECTION_GROUP, "name", error);
if (!priv->name)
goto error;
priv->service_running = nm_dbus_manager_name_has_owner (nm_dbus_manager_get (), priv->dbus_service);
g_key_file_free (kf);
return self;
error:
g_object_unref (self);
g_key_file_free (kf);
return NULL;
}
const char *
nm_vpn_service_get_dbus_service (NMVpnService *service)
{
g_return_val_if_fail (NM_IS_VPN_SERVICE (service), NULL);
return NM_VPN_SERVICE_GET_PRIVATE (service)->dbus_service;
}
const char *
nm_vpn_service_get_name_file (NMVpnService *service)
{
g_return_val_if_fail (NM_IS_VPN_SERVICE (service), NULL);
return NM_VPN_SERVICE_GET_PRIVATE (service)->namefile;
}
static void
connection_vpn_state_changed (NMVpnConnection *connection,
NMVpnConnectionState new_state,
NMVpnConnectionState old_state,
NMVpnConnectionStateReason reason,
gpointer user_data)
{
NMVpnService *self = NM_VPN_SERVICE (user_data);
NMVpnServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (self);
if (new_state == NM_VPN_CONNECTION_STATE_FAILED ||
new_state == NM_VPN_CONNECTION_STATE_DISCONNECTED) {
g_signal_handlers_disconnect_by_func (connection, G_CALLBACK (connection_vpn_state_changed), self);
if (connection == priv->active) {
priv->active = NULL;
start_pending_vpn (self);
} else
priv->pending = g_slist_remove (priv->pending, connection);
g_object_unref (connection);
}
}
void
nm_vpn_service_stop_connections (NMVpnService *service,
gboolean quitting,
NMVpnConnectionStateReason reason)
{
NMVpnServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (service);
GSList *iter;
/* Just add priv->active to the beginning of priv->pending,
* since we're going to clear priv->pending immediately anyway.
*/
if (priv->active) {
priv->pending = g_slist_prepend (priv->pending, priv->active);
priv->active = NULL;
}
for (iter = priv->pending; iter; iter = iter->next) {
NMVpnConnection *vpn = NM_VPN_CONNECTION (iter->data);
g_signal_handlers_disconnect_by_func (vpn, G_CALLBACK (connection_vpn_state_changed), service);
if (quitting) {
/* Deactivate to allow pre-down before disconnecting */
nm_vpn_connection_deactivate (vpn, reason, quitting);
}
nm_vpn_connection_disconnect (vpn, reason, quitting);
g_object_unref (vpn);
}
g_clear_pointer (&priv->pending, g_slist_free);
}
static void
_daemon_setup (gpointer user_data G_GNUC_UNUSED)
{
/* We are in the child process at this point */
pid_t pid = getpid ();
setpgid (pid, pid);
/*
* We blocked signals in main(). We need to restore original signal
* mask for VPN service here so that it can receive signals.
*/
nm_unblock_posix_signals (NULL);
}
static gboolean
_daemon_exec_timeout (gpointer data)
{
NMVpnService *self = NM_VPN_SERVICE (data);
NMVpnServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (self);
nm_log_warn (LOGD_VPN, "VPN service '%s' start timed out", priv->name);
priv->start_timeout = 0;
nm_vpn_service_stop_connections (self, FALSE, NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_TIMEOUT);
return G_SOURCE_REMOVE;
}
static gboolean
nm_vpn_service_daemon_exec (NMVpnService *service, GError **error)
{
NMVpnServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (service);
GPid pid;
char *vpn_argv[2];
gboolean success = FALSE;
GError *spawn_error = NULL;
g_return_val_if_fail (NM_IS_VPN_SERVICE (service), FALSE);
vpn_argv[0] = priv->program;
vpn_argv[1] = NULL;
success = g_spawn_async (NULL, vpn_argv, NULL, 0, _daemon_setup, NULL, &pid, &spawn_error);
if (success) {
nm_log_info (LOGD_VPN, "VPN service '%s' started (%s), PID %ld",
priv->name, priv->dbus_service, (long int) pid);
priv->start_timeout = g_timeout_add_seconds (5, _daemon_exec_timeout, service);
} else {
nm_log_warn (LOGD_VPN, "VPN service '%s': could not launch the VPN service. error: (%d) %s.",
priv->name,
spawn_error ? spawn_error->code : -1,
spawn_error && spawn_error->message ? spawn_error->message : "(unknown)");
g_set_error (error,
NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
"%s", spawn_error ? spawn_error->message : "unknown g_spawn_async() error");
nm_vpn_service_stop_connections (service, FALSE, NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_FAILED);
if (spawn_error)
g_error_free (spawn_error);
}
return success;
}
static gboolean
start_active_vpn (NMVpnService *self, GError **error)
{
NMVpnServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (self);
if (!priv->active)
return TRUE;
if (priv->service_running) {
/* Just activate the VPN */
nm_vpn_connection_activate (priv->active);
return TRUE;
} else if (priv->start_timeout == 0) {
/* VPN service not running, start it */
nm_log_info (LOGD_VPN, "Starting VPN service '%s'...", priv->name);
return nm_vpn_service_daemon_exec (self, error);
}
/* Already started VPN service, waiting for it to appear on D-Bus */
return TRUE;
}
static gboolean
start_pending_vpn (NMVpnService *self)
{
NMVpnServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (self);
g_assert (priv->active == NULL);
if (!priv->pending)
return TRUE;
/* Make next VPN active */
priv->active = g_slist_nth_data (priv->pending, 0);
priv->pending = g_slist_remove (priv->pending, priv->active);
return start_active_vpn (self, NULL);
}
gboolean
nm_vpn_service_activate (NMVpnService *service,
NMVpnConnection *vpn,
GError **error)
{
NMVpnServicePrivate *priv;
g_return_val_if_fail (NM_IS_VPN_SERVICE (service), FALSE);
g_return_val_if_fail (NM_IS_VPN_CONNECTION (vpn), FALSE);
g_return_val_if_fail (error != NULL, FALSE);
g_return_val_if_fail (*error == NULL, FALSE);
priv = NM_VPN_SERVICE_GET_PRIVATE (service);
g_signal_connect (vpn, NM_VPN_CONNECTION_INTERNAL_STATE_CHANGED,
G_CALLBACK (connection_vpn_state_changed),
service);
/* Queue up the new VPN connection */
priv->pending = g_slist_append (priv->pending, g_object_ref (vpn));
/* Tell the active VPN to deactivate and wait for it to quit before we
* start the next VPN. The just-queued VPN will then be started from
* connection_vpn_state_changed().
*/
if (priv->active) {
nm_vpn_connection_deactivate (priv->active, NM_VPN_CONNECTION_STATE_REASON_USER_DISCONNECTED, FALSE);
return TRUE;
}
/* Otherwise start the next VPN */
return start_pending_vpn (service);
}
static void
_name_owner_changed (NMDBusManager *mgr,
const char *name,
const char *old,
const char *new,
gpointer user_data)
{
NMVpnService *service = NM_VPN_SERVICE (user_data);
NMVpnServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (service);
gboolean old_owner_good, new_owner_good, success;
if (strcmp (name, priv->dbus_service))
return;
/* Service changed, no need to wait for the timeout any longer */
if (priv->start_timeout) {
g_source_remove (priv->start_timeout);
priv->start_timeout = 0;
}
old_owner_good = (old && old[0]);
new_owner_good = (new && new[0]);
if (!old_owner_good && new_owner_good) {
/* service appeared */
priv->service_running = TRUE;
nm_log_info (LOGD_VPN, "VPN service '%s' appeared; activating connections", priv->name);
/* Expect success because the VPN service has already appeared */
success = start_active_vpn (service, NULL);
g_warn_if_fail (success);
} else if (old_owner_good && !new_owner_good) {
/* service went away */
priv->service_running = FALSE;
nm_log_info (LOGD_VPN, "VPN service '%s' disappeared", priv->name);
nm_vpn_service_stop_connections (service, FALSE, NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED);
}
}
/******************************************************************************/
static void
nm_vpn_service_init (NMVpnService *self)
{
g_signal_connect (nm_dbus_manager_get (),
NM_DBUS_MANAGER_NAME_OWNER_CHANGED,
G_CALLBACK (_name_owner_changed),
self);
}
static void
dispose (GObject *object)
{
NMVpnService *self = NM_VPN_SERVICE (object);
NMVpnServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (self);
if (priv->start_timeout) {
g_source_remove (priv->start_timeout);
priv->start_timeout = 0;
}
/* VPNService owner is required to stop connections before releasing */
g_assert (priv->active == NULL);
g_assert (priv->pending == NULL);
g_signal_handlers_disconnect_by_func (nm_dbus_manager_get (),
G_CALLBACK (_name_owner_changed),
self);
G_OBJECT_CLASS (nm_vpn_service_parent_class)->dispose (object);
}
static void
finalize (GObject *object)
{
NMVpnServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (object);
g_free (priv->name);
g_free (priv->dbus_service);
g_free (priv->program);
g_free (priv->namefile);
G_OBJECT_CLASS (nm_vpn_service_parent_class)->finalize (object);
}
static void
nm_vpn_service_class_init (NMVpnServiceClass *service_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (service_class);
g_type_class_add_private (service_class, sizeof (NMVpnServicePrivate));
/* virtual methods */
object_class->dispose = dispose;
object_class->finalize = finalize;
}