~stgraber/+junk/nm-ofono

« back to all changes in this revision

Viewing changes to src/vpn-manager/nm-vpn-service.c

  • Committer: Stéphane Graber
  • Date: 2013-03-15 18:27:57 UTC
  • Revision ID: stgraber@ubuntu.com-20130315182757-7bcjymubsn3lsshs
Import of NM 0.9.8.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
 
2
/* NetworkManager -- Network link manager
 
3
 *
 
4
 * This program is free software; you can redistribute it and/or modify
 
5
 * it under the terms of the GNU General Public License as published by
 
6
 * the Free Software Foundation; either version 2 of the License, or
 
7
 * (at your option) any later version.
 
8
 *
 
9
 * This program is distributed in the hope that it will be useful,
 
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
 * GNU General Public License for more details.
 
13
 *
 
14
 * You should have received a copy of the GNU General Public License along
 
15
 * with this program; if not, write to the Free Software Foundation, Inc.,
 
16
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
17
 *
 
18
 * Copyright (C) 2005 - 2012 Red Hat, Inc.
 
19
 * Copyright (C) 2005 - 2008 Novell, Inc.
 
20
 */
 
21
 
 
22
#include <config.h>
 
23
#include <glib.h>
 
24
#include <string.h>
 
25
#include <dbus/dbus.h>
 
26
#include <sys/types.h>
 
27
#include <sys/wait.h>
 
28
#include <signal.h>
 
29
#include <unistd.h>
 
30
 
 
31
#include "nm-vpn-service.h"
 
32
#include "nm-dbus-manager.h"
 
33
#include "nm-logging.h"
 
34
#include "nm-posix-signals.h"
 
35
#include "nm-vpn-manager.h"
 
36
#include "nm-glib-compat.h"
 
37
 
 
38
G_DEFINE_TYPE (NMVPNService, nm_vpn_service, G_TYPE_OBJECT)
 
39
 
 
40
typedef struct {
 
41
        gboolean disposed;
 
42
 
 
43
        NMDBusManager *dbus_mgr;
 
44
        char *name;
 
45
        char *dbus_service;
 
46
        char *program;
 
47
        char *namefile;
 
48
 
 
49
        GPid pid;
 
50
        GSList *connections;
 
51
        guint start_timeout;
 
52
        guint quit_timeout;
 
53
        guint child_watch;
 
54
        gulong name_owner_id;
 
55
} NMVPNServicePrivate;
 
56
 
 
57
#define NM_VPN_SERVICE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_VPN_SERVICE, NMVPNServicePrivate))
 
58
 
 
59
NMVPNService *
 
60
nm_vpn_service_new (const char *namefile, GError **error)
 
61
{
 
62
        NMVPNService *self = NULL;
 
63
        GKeyFile *kf;
 
64
        char *dbus_service = NULL, *program = NULL, *name = NULL;
 
65
 
 
66
        g_return_val_if_fail (namefile != NULL, NULL);
 
67
        g_return_val_if_fail (g_path_is_absolute (namefile), NULL);
 
68
 
 
69
        kf = g_key_file_new ();
 
70
        if (!g_key_file_load_from_file (kf, namefile, G_KEY_FILE_NONE, error)) {
 
71
                g_key_file_free (kf);
 
72
                return NULL;
 
73
        }
 
74
 
 
75
        dbus_service = g_key_file_get_string (kf, VPN_CONNECTION_GROUP, "service", NULL);
 
76
        if (!dbus_service) {
 
77
                g_set_error (error, 0, 0, "VPN service file %s had no 'service' key", namefile);
 
78
                goto out;
 
79
        }
 
80
 
 
81
        program = g_key_file_get_string (kf, VPN_CONNECTION_GROUP, "program", NULL);
 
82
        if (!program) {
 
83
                g_set_error (error, 0, 0, "VPN service file %s had no 'program' key", namefile);
 
84
                goto out;
 
85
        }
 
86
 
 
87
        name = g_key_file_get_string (kf, VPN_CONNECTION_GROUP, "name", NULL);
 
88
        if (!name) {
 
89
                g_set_error (error, 0, 0, "VPN service file %s had no 'name' key", namefile);
 
90
                goto out;
 
91
        }
 
92
 
 
93
        self = (NMVPNService *) g_object_new (NM_TYPE_VPN_SERVICE, NULL);
 
94
        if (!self) {
 
95
                g_set_error (error, 0, 0, "out of memory creating VPN service object");
 
96
                goto out;
 
97
        }
 
98
 
 
99
        NM_VPN_SERVICE_GET_PRIVATE (self)->name = g_strdup (name);
 
100
        NM_VPN_SERVICE_GET_PRIVATE (self)->dbus_service = g_strdup (dbus_service);
 
101
        NM_VPN_SERVICE_GET_PRIVATE (self)->program = g_strdup (program);
 
102
        NM_VPN_SERVICE_GET_PRIVATE (self)->namefile = g_strdup (namefile);
 
103
 
 
104
 out:
 
105
        g_key_file_free (kf);
 
106
        g_free (dbus_service);
 
107
        g_free (program);
 
108
        g_free (name);
 
109
        return self;
 
110
}
 
