session_policy_local: Rework policy file handling
[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 *file_hash;    /* (filename, policy_file) */
50 static GHashTable *session_hash; /* (connman_session, policy_config) */
51
52 /* Global lookup table for mapping sessions to policies */
53 static GHashTable *selinux_hash; /* (lsm context, policy_group) */
54
55 struct create_data {
56         struct connman_session *session;
57 };
58
59 /*
60  * A instance of struct policy_file is created per file in
61  * POLICYDIR.
62  */
63 struct policy_file {
64         /*
65          * A valid file is a keyfile with one ore more groups. All
66          * groups are keept in this list.
67          */
68         GSList *groups;
69 };
70
71 struct policy_group {
72         char *selinux;
73
74         /*
75          * Each policy_group owns a config and is not shared with
76          * sessions. Instead each session copies the valued from this
77          * object.
78          */
79         struct connman_session_config *config;
80
81         /* All 'users' of this policy. */
82         GSList *sessions;
83 };
84
85 /* A struct policy_config object is created and owned by a session. */
86 struct policy_config {
87         char *selinux;
88
89         /* The policy config owned by the session */
90         struct connman_session_config *config;
91
92         /* To which policy belongs this policy_config */
93         struct connman_session *session;
94         /*
95          * Points to the policy_group when a config has been applied
96          * from a file.
97          */
98         struct policy_group *group;
99 };
100
101 static void copy_session_config(struct connman_session_config *dst,
102                         struct connman_session_config *src)
103 {
104         g_slist_free(dst->allowed_bearers);
105         dst->allowed_bearers = g_slist_copy(src->allowed_bearers);
106         dst->ecall = src->ecall;
107         dst->type = src->type;
108         dst->roaming_policy = src->roaming_policy;
109         dst->priority = src->priority;
110 }
111
112 static void set_policy(struct policy_config *policy,
113                         struct policy_group *group)
114 {
115         DBG("policy %p group %p", policy, group);
116
117         group->sessions = g_slist_prepend(group->sessions, policy);
118         policy->group = group;
119
120         copy_session_config(policy->config, group->config);
121 }
122
123 static char *parse_selinux_type(const char *context)
124 {
125         char *ident, **tokens;
126
127         /*
128          * SELinux combines Role-Based Access Control (RBAC), Type
129          * Enforcment (TE) and optionally Multi-Level Security (MLS).
130          *
131          * When SELinux is enabled all processes and files are labeled
132          * with a contex that contains information such as user, role
133          * type (and optionally a level). E.g.
134          *
135          * $ ls -Z
136          * -rwxrwxr-x. wagi wagi unconfined_u:object_r:haifux_exec_t:s0 session_ui.py
137          *
138          * For identifyng application we (ab)using the type
139          * information. In the above example the haifux_exec_t type
140          * will be transfered to haifux_t as defined in the domain
141          * transition and thus we are able to identify the application
142          * as haifux_t.
143          */
144
145         tokens = g_strsplit(context, ":", 0);
146         if (g_strv_length(tokens) < 2) {
147                 g_strfreev(tokens);
148                 return NULL;
149         }
150
151         /* Use the SELinux type as identification token. */
152         ident = g_strdup(tokens[2]);
153
154         g_strfreev(tokens);
155
156         return ident;
157 }
158
159 static struct policy_config *create_policy(void)
160 {
161         struct policy_config *policy;
162
163         policy = g_new0(struct policy_config, 1);
164
165         DBG("policy %p", policy);
166
167         policy->config = connman_session_create_default_config();
168
169         return policy;
170 }
171
172 static void selinux_context_reply(const unsigned char *context, void *user_data,
173                                         int err)
174 {
175         struct cb_data *cbd = user_data;
176         connman_session_config_func_t cb = cbd->cb;
177         struct create_data *data = cbd->data;
178         struct policy_config *policy;
179         struct policy_group *group;
180         struct connman_session_config *config = NULL;
181         char *ident = NULL;
182
183         DBG("session %p", data->session);
184
185         if (err < 0)
186                 goto done;
187
188         DBG("SELinux context %s", context);
189
190         ident = parse_selinux_type((const char*)context);
191         if (ident == NULL) {
192                 err = -EINVAL;
193                 goto done;
194         }
195
196         policy = create_policy();
197         policy->selinux = g_strdup(ident);
198         policy->session = data->session;
199
200         group = g_hash_table_lookup(selinux_hash, policy->selinux);
201         if (group != NULL)
202                 set_policy(policy, group);
203
204         g_hash_table_replace(session_hash, data->session, policy);
205         config = policy->config;
206
207 done:
208         (*cb)(data->session, config, cbd->user_data, err);
209
210         g_free(cbd);
211         g_free(data);
212         g_free(ident);
213 }
214
215 static int policy_local_create(struct connman_session *session,
216                                 connman_session_config_func_t cb,
217                                 void *user_data)
218 {
219         struct cb_data *cbd = cb_data_new(cb, user_data);
220         struct create_data *data;
221         const char *owner;
222         int err;
223
224         DBG("session %p", session);
225
226         data = g_new0(struct create_data, 1);
227         cbd->data = data;
228
229         data->session = session;
230
231         owner = connman_session_get_owner(session);
232
233         err = connman_dbus_get_selinux_context(connection, owner,
234                                         selinux_context_reply,
235                                         cbd);
236         if (err < 0) {
237                 connman_error("Could not get SELinux context");
238                 g_free(data);
239                 g_free(cbd);
240                 return err;
241         }
242
243         return 0;
244 }
245
246 static void policy_local_destroy(struct connman_session *session)
247 {
248         struct policy_data *policy;
249
250         DBG("session %p", session);
251
252         policy = g_hash_table_lookup(session_hash, session);
253         if (policy == NULL)
254                 return;
255
256         g_hash_table_remove(session_hash, session);
257 }
258
259 static struct connman_session_policy session_policy_local = {
260         .name = "session local policy configuration",
261         .priority = CONNMAN_SESSION_POLICY_PRIORITY_DEFAULT,
262         .create = policy_local_create,
263         .destroy = policy_local_destroy,
264 };
265
266 static int load_keyfile(const char *pathname, GKeyFile **keyfile)
267 {
268         GError *error = NULL;
269         int err;
270
271         *keyfile = g_key_file_new();
272
273         if (g_key_file_load_from_file(*keyfile, pathname, 0, &error) == FALSE)
274                 goto err;
275
276         return 0;
277
278 err:
279         /*
280          * The fancy G_FILE_ERROR_* codes are identical to the native
281          * error codes.
282          */
283         err = -error->code;
284
285         DBG("Unable to load %s: %s", pathname, error->message);
286         g_clear_error(&error);
287
288         g_key_file_free(*keyfile);
289         *keyfile = NULL;
290
291         return err;
292 }
293
294 static int load_policy(GKeyFile *keyfile, const char *groupname,
295                         struct policy_group *group)
296 {
297         struct connman_session_config *config = group->config;
298         char *str, **tokens;
299         int i, err = 0;
300
301         group->selinux = g_key_file_get_string(keyfile, groupname,
302                                                 "selinux", NULL);
303         if (group->selinux == NULL)
304                 return -EINVAL;
305
306         config->priority = g_key_file_get_boolean(keyfile, groupname,
307                                                 "Priority", NULL);
308
309         str = g_key_file_get_string(keyfile, groupname, "RoamingPolicy",
310                                 NULL);
311         if (str != NULL) {
312                 config->roaming_policy = connman_session_parse_roaming_policy(str);
313                 g_free(str);
314         }
315
316         str = g_key_file_get_string(keyfile, groupname, "ConnectionType",
317                                 NULL);
318         if (str != NULL) {
319                 config->type = connman_session_parse_connection_type(str);
320                 g_free(str);
321         }
322
323         config->ecall = g_key_file_get_boolean(keyfile, groupname,
324                                                 "EmergencyCall", NULL);
325
326         str = g_key_file_get_string(keyfile, groupname, "AllowedBearers",
327                                 NULL);
328         if (str != NULL) {
329                 tokens = g_strsplit(str, " ", 0);
330
331                 for (i = 0; tokens[i] != NULL; i++) {
332                         err = connman_session_parse_bearers(tokens[i],
333                                         &config->allowed_bearers);
334                         if (err < 0)
335                                 break;
336                 }
337
338                 g_free(str);
339                 g_strfreev(tokens);
340         }
341
342         DBG("group %p selinux %s", group, group->selinux);
343
344         return err;
345 }
346 static void update_session(struct policy_config *policy)
347 {
348         DBG("policy %p session %p", policy, policy->session);
349
350         if (policy->session == NULL)
351                 return;
352
353         if (connman_session_config_update(policy->session) < 0)
354                 connman_session_destroy(policy->session);
355 }
356
357 static void set_default_config(gpointer user_data)
358 {
359         struct policy_config *policy = user_data;
360
361         connman_session_set_default_config(policy->config);
362         policy->group = NULL;
363         update_session(policy);
364 }
365
366 static void cleanup_config(gpointer user_data)
367 {
368         struct policy_config *policy = user_data;
369
370         DBG("policy %p group %p", policy, policy->group);
371
372         if (policy->group != NULL)
373                 policy->group->sessions =
374                         g_slist_remove(policy->group->sessions, policy);
375
376         g_slist_free(policy->config->allowed_bearers);
377         g_free(policy->config);
378         g_free(policy->selinux);
379         g_free(policy);
380 }
381
382 static void cleanup_group(gpointer user_data)
383 {
384         struct policy_group *group = user_data;
385
386         DBG("group %p", group);
387
388         g_slist_free_full(group->sessions, set_default_config);
389
390         g_slist_free(group->config->allowed_bearers);
391         g_free(group->config);
392         if (group->selinux != NULL)
393                 g_hash_table_remove(selinux_hash, group->selinux);
394         g_free(group->selinux);
395         g_free(group);
396 }
397
398 static void cleanup_file(gpointer user_data)
399 {
400         struct policy_file *file = user_data;
401
402         DBG("file %p", file);
403
404         g_slist_free_full(file->groups, cleanup_group);
405         g_free(file);
406 }
407
408 static void recheck_sessions(void)
409 {
410         GHashTableIter iter;
411         gpointer value, key;
412         struct policy_group *group = NULL;
413
414         g_hash_table_iter_init(&iter, session_hash);
415         while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
416                 struct policy_config *policy = value;
417
418                 if (policy->group != NULL)
419                         continue;
420
421                 group = g_hash_table_lookup(selinux_hash, policy->selinux);
422                 if (group != NULL) {
423                         set_policy(policy, group);
424                         update_session(policy);
425                 }
426         }
427 }
428
429 static int load_file(const char *filename, struct policy_file *file)
430 {
431         GKeyFile *keyfile;
432         struct policy_group *group;
433         char **groupnames;
434         char *pathname;
435         int err = 0, i;
436
437         DBG("%s", filename);
438
439         pathname = g_strdup_printf("%s/%s", POLICYDIR, filename);
440         err = load_keyfile(pathname, &keyfile);
441         g_free(pathname);
442
443         if (err < 0)
444                 return err;
445
446         groupnames = g_key_file_get_groups(keyfile, NULL);
447
448         for (i = 0; groupnames[i] != NULL; i++) {
449                 group = g_new0(struct policy_group, 1);
450                 group->config = g_new0(struct connman_session_config, 1);
451
452                 err = load_policy(keyfile, groupnames[i], group);
453                 if (err < 0) {
454                         g_free(group->config);
455                         g_free(group);
456                         break;
457                 }
458                 g_hash_table_replace(selinux_hash, group->selinux, group);
459
460                 file->groups = g_slist_prepend(file->groups, group);
461         }
462
463         g_strfreev(groupnames);
464
465         if (err < 0)
466                 g_slist_free_full(file->groups, cleanup_group);
467
468         g_key_file_free(keyfile);
469
470         return err;
471 }
472
473 static connman_bool_t is_filename_valid(const char *filename)
474 {
475         if (filename == NULL)
476                 return FALSE;
477
478         if (filename[0] == '.')
479                 return FALSE;
480
481         return g_str_has_suffix(filename, ".policy");
482 }
483
484 static int read_policies()
485 {
486         GDir *dir;
487         const gchar *filename;
488         struct policy_file *file;
489
490         DBG("");
491
492         dir = g_dir_open(POLICYDIR, 0, NULL);
493         if (dir == NULL)
494                 return -EINVAL;
495
496         while ((filename = g_dir_read_name(dir)) != NULL) {
497                 if (is_filename_valid(filename) == FALSE)
498                         continue;
499
500                 file = g_new0(struct policy_file, 1);
501                 if (load_file(filename, file) < 0) {
502                         g_free(file);
503                         continue;
504                 }
505
506                 g_hash_table_replace(file_hash, g_strdup(filename), file);
507         }
508
509         g_dir_close(dir);
510
511         return 0;
512 }
513
514
515 static void notify_handler(struct inotify_event *event,
516                                         const char *filename)
517 {
518         struct policy_file *file;
519
520         DBG("event %x file %s", event->mask, filename);
521
522         if (event->mask & IN_CREATE)
523                 return;
524
525         if (is_filename_valid(filename) == FALSE)
526                 return;
527
528         /*
529          * load_file() will modify the global selinux/uid/gid hash
530          * tables. We need to remove the old entries first before
531          * else the table points to the wrong entries.
532          */
533         g_hash_table_remove(file_hash, filename);
534
535         if (event->mask & (IN_DELETE | IN_MOVED_FROM))
536                 return;
537
538         if (event->mask & (IN_MOVED_TO | IN_MODIFY)) {
539                 connman_info("Policy update for '%s'", filename);
540
541                 file = g_new0(struct policy_file, 1);
542                 if (load_file(filename, file) < 0) {
543                         g_free(file);
544                         return;
545                 }
546
547                 g_hash_table_replace(file_hash, g_strdup(filename), file);
548                 recheck_sessions();
549         }
550 }
551
552 static int session_policy_local_init(void)
553 {
554         int err;
555
556         DBG("");
557
558         /* If the dir doesn't exist, create it */
559         if (g_file_test(POLICYDIR, G_FILE_TEST_IS_DIR) == FALSE) {
560                 if (mkdir(POLICYDIR, MODE) < 0) {
561                         if (errno != EEXIST)
562                                 return -errno;
563                 }
564         }
565
566         connection = connman_dbus_get_connection();
567         if (connection == NULL)
568                 return -EIO;
569
570         file_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
571                                         g_free, cleanup_file);
572         session_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal,
573                                                 NULL, cleanup_config);
574         selinux_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
575                                         NULL, NULL);
576
577         err = connman_inotify_register(POLICYDIR, notify_handler);
578         if (err < 0)
579                 goto err;
580
581         err = connman_session_policy_register(&session_policy_local);
582         if (err < 0)
583                 goto err_notify;
584
585         read_policies();
586
587         return 0;
588
589 err_notify:
590
591         connman_inotify_unregister(POLICYDIR, notify_handler);
592
593 err:
594         if (file_hash != NULL)
595                 g_hash_table_destroy(file_hash);
596
597         if (session_hash != NULL)
598                 g_hash_table_destroy(session_hash);
599
600         if (selinux_hash != NULL)
601                 g_hash_table_destroy(selinux_hash);
602
603         connman_session_policy_unregister(&session_policy_local);
604
605         dbus_connection_unref(connection);
606
607         return err;
608 }
609
610 static void session_policy_local_exit(void)
611 {
612         DBG("");
613
614         g_hash_table_destroy(file_hash);
615         g_hash_table_destroy(session_hash);
616         g_hash_table_destroy(selinux_hash);
617
618         connman_session_policy_unregister(&session_policy_local);
619
620         dbus_connection_unref(connection);
621
622         connman_inotify_unregister(POLICYDIR, notify_handler);
623 }
624
625 CONNMAN_PLUGIN_DEFINE(session_policy_local,
626                 "Session local file policy configuration plugin",
627                 VERSION, CONNMAN_PLUGIN_PRIORITY_DEFAULT,
628                 session_policy_local_init, session_policy_local_exit)