device: renew dhcp leases on awake for software devices
[NetworkManager.git] / src / nm-auth-utils.c
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) 2010 Red Hat, Inc.
19  */
20
21 #include "nm-default.h"
22
23 #include <string.h>
24
25 #include "nm-setting-connection.h"
26 #include "nm-auth-utils.h"
27 #include "nm-auth-subject.h"
28 #include "nm-auth-manager.h"
29 #include "nm-session-monitor.h"
30
31 struct NMAuthChain {
32         guint32 refcount;
33         GSList *calls;
34         GHashTable *data;
35
36         GDBusMethodInvocation *context;
37         NMAuthSubject *subject;
38         GError *error;
39
40         guint idle_id;
41         gboolean done;
42
43         NMAuthChainResultFunc done_func;
44         gpointer user_data;
45 };
46
47 typedef struct {
48         NMAuthChain *chain;
49         GCancellable *cancellable;
50         char *permission;
51         guint call_idle_id;
52 } AuthCall;
53
54 typedef struct {
55         gpointer data;
56         GDestroyNotify destroy;
57 } ChainData;
58
59 static ChainData *
60 chain_data_new (gpointer data, GDestroyNotify destroy)
61 {
62         ChainData *tmp;
63
64         tmp = g_slice_new (ChainData);
65         tmp->data = data;
66         tmp->destroy = destroy;
67         return tmp;
68 }
69
70 static void
71 chain_data_free (gpointer data)
72 {
73         ChainData *tmp = data;
74
75         if (tmp->destroy)
76                 tmp->destroy (tmp->data);
77         memset (tmp, 0, sizeof (ChainData));
78         g_slice_free (ChainData, tmp);
79 }
80
81 static gboolean
82 auth_chain_finish (gpointer user_data)
83 {
84         NMAuthChain *self = user_data;
85
86         self->idle_id = 0;
87         self->done = TRUE;
88
89         /* Ensure we stay alive across the callback */
90         self->refcount++;
91         self->done_func (self, self->error, self->context, self->user_data);
92         nm_auth_chain_unref (self);
93         return FALSE;
94 }
95
96 /* Creates the NMAuthSubject automatically */
97 NMAuthChain *
98 nm_auth_chain_new_context (GDBusMethodInvocation *context,
99                            NMAuthChainResultFunc done_func,
100                            gpointer user_data)
101 {
102         NMAuthSubject *subject;
103         NMAuthChain *chain;
104
105         g_return_val_if_fail (context != NULL, NULL);
106
107         subject = nm_auth_subject_new_unix_process_from_context (context);
108         if (!subject)
109                 return NULL;
110
111         chain = nm_auth_chain_new_subject (subject,
112                                            context,
113                                            done_func,
114                                            user_data);
115         g_object_unref (subject);
116         return chain;
117 }
118
119 /* Requires an NMAuthSubject */
120 NMAuthChain *
121 nm_auth_chain_new_subject (NMAuthSubject *subject,
122                            GDBusMethodInvocation *context,
123                            NMAuthChainResultFunc done_func,
124                            gpointer user_data)
125 {
126         NMAuthChain *self;
127
128         g_return_val_if_fail (NM_IS_AUTH_SUBJECT (subject), NULL);
129         g_return_val_if_fail (nm_auth_subject_is_unix_process (subject) || nm_auth_subject_is_internal (subject), NULL);
130
131         self = g_slice_new0 (NMAuthChain);
132         self->refcount = 1;
133         self->data = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, chain_data_free);
134         self->done_func = done_func;
135         self->user_data = user_data;
136         self->context = context ? g_object_ref (context) : NULL;
137         self->subject = g_object_ref (subject);
138
139         return self;
140 }
141
142 static gpointer
143 _get_data (NMAuthChain *self, const char *tag)
144 {
145         ChainData *tmp;
146
147         tmp = g_hash_table_lookup (self->data, tag);
148         return tmp ? tmp->data : NULL;
149 }
150
151 gpointer
152 nm_auth_chain_get_data (NMAuthChain *self, const char *tag)
153 {
154         g_return_val_if_fail (self != NULL, NULL);
155         g_return_val_if_fail (tag != NULL, NULL);
156
157         return _get_data (self, tag);
158 }
159
160 /**
161  * nm_auth_chain_steal_data:
162  * @self: A #NMAuthChain.
163  * @tag: A "tag" uniquely identifying the data to steal.
164  *
165  * Removes the datum assocated with @tag from the chain's data associations,
166  * without invoking the association's destroy handler.  The caller assumes
167  * ownership over the returned value.
168  *
169  * Returns: the datum originally associated with @tag
170  */
171 gpointer
172 nm_auth_chain_steal_data (NMAuthChain *self, const char *tag)
173 {
174         ChainData *tmp;
175         gpointer value = NULL;
176         void *orig_key;
177
178         g_return_val_if_fail (self != NULL, NULL);
179         g_return_val_if_fail (tag != NULL, NULL);
180
181         if (g_hash_table_lookup_extended (self->data, tag, &orig_key, (gpointer)&tmp)) {
182                 g_hash_table_steal (self->data, tag);
183                 value = tmp->data;
184                 /* Make sure the destroy handler isn't called when freeing */
185                 tmp->destroy = NULL;
186                 chain_data_free (tmp);
187                 g_free (orig_key);
188         }
189         return value;
190 }
191
192 void
193 nm_auth_chain_set_data (NMAuthChain *self,
194                         const char *tag,
195                         gpointer data,
196                         GDestroyNotify data_destroy)
197 {
198         g_return_if_fail (self != NULL);
199         g_return_if_fail (tag != NULL);
200
201         if (data == NULL)
202                 g_hash_table_remove (self->data, tag);
203         else {
204                 g_hash_table_insert (self->data,
205                                      g_strdup (tag),
206                                      chain_data_new (data, data_destroy));
207         }
208 }
209
210 gulong
211 nm_auth_chain_get_data_ulong (NMAuthChain *self, const char *tag)
212 {
213         gulong *data;
214
215         g_return_val_if_fail (self != NULL, 0);
216         g_return_val_if_fail (tag != NULL, 0);
217
218         data = _get_data (self, tag);
219         return data ? *data : 0ul;
220 }
221
222 void
223 nm_auth_chain_set_data_ulong (NMAuthChain *self,
224                               const char *tag,
225                               gulong data)
226 {
227         gulong *ptr;
228
229         g_return_if_fail (self != NULL);
230         g_return_if_fail (tag != NULL);
231
232         ptr = g_malloc (sizeof (*ptr));
233         *ptr = data;
234         nm_auth_chain_set_data (self, tag, ptr, g_free);
235 }
236
237 NMAuthSubject *
238 nm_auth_chain_get_subject (NMAuthChain *self)
239 {
240         g_return_val_if_fail (self != NULL, NULL);
241
242         return self->subject;
243 }
244
245 NMAuthCallResult
246 nm_auth_chain_get_result (NMAuthChain *self, const char *permission)
247 {
248         gpointer data;
249
250         g_return_val_if_fail (self != NULL, NM_AUTH_CALL_RESULT_UNKNOWN);
251         g_return_val_if_fail (permission != NULL, NM_AUTH_CALL_RESULT_UNKNOWN);
252
253         data = _get_data (self, permission);
254         return data ? GPOINTER_TO_UINT (data) : NM_AUTH_CALL_RESULT_UNKNOWN;
255 }
256
257 static AuthCall *
258 auth_call_new (NMAuthChain *chain, const char *permission)
259 {
260         AuthCall *call;
261
262         call = g_slice_new0 (AuthCall);
263         call->chain = chain;
264         call->permission = g_strdup (permission);
265         return call;
266 }
267
268 static void
269 auth_call_free (AuthCall *call)
270 {
271         g_free (call->permission);
272         g_clear_object (&call->cancellable);
273         g_slice_free (AuthCall, call);
274 }
275
276 static gboolean
277 auth_call_complete (AuthCall *call)
278 {
279         NMAuthChain *self;
280
281         g_return_val_if_fail (call, G_SOURCE_REMOVE);
282
283         self = call->chain;
284
285         g_return_val_if_fail (self, G_SOURCE_REMOVE);
286         g_return_val_if_fail (g_slist_find (self->calls, call), G_SOURCE_REMOVE);
287
288         self->calls = g_slist_remove (self->calls, call);
289
290         if (!self->calls) {
291                 g_assert (!self->idle_id && !self->done);
292                 self->idle_id = g_idle_add (auth_chain_finish, self);
293         }
294         auth_call_free (call);
295         return FALSE;
296 }
297
298 static void
299 auth_call_cancel (gpointer user_data)
300 {
301         AuthCall *call = user_data;
302
303         if (call->cancellable) {
304                 /* we don't free call immediately. Instead we cancel the async operation
305                  * and set cancellable to NULL. pk_call_cb() will check for this and
306                  * do the final cleanup. */
307                 g_cancellable_cancel (call->cancellable);
308                 g_clear_object (&call->cancellable);
309         } else {
310                 g_source_remove (call->call_idle_id);
311                 auth_call_free (call);
312         }
313 }
314
315 #if WITH_POLKIT
316 static void
317 pk_call_cb (GObject *object, GAsyncResult *result, gpointer user_data)
318 {
319         AuthCall *call = user_data;
320         GError *error = NULL;
321         gboolean is_authorized, is_challenge;
322
323         nm_auth_manager_polkit_authority_check_authorization_finish (NM_AUTH_MANAGER (object),
324                                                                      result,
325                                                                      &is_authorized,
326                                                                      &is_challenge,
327                                                                      &error);
328
329         /* If the call is already canceled do nothing */
330         if (!call->cancellable) {
331                 nm_log_dbg (LOGD_CORE, "callback already cancelled");
332                 g_clear_error (&error);
333                 auth_call_free (call);
334                 return;
335         }
336
337         if (error) {
338                 nm_log_warn (LOGD_CORE, "error requesting auth for %s: %s",
339                              call->permission, error->message);
340
341                 if (!call->chain->error) {
342                         call->chain->error = error;
343                         error = NULL;
344                 } else
345                         g_clear_error (&error);
346         } else {
347                 guint call_result = NM_AUTH_CALL_RESULT_UNKNOWN;
348
349                 if (is_authorized) {
350                         /* Caller has the permission */
351                         call_result = NM_AUTH_CALL_RESULT_YES;
352                 } else if (is_challenge) {
353                         /* Caller could authenticate to get the permission */
354                         call_result = NM_AUTH_CALL_RESULT_AUTH;
355                 } else
356                         call_result = NM_AUTH_CALL_RESULT_NO;
357
358                 nm_auth_chain_set_data (call->chain, call->permission, GUINT_TO_POINTER (call_result), NULL);
359         }
360
361         auth_call_complete (call);
362 }
363 #endif
364
365 void
366 nm_auth_chain_add_call (NMAuthChain *self,
367                         const char *permission,
368                         gboolean allow_interaction)
369 {
370         AuthCall *call;
371         NMAuthManager *auth_manager = nm_auth_manager_get ();
372
373         g_return_if_fail (self != NULL);
374         g_return_if_fail (permission && *permission);
375         g_return_if_fail (self->subject);
376         g_return_if_fail (nm_auth_subject_is_unix_process (self->subject) || nm_auth_subject_is_internal (self->subject));
377         g_return_if_fail (!self->idle_id && !self->done);
378
379         call = auth_call_new (self, permission);
380         self->calls = g_slist_append (self->calls, call);
381
382         if (   nm_auth_subject_is_internal (self->subject)
383             || nm_auth_subject_get_unix_process_uid (self->subject) == 0
384             || !nm_auth_manager_get_polkit_enabled (auth_manager)) {
385                 /* Root user or non-polkit always gets the permission */
386                 nm_auth_chain_set_data (self, permission, GUINT_TO_POINTER (NM_AUTH_CALL_RESULT_YES), NULL);
387                 call->call_idle_id = g_idle_add ((GSourceFunc) auth_call_complete, call);
388         } else {
389                 /* Non-root always gets authenticated when using polkit */
390 #if WITH_POLKIT
391                 call->cancellable = g_cancellable_new ();
392                 nm_auth_manager_polkit_authority_check_authorization (auth_manager,
393                                                                       self->subject,
394                                                                       permission,
395                                                                       allow_interaction,
396                                                                       call->cancellable,
397                                                                       pk_call_cb,
398                                                                       call);
399 #else
400                 if (!call->chain->error) {
401                         call->chain->error = g_error_new_literal (DBUS_GERROR,
402                                                                   DBUS_GERROR_FAILED,
403                                                                   "Polkit support is disabled at compile time");
404                 }
405                 call->call_idle_id = g_idle_add ((GSourceFunc) auth_call_complete, call);
406 #endif
407         }
408 }
409
410 /**
411  * nm_auth_chain_unref:
412  * @self: the auth-chain
413  *
414  * Unrefs the auth-chain. By unrefing the auth-chain, you also cancel
415  * the receipt of the done-callback. IOW, the callback will not be invoked.
416  *
417  * The only exception is, if you call nm_auth_chain_unref() from inside
418  * the callback. In this case, @self stays alive until the callback returns.
419  */
420 void
421 nm_auth_chain_unref (NMAuthChain *self)
422 {
423         g_return_if_fail (self != NULL);
424         g_return_if_fail (self->refcount > 0);
425
426         self->refcount--;
427         if (self->refcount > 0)
428                 return;
429
430         if (self->idle_id)
431                 g_source_remove (self->idle_id);
432
433         g_object_unref (self->subject);
434
435         if (self->context)
436                 g_object_unref (self->context);
437
438         g_slist_free_full (self->calls, auth_call_cancel);
439
440         g_clear_error (&self->error);
441         g_hash_table_destroy (self->data);
442
443         memset (self, 0, sizeof (NMAuthChain));
444         g_slice_free (NMAuthChain, self);
445 }
446
447 /************ utils **************/
448
449 gboolean
450 nm_auth_is_subject_in_acl (NMConnection *connection,
451                            NMAuthSubject *subject,
452                            char **out_error_desc)
453 {
454         NMSettingConnection *s_con;
455         const char *user = NULL;
456         gulong uid;
457
458         g_return_val_if_fail (connection != NULL, FALSE);
459         g_return_val_if_fail (NM_IS_AUTH_SUBJECT (subject), FALSE);
460         g_return_val_if_fail (nm_auth_subject_is_internal (subject) || nm_auth_subject_is_unix_process (subject), FALSE);
461
462         if (nm_auth_subject_is_internal (subject))
463                 return TRUE;
464
465         uid = nm_auth_subject_get_unix_process_uid (subject);
466
467         /* Root gets a free pass */
468         if (0 == uid)
469                 return TRUE;
470
471         if (!nm_session_monitor_uid_to_user (uid, &user)) {
472                 if (out_error_desc)
473                         *out_error_desc = g_strdup_printf ("Could not determine username for uid %lu", uid);
474                 return FALSE;
475         }
476
477         s_con = nm_connection_get_setting_connection (connection);
478         if (!s_con) {
479                 /* This can only happen when called from AddAndActivate, so we know
480                  * the user will be authorized when the connection is completed.
481                  */
482                 return TRUE;
483         }
484
485         /* Match the username returned by the session check to a user in the ACL */
486         if (!nm_setting_connection_permissions_user_allowed (s_con, user)) {
487                 if (out_error_desc)
488                         *out_error_desc = g_strdup_printf ("uid %lu has no permission to perform this operation", uid);
489                 return FALSE;
490         }
491
492         return TRUE;
493 }
494
495