111
 
 
112
const char *
 
113
nm_vpn_service_get_dbus_service (NMVPNService *service)
 
114
{
 
115
        g_return_val_if_fail (NM_IS_VPN_SERVICE (service), NULL);
 
116
 
 
117
        return NM_VPN_SERVICE_GET_PRIVATE (service)->dbus_service;
 
118
}
 
119
 
 
120
const char *
 
121
nm_vpn_service_get_name_file (NMVPNService *service)
 
122
{
 
123
        g_return_val_if_fail (NM_IS_VPN_SERVICE (service), NULL);
 
124
 
 
125
        return NM_VPN_SERVICE_GET_PRIVATE (service)->namefile;
 
126
}
 
127
 
 
128
void
 
129
nm_vpn_service_connections_stop (NMVPNService *service,
 
130
                                 gboolean fail,
 
131
                                 NMVPNConnectionStateReason reason)
 
132
{
 
133
        NMVPNServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (service);
 
134
        GSList *iter, *copy;
 
135
 
 
136
        /* Copy because stopping the connection may remove it from the list
 
137
         * in the the NMVPNService objects' VPN connection state handler.
 
138
         */
 
139
        copy = g_slist_copy (priv->connections);
 
140
        for (iter = copy; iter; iter = iter->next) {
 
141
                if (fail)
 
142
                        nm_vpn_connection_fail (NM_VPN_CONNECTION (iter->data), reason);
 
143
                else
 
144
                        nm_vpn_connection_disconnect (NM_VPN_CONNECTION (iter->data), reason);
 
145
        }
 
146
        g_slist_free (copy);
 
147
}
 
148
 
 
149
static void
 
150
clear_quit_timeout (NMVPNService *self)
 
151
{
 
152
        NMVPNServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (self);
 
153
 
 
154
        if (priv->quit_timeout) {
 
155
                g_source_remove (priv->quit_timeout);
 
156
                priv->quit_timeout = 0;
 
157
        }
 
158
}
 
159
 
 
160
/*
 
161
 * nm_vpn_service_child_setup
 
162
 *
 
163
 * Set the process group ID of the newly forked process
 
164
 *
 
165
 */
 
166
static void
 
167
nm_vpn_service_child_setup (gpointer user_data G_GNUC_UNUSED)
 
168
{
 
169
        /* We are in the child process at this point */
 
170
        pid_t pid = getpid ();
 
171
        setpgid (pid, pid);
 
172
 
 
173
        /*
 
174
         * We blocked signals in main(). We need to restore original signal
 
175
         * mask for VPN service here so that it can receive signals.
 
176
         */
 
177
        nm_unblock_posix_signals (NULL);
 
178
}
 
179
 
 
180
static void
 
181
vpn_service_watch_cb (GPid pid, gint status, gpointer user_data)
 
182
{
 
183
        NMVPNService *service = NM_VPN_SERVICE (user_data);
 
184
        NMVPNServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (service);
 
185
 
 
186
        if (WIFEXITED (status)) {
 
187
                guint err = WEXITSTATUS (status);
 
188
 
 
189
                if (err != 0) {
 
190
                        nm_log_warn (LOGD_VPN, "VPN service '%s' exited with error: %d",
 
191
                                     priv->name, WSTOPSIG (status));
 
192
                }
 
193
        } else if (WIFSTOPPED (status)) {
 
194
                nm_log_warn (LOGD_VPN, "VPN service '%s' stopped unexpectedly with signal %d",
 
195
                             priv->name, WSTOPSIG (status));
 
196
        } else if (WIFSIGNALED (status)) {
 
197
                nm_log_warn (LOGD_VPN, "VPN service '%s' died with signal %d",
 
198
                             priv->name, WTERMSIG (status));
 
199
        } else {
 
200
                nm_log_warn (LOGD_VPN, "VPN service '%s' died from an unknown cause", 
 
201
                             priv->name);
 
202
        }
 
203
 
 
204
        priv->pid = 0;
 
205
        priv->child_watch = 0;
 
206
        clear_quit_timeout (service);
 
207
 
 
208
        nm_vpn_service_connections_stop (service, TRUE, NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED);
 
209
}
 
