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