session_policy_local: Use callback helpers
[platform/upstream/connman.git] / plugins / session_policy_local.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2012  BMW Car IT GbmH. All rights reserved.
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <errno.h>
27 #include <string.h>
28 #include <sys/inotify.h>
29 #include <sys/stat.h>
30
31 #include <glib.h>
32
33 #include <gdbus.h>
34
35 #define CONNMAN_API_SUBJECT_TO_CHANGE
36 #include <connman/plugin.h>
37 #include <connman/log.h>
38 #include <connman/session.h>
39 #include <connman/dbus.h>
40 #include <connman/inotify.h>
41
42 #define POLICYDIR STORAGEDIR "/session_policy_local"
43
44 #define MODE            (S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | \
45                         S_IXGRP | S_IROTH | S_IXOTH)
46
47 static DBusConnection *connection;
48
49 static GHashTable *policy_hash;
50 static GHashTable *session_hash;
51
52 struct create_data {
53         struct connman_session *session;
54 };
55
56 struct policy_data {
57         int refcount;
58         char *ident;
59
60         struct connman_session *session;
61         struct connman_session_config *config;
62 };
63
64 static void cleanup_policy(gpointer user_data)
65 {
66         struct policy_data *policy = user_data;
67
68         if (policy->config != NULL)
69                 g_slist_free(policy->config->allowed_bearers);
70
71         g_free(policy->ident);
72         g_free(policy->config);
73         g_free(policy);
74 }
75
76 static char *parse_ident(const unsigned char *context)
77 {
78         char *str, *ident, **tokens;
79
80         /*
81          * SELinux combines Role-Based Access Control (RBAC), Type
82          * Enforcment (TE) and optionally Multi-Level Security (MLS).
83          *
84          * When SELinux is enabled all processes and files are labeled
85          * with a contex that contains information such as user, role
86          * type (and optionally a level). E.g.
87          *
88          * $ ls -Z
89          * -rwxrwxr-x. wagi wagi unconfined_u:object_r:haifux_exec_t:s0 session_ui.py
90          *
91          * For identifyng application we (ab)using the type
92          * information. In the above example the haifux_exec_t type
93          * will be transfered to haifux_t as defined in the domain
94          * transition and thus we are able to identify the application
95          * as haifux_t.
96          */
97
98         str = g_strdup((const gchar*)context);
99         if (str == NULL)
100                 return NULL;
101
102         DBG("SELinux context %s", str);
103
104         tokens = g_strsplit(str, ":", 0);
105         if (tokens == NULL) {
106                 g_free(str);
107                 return NULL;
108         }
109
110         /* Use the SELinux type as identification token. */
111         ident = g_strdup(tokens[2]);
112
113         g_strfreev(tokens);
114         g_free(str);
115
116         return ident;
117 }
118
119 static struct policy_data *create_policy(const char *ident)
120 {
121         struct policy_data *policy;
122
123         DBG("ident %s", ident);
124
125         policy = g_try_new0(struct policy_data, 1);
126         if (policy == NULL)
127                 return NULL;
128
129         policy->config = connman_session_create_default_config();
130         if (policy->config == NULL) {
131                 g_free(policy);
132                 return NULL;
133         }
134
135         policy->refcount = 1;
136         policy->ident = g_strdup(ident);
137
138         g_hash_table_replace(policy_hash, policy->ident, policy);
139
140         return policy;
141 }
142
143 static struct policy_data *policy_ref(struct policy_data *policy)
144 {
145         DBG("%p %s ref %d", policy, policy->ident, policy->refcount + 1);
146
147         __sync_fetch_and_add(&policy->refcount, 1);
148
149         return policy;
150 }
151
152 static void policy_unref(struct policy_data *policy)
153 {
154         DBG(" %p %s ref %d", policy, policy->ident, policy->refcount - 1);
155
156         if (__sync_fetch_and_sub(&policy->refcount, 1) != 1)
157                 return;
158
159         g_hash_table_remove(policy_hash, policy->ident);
160 };
161
162 static void selinux_context_reply(const unsigned char *context, void *user_data,
163                                         int err)
164 {
165         struct cb_data *cbd = user_data;
166         connman_session_config_cb cb = cbd->cb;
167         struct create_data *data = cbd->data;
168         struct policy_data *policy;
169         struct connman_session_config *config = NULL;
170         char *ident = NULL;
171
172         DBG("session %p", data->session);
173
174         if (err < 0)
175                 goto done;
176
177         ident = parse_ident(context);
178         if (ident == NULL) {
179                 err = -EINVAL;
180                 goto done;
181         }
182
183         policy = g_hash_table_lookup(policy_hash, ident);
184         if (policy != NULL) {
185                 policy_ref(policy);
186                 policy->session = data->session;
187         } else {
188                 policy = create_policy(ident);
189                 if (policy == NULL) {
190                         err = -ENOMEM;
191                         goto done;
192                 }
193         }
194
195         g_hash_table_replace(session_hash, data->session, policy);
196         config = policy->config;
197
198 done:
199         (*cb)(data->session, config, cbd->user_data, err);
200
201         g_free(cbd);
202         g_free(data);
203         g_free(ident);
204 }
205
206 static int policy_local_create(struct connman_session *session,
207                                 connman_session_config_cb callback,
208                                 void *user_data)
209 {
210         struct cb_data *cbd = cb_data_new(callback, user_data);
211         struct create_data *data;
212         const char *owner;
213         int err;
214
215         DBG("session %p", session);
216
217         data = g_try_new0(struct create_data, 1);
218         if (data == NULL)
219                 return -ENOMEM;
220         cbd->data = data;
221
222         data->session = session;
223
224         owner = connman_session_get_owner(session);
225
226         err = connman_dbus_get_selinux_context(connection, owner,
227                                         selinux_context_reply,
228                                         cbd);
229         if (err < 0) {
230                 connman_error("Could not get SELinux context");
231                 g_free(data);
232                 g_free(cbd);
233                 return err;
234         }
235
236         return 0;
237 }
238
239 static void policy_local_destroy(struct connman_session *session)
240 {
241         struct policy_data *policy;
242
243         DBG("session %p", session);
244
245         policy = g_hash_table_lookup(session_hash, session);
246         if (policy == NULL)
247                 return;
248
249         g_hash_table_remove(session_hash, session);
250         policy->session = NULL;
251         policy_unref(policy);
252 }
253
254 static struct connman_session_policy session_policy_local = {
255         .name = "session local policy configuration",
256         .priority = CONNMAN_SESSION_POLICY_PRIORITY_DEFAULT,
257         .create = policy_local_create,
258         .destroy = policy_local_destroy,
259 };
260
261 static int load_keyfile(const char *pathname, GKeyFile **keyfile)
262 {
263         GError *error = NULL;
264         int err;
265
266         DBG("Loading %s", pathname);
267
268         *keyfile = g_key_file_new();
269
270         if (g_key_file_load_from_file(*keyfile, pathname, 0, &error) == FALSE)
271                 goto err;
272
273         return 0;
274
275 err:
276         /*
277          * The fancy G_FILE_ERROR_* codes are identical to the native
278          * error codes.
279          */
280         err = -error->code;
281
282         DBG("Unable to load %s: %s", pathname, error->message);
283         g_clear_error(&error);
284
285         g_key_file_free(*keyfile);
286         *keyfile = NULL;
287
288         return err;
289 }
290
291 static int load_policy(struct policy_data *policy)
292 {
293         struct connman_session_config *config = policy->config;
294         GKeyFile *keyfile;
295         char *pathname;
296         char *str, **tokens;
297         int i, err = 0;
298
299         connman_session_set_default_config(config);
300
301         pathname = g_strdup_printf("%s/%s", POLICYDIR, policy->ident);
302         if(pathname == NULL)
303                 return -ENOMEM;
304
305         err = load_keyfile(pathname, &keyfile);
306         if (err < 0) {
307                 g_free(pathname);
308
309                 if (err == -ENOENT) {
310                         /* Ignore empty files */
311                         return 0;
312                 }
313
314                 return err;
315         }
316
317         config->priority = g_key_file_get_boolean(keyfile, "Default",
318                                                 "Priority", NULL);
319
320         str = g_key_file_get_string(keyfile, "Default", "RoamingPolicy",
321                                 NULL);
322         if (str != NULL) {
323                 config->roaming_policy = connman_session_parse_roaming_policy(str);
324                 g_free(str);
325         }
326
327         str = g_key_file_get_string(keyfile, "Default", "ConnectionType",
328                                 NULL);
329         if (str != NULL) {
330                 config->type = connman_session_parse_connection_type(str);
331                 g_free(str);
332         }
333
334         config->ecall = g_key_file_get_boolean(keyfile, "Default",
335                                                 "EmergencyCall", NULL);
336
337         str = g_key_file_get_string(keyfile, "Default", "AllowedBearers",
338                                 NULL);
339
340         if (str != NULL) {
341                 g_slist_free(config->allowed_bearers);
342                 config->allowed_bearers = NULL;
343
344                 tokens = g_strsplit(str, " ", 0);
345
346                 for (i = 0; tokens[i] != NULL; i++) {
347                         err = connman_session_parse_bearers(tokens[i],
348                                         &config->allowed_bearers);
349                         if (err < 0)
350                                 break;
351                 }
352
353                 g_free(str);
354                 g_strfreev(tokens);
355         }
356
357         g_key_file_free(keyfile);
358         g_free(pathname);
359
360         return err;
361 }
362
363 static void update_session(struct connman_session *session)
364 {
365         if (connman_session_config_update(session) < 0)
366                 connman_session_destroy(session);
367 }
368
369 static void remove_policy(struct policy_data *policy)
370 {
371         connman_bool_t update = FALSE;
372         int err;
373
374         if (policy->session != NULL)
375                 update = TRUE;
376
377         policy_unref(policy);
378
379         if (update == FALSE)
380                 return;
381
382         err = connman_session_set_default_config(policy->config);
383         if (err < 0) {
384                 connman_session_destroy(policy->session);
385                 return;
386         }
387
388         update_session(policy->session);
389 }
390
391 static void notify_handler(struct inotify_event *event,
392                                         const char *ident)
393 {
394         struct policy_data *policy;
395         int err;
396
397         if (ident == NULL)
398                 return;
399
400         policy = g_hash_table_lookup(policy_hash, ident);
401
402         if (event->mask & (IN_CREATE | IN_MOVED_TO)) {
403                 connman_info("Policy added for '%s'", ident);
404
405                 if (policy != NULL)
406                         policy_ref(policy);
407                 else
408                         policy = create_policy(ident);
409
410                 err = load_policy(policy);
411                 if (err < 0) {
412                         connman_warn("Loading policy file '%s' failed with %s",
413                                         ident, strerror(-err));
414                         return;
415                 }
416         }
417
418         if (policy == NULL)
419                 return;
420
421         if (event->mask & IN_MODIFY) {
422                 connman_info("Policy modifed for '%s'", ident);
423
424                 err = load_policy(policy);
425                 if (err < 0) {
426                         connman_warn("Loading policy file '%s' failed with %s",
427                                         ident, strerror(-err));
428                         return;
429                 }
430         }
431
432         if (event->mask & (IN_DELETE | IN_MOVED_FROM)) {
433                 connman_info("Policy deleted for '%s'", ident);
434
435                 remove_policy(policy);
436                 return;
437         }
438
439         if (policy->session != NULL)
440                 update_session(policy->session);
441 }
442
443 static int read_policies(void)
444 {
445         GDir *dir;
446         int err = 0;
447
448         DBG("");
449
450         dir = g_dir_open(POLICYDIR, 0, NULL);
451         if (dir != NULL) {
452                 const gchar *file;
453
454                 while ((file = g_dir_read_name(dir)) != NULL) {
455                         struct policy_data *policy;
456
457                         policy = create_policy(file);
458                         if (policy == NULL) {
459                                 err = -ENOMEM;
460                                 break;
461                         }
462
463                         err = load_policy(policy);
464                         if (err < 0)
465                                 break;
466                 }
467
468                 g_dir_close(dir);
469         }
470
471         return err;
472 }
473
474 static int session_policy_local_init(void)
475 {
476         int err;
477
478         /* If the dir doesn't exist, create it */
479         if (g_file_test(POLICYDIR, G_FILE_TEST_IS_DIR) == FALSE) {
480                 if (mkdir(POLICYDIR, MODE) < 0) {
481                         if (errno != EEXIST)
482                                 return -errno;
483                 }
484         }
485
486         connection = connman_dbus_get_connection();
487         if (connection == NULL)
488                 return -EIO;
489
490         session_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal,
491                                                 NULL, NULL);
492         if (session_hash == NULL) {
493                 err = -ENOMEM;
494                 goto err;
495         }
496
497         policy_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
498                                         NULL, cleanup_policy);
499         if (policy_hash == NULL) {
500                 err = -ENOMEM;
501                 goto err;
502         }
503
504         err = connman_inotify_register(POLICYDIR, notify_handler);
505         if (err < 0)
506                 goto err;
507
508         err = read_policies();
509         if (err < 0)
510                 goto err_notify;
511
512         err = connman_session_policy_register(&session_policy_local);
513         if (err < 0)
514                 goto err_notify;
515
516         return 0;
517
518 err_notify:
519
520         connman_inotify_unregister(POLICYDIR, notify_handler);
521
522 err:
523         if (session_hash != NULL)
524                 g_hash_table_destroy(session_hash);
525         if (policy_hash != NULL)
526                 g_hash_table_destroy(policy_hash);
527
528         connman_session_policy_unregister(&session_policy_local);
529
530         dbus_connection_unref(connection);
531
532         return err;
533 }
534
535 static void session_policy_local_exit(void)
536 {
537         g_hash_table_destroy(session_hash);
538         g_hash_table_destroy(policy_hash);
539
540         connman_session_policy_unregister(&session_policy_local);
541
542         dbus_connection_unref(connection);
543
544         connman_inotify_unregister(POLICYDIR, notify_handler);
545 }
546
547 CONNMAN_PLUGIN_DEFINE(session_policy_local,
548                 "Session local file policy configuration plugin",
549                 VERSION, CONNMAN_PLUGIN_PRIORITY_DEFAULT,
550                 session_policy_local_init, session_policy_local_exit)