210
 
 
211
static gboolean
 
212
nm_vpn_service_timeout (gpointer data)
 
213
{
 
214
        NMVPNService *self = NM_VPN_SERVICE (data);
 
215
        NMVPNServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (self);
 
216
 
 
217
        nm_log_warn (LOGD_VPN, "VPN service '%s' start timed out", priv->name);
 
218
        priv->start_timeout = 0;
 
219
        nm_vpn_service_connections_stop (self, TRUE, NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_TIMEOUT);
 
220
        return FALSE;
 
221
}
 
222
 
 
223
static gboolean
 
224
nm_vpn_service_daemon_exec (NMVPNService *service, GError **error)
 
225
{
 
226
        NMVPNServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (service);
 
227
        char *vpn_argv[2];
 
228
        gboolean success = FALSE;
 
229
        GError *spawn_error = NULL;
 
230
 
 
231
        g_return_val_if_fail (NM_IS_VPN_SERVICE (service), FALSE);
 
232
        g_return_val_if_fail (error != NULL, FALSE);
 
233
        g_return_val_if_fail (*error == NULL, FALSE);
 
234
 
 
235
        vpn_argv[0] = priv->program;
 
236
        vpn_argv[1] = NULL;
 
237
 
 
238
        success = g_spawn_async (NULL, vpn_argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
 
239
                                 nm_vpn_service_child_setup, NULL, &priv->pid,
 
240
                                 &spawn_error);
 
241
        if (success) {
 
242
                nm_log_info (LOGD_VPN, "VPN service '%s' started (%s), PID %d", 
 
243
                             priv->name, priv->dbus_service, priv->pid);
 
244
 
 
245
                priv->child_watch = g_child_watch_add (priv->pid, vpn_service_watch_cb, service);
 
246
                priv->start_timeout = g_timeout_add_seconds (5, nm_vpn_service_timeout, service);
 
247
        } else {
 
248
                nm_log_warn (LOGD_VPN, "VPN service '%s': could not launch the VPN service. error: (%d) %s.",
 
249
                             priv->name,
 
250
                             spawn_error ? spawn_error->code : -1,
 
251
                             spawn_error && spawn_error->message ? spawn_error->message : "(unknown)");
 
252
 
 
253
                g_set_error (error,
 
254
                             NM_VPN_MANAGER_ERROR, NM_VPN_MANAGER_ERROR_SERVICE_START_FAILED,
 
255
                             "%s", spawn_error ? spawn_error->message : "unknown g_spawn_async() error");
 
256
 
 
257
                nm_vpn_service_connections_stop (service, TRUE, NM_VPN_CONNECTION_STATE_REASON_SERVICE_START_FAILED);
 
258
                if (spawn_error)
 
259
                        g_error_free (spawn_error);
 
260
        }
 
261
 
 
262
        return success;
 
263
}
 
264
 
 
265
static gboolean
 
266
ensure_killed (gpointer data)
 
267
{
 
268
        int pid = GPOINTER_TO_INT (data);
 
269
 
 
270
        if (kill (pid, 0) == 0)
 
271
                kill (pid, SIGKILL);
 
272
 
 
273
        /* ensure the child is reaped */
 
274
        nm_log_dbg (LOGD_VPN, "waiting for VPN service pid %d to exit", pid);
 
275
        waitpid (pid, NULL, 0);
 
276
        nm_log_dbg (LOGD_VPN, "VPN service pid %d cleaned up", pid);
 
277
 
 
278
        return FALSE;
 
279
}
 
280
 
 
281
static gboolean
 
282
service_quit (gpointer user_data)
 
283
{
 
284
        NMVPNServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (user_data);
 
285
 
 
286
        if (priv->pid) {
 
287
                if (kill (priv->pid, SIGTERM) == 0)
 
288
                        g_timeout_add_seconds (2, ensure_killed, GINT_TO_POINTER (priv->pid));
 
289
                else {
 
290
                        kill (priv->pid, SIGKILL);
 
291
 
 
292
                        /* ensure the child is reaped */
 
293
                        nm_log_dbg (LOGD_VPN, "waiting for VPN service pid %d to exit", priv->pid);
 
294
                        waitpid (priv->pid, NULL, 0);
 
295
                        nm_log_dbg (LOGD_VPN, "VPN service pid %d cleaned up", priv->pid);
 
296
                }
 
297
                priv->pid = 0;
 
298
        }
 
299
        priv->quit_timeout = 0;
 
300
 
 
301
        return FALSE;
 
302
}
 
303
 
 
304
static void
 
305
connection_vpn_state_changed (NMVPNConnection *connection,
 
306
                              NMVPNConnectionState new_state,
 
307
                              NMVPNConnectionState old_state,
 
308
                              NMVPNConnectionStateReason reason,
 
309
                              gpointer user_data)
 
310
{
 
311
        NMVPNServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (user_data);
 
312
 
 
313
        switch (new_state) {
 
314
        case NM_VPN_CONNECTION_STATE_FAILED:
 
315
        case NM_VPN_CONNECTION_STATE_DISCONNECTED:
 
316
                /* Remove the connection from our list */
 
317
                priv->connections = g_slist_remove (priv->connections, connection);
 
318
                g_object_unref (connection);
 
319
 
 
320
                if (priv->connections == NULL) {
 
321
                        /* Tell the service to quit in a few seconds */
 
322
                        if (!priv->quit_timeout)
 
323
                                priv->quit_timeout = g_timeout_add_seconds (5, service_quit, user_data);
 
324
                }
 
325
                break;
 
326
        default:
 
327
                break;
 
328
        }
 
329
}
 
330
 
 
331
NMVPNConnection *
 
332
nm_vpn_service_activate (NMVPNService *service,
 
333
                         NMConnection *connection,
 
334
                         NMDevice *device,
 
335
                         const char *specific_object,
 
336
                         gboolean user_requested,
 
337
                         gulong user_uid,
 
338
                         GError **error)
 
339
{
 
340
        NMVPNConnection *vpn;
 
341
        NMVPNServicePrivate *priv;
 
342
 
 
343
        g_return_val_if_fail (NM_IS_VPN_SERVICE (service), NULL);
 
344
        g_return_val_if_fail (NM_IS_CONNECTION (connection), NULL);
 
345
        g_return_val_if_fail (NM_IS_DEVICE (device), NULL);
 
346
        g_return_val_if_fail (error != NULL, NULL);
 
347
        g_return_val_if_fail (*error == NULL, NULL);
 
348
 
 
349
        priv = NM_VPN_SERVICE_GET_PRIVATE (service);
 
350
 
 
351
        clear_quit_timeout (service);
 
352
 
 
353
        vpn = nm_vpn_connection_new (connection, device, specific_object, user_requested, user_uid);
 
354
        g_signal_connect (vpn, NM_VPN_CONNECTION_INTERNAL_STATE_CHANGED,
 
355
                          G_CALLBACK (connection_vpn_state_changed),
 
356
                          service);
 
357
 
 
358
        priv->connections = g_slist_prepend (priv->connections, g_object_ref (vpn));
 
359
 
 
360
        if (nm_dbus_manager_name_has_owner (priv->dbus_mgr, priv->dbus_service)) {
 
361
                // FIXME: fill in error when errors happen
 
362
                nm_vpn_connection_activate (vpn);
 
363
        } else if (priv->start_timeout == 0) {
 
364
                nm_log_info (LOGD_VPN, "Starting VPN service '%s'...", priv->name);
 
365
                if (!nm_vpn_service_daemon_exec (service, error))
 
366
                        vpn = NULL;
 
367
        }
 
368
 
 
369
        return vpn;
 
370
}
 
371
 
 
372
const GSList *
 
373
nm_vpn_service_get_active_connections (NMVPNService *service)
 
374
{
 
375
        g_return_val_if_fail (NM_IS_VPN_SERVICE (service), NULL);
 
376
 
 
377
        return NM_VPN_SERVICE_GET_PRIVATE (service)->connections;
 
378
}
 
379
 
 
380
static void
 
381
nm_vpn_service_name_owner_changed (NMDBusManager *mgr,
 
382
                                                        const char *name,
 
383
                                                        const char *old,
 
384
                                                        const char *new,
 
385
                                                        gpointer user_data)
 
386
{
 
387
        NMVPNService *service = NM_VPN_SERVICE (user_data);
 
388
        NMVPNServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (service);
 
389
        gboolean old_owner_good;
 
390
        gboolean new_owner_good;
 
391
        GSList *iter;
 
392
 
 
393
        if (strcmp (name, priv->dbus_service))
 
394
                return;
 
395
 
 
396
        /* Service changed, no need to wait for the timeout any longer */
 
397
        if (priv->start_timeout) {
 
398
                g_source_remove (priv->start_timeout);
 
399
                priv->start_timeout = 0;
 
400
        }
 
401
 
 
402
        old_owner_good = (old && (strlen (old) > 0));
 
403
        new_owner_good = (new && (strlen (new) > 0));
 
404
 
 
405
        if (!old_owner_good && new_owner_good) {
 
406
                /* service just appeared */
 
407
                nm_log_info (LOGD_VPN, "VPN service '%s' appeared; activating connections", priv->name);
 
408
                clear_quit_timeout (service);
 
409
 
 
410
                for (iter = priv->connections; iter; iter = iter->next)
 
411
                        nm_vpn_connection_activate (NM_VPN_CONNECTION (iter->data));
 
412
        } else if (old_owner_good && !new_owner_good) {
 
413
                /* service went away */
 
414
                nm_log_info (LOGD_VPN, "VPN service '%s' disappeared", priv->name);
 
415
                nm_vpn_service_connections_stop (service, TRUE, NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED);
 
416
        }
 
417
}
 
418
 
 
419
/******************************************************************************/
 
420
 
 
421
static void
 
422
nm_vpn_service_init (NMVPNService *self)
 
423
{
 
424
        NMVPNServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (self);
 
425
 
 
426
        priv->dbus_mgr = nm_dbus_manager_get ();
 
427
        priv->name_owner_id = g_signal_connect (priv->dbus_mgr,
 
428
                                                NM_DBUS_MANAGER_NAME_OWNER_CHANGED,
 
429
                                                G_CALLBACK (nm_vpn_service_name_owner_changed),
 
430
                                                self);
 
431
}
 
432
 
 
433
static void
 
434
dispose (GObject *object)
 
435
{
 
436
        NMVPNService *self = NM_VPN_SERVICE (object);
 
437
        NMVPNServicePrivate *priv = NM_VPN_SERVICE_GET_PRIVATE (self);
 
438
 
 
439
        if (priv->disposed)
 
440
                goto out;
 
441
        priv->disposed = TRUE;
 
442
 
 
443
        if (priv->start_timeout)
 
444
                g_source_remove (priv->start_timeout);
 
445
 
 
446
        nm_vpn_service_connections_stop (NM_VPN_SERVICE (object),
 
447
                                         FALSE,
 
448
                                         NM_VPN_CONNECTION_STATE_REASON_SERVICE_STOPPED);
 
449
 
 
450
        g_signal_handler_disconnect (priv->dbus_mgr, priv->name_owner_id);
 
451
 
 
452
        if (priv->child_watch)
 
453
                g_source_remove (priv->child_watch);
 
454
 
 
455
        clear_quit_timeout (self);
 
456
        service_quit (self);
 
457
 
 
458
        g_object_unref (priv->dbus_mgr);
 
459
 
 
460
        g_free (priv->name);
 
461
        g_free (priv->dbus_service);
 
462
        g_free (priv->program);
 
463
        g_free (priv->namefile);
 
464
 
 
465
out:
 
466
        G_OBJECT_CLASS (nm_vpn_service_parent_class)->dispose (object);
 
467
}
 
468
 
 
469
static void
 
470
nm_vpn_service_class_init (NMVPNServiceClass *service_class)
 
471
{
 
472
        GObjectClass *object_class = G_OBJECT_CLASS (service_class);
 
473
 
 
474
        g_type_class_add_private (service_class, sizeof (NMVPNServicePrivate));
 
475
 
 
476
        /* virtual methods */
 
477
        object_class->dispose = dispose;
 
478
}