session_policy_local: Add some more debug information
[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         DBG("policy %p", policy);
69
70         if (policy->config != NULL)
71                 g_slist_free(policy->config->allowed_bearers);
72
73         g_free(policy->ident);
74         g_free(policy->config);
75         g_free(policy);
76 }
77
78 static char *parse_selinux_type(const char *context)
79 {
80         char *ident, **tokens;
81
82         /*
83          * SELinux combines Role-Based Access Control (RBAC), Type
84          * Enforcment (TE) and optionally Multi-Level Security (MLS).
85          *
86          * When SELinux is enabled all processes and files are labeled
87          * with a contex that contains information such as user, role
88          * type (and optionally a level). E.g.
89          *
90          * $ ls -Z
91          * -rwxrwxr-x. wagi wagi unconfined_u:object_r:haifux_exec_t:s0 session_ui.py
92          *
93          * For identifyng application we (ab)using the type
94          * information. In the above example the haifux_exec_t type
95          * will be transfered to haifux_t as defined in the domain
96          * transition and thus we are able to identify the application
97          * as haifux_t.
98          */
99
100         tokens = g_strsplit(context, ":", 0);
101         if (g_strv_length(tokens) < 2) {
102                 g_strfreev(tokens);
103                 return NULL;
104         }
105
106         /* Use the SELinux type as identification token. */
107         ident = g_strdup(tokens[2]);
108
109         g_strfreev(tokens);
110
111         return ident;
112 }
113
114 static struct policy_data *create_policy(const char *ident)
115 {
116         struct policy_data *policy;
117
118         DBG("ident %s", ident);
119
120         policy = g_new0(struct policy_data, 1);
121         policy->refcount = 1;
122
123         DBG("policy %p", policy);
124
125         policy->config = connman_session_create_default_config();
126         policy->ident = g_strdup(ident);
127
128         g_hash_table_replace(policy_hash, policy->ident, policy);
129
130         return policy;
131 }
132
133 static struct policy_data *policy_ref(struct policy_data *policy)
134 {
135         DBG("%p %s ref %d", policy, policy->ident, policy->refcount + 1);
136
137         __sync_fetch_and_add(&policy->refcount, 1);
138
139         return policy;
140 }
141
142 static void policy_unref(struct policy_data *policy)
143 {
144         DBG(" %p %s ref %d", policy, policy->ident, policy->refcount - 1);
145
146         if (__sync_fetch_and_sub(&policy->refcount, 1) != 1)
147                 return;
148
149         g_hash_table_remove(policy_hash, policy->ident);
150 };
151
152 static void selinux_context_reply(const unsigned char *context, void *user_data,
153                                         int err)
154 {
155         struct cb_data *cbd = user_data;
156         connman_session_config_func_t cb = cbd->cb;
157         struct create_data *data = cbd->data;
158         struct policy_data *policy;
159         struct connman_session_config *config = NULL;
160         char *ident = NULL;
161
162         DBG("session %p", data->session);
163
164         if (err < 0)
165                 goto done;
166
167         DBG("SELinux context %s", context);
168
169         ident = parse_selinux_type((const char*)context);
170         if (ident == NULL) {
171                 err = -EINVAL;
172                 goto done;
173         }
174
175         policy = g_hash_table_lookup(policy_hash, ident);
176         if (policy != NULL) {
177                 policy_ref(policy);
178                 policy->session = data->session;
179         } else
180                 policy = create_policy(ident);
181
182         g_hash_table_replace(session_hash, data->session, policy);
183         config = policy->config;
184
185 done:
186         (*cb)(data->session, config, cbd->user_data, err);
187
188         g_free(cbd);
189         g_free(data);
190         g_free(ident);
191 }
192
193 static int policy_local_create(struct connman_session *session,
194                                 connman_session_config_func_t cb,
195                                 void *user_data)
196 {
197         struct cb_data *cbd = cb_data_new(cb, user_data);
198         struct create_data *data;
199         const char *owner;
200         int err;
201
202         DBG("session %p", session);
203
204         data = g_new0(struct create_data, 1);
205         cbd->data = data;
206
207         data->session = session;
208
209         owner = connman_session_get_owner(session);
210
211         err = connman_dbus_get_selinux_context(connection, owner,
212                                         selinux_context_reply,
213                                         cbd);
214         if (err < 0) {
215                 connman_error("Could not get SELinux context");
216                 g_free(data);
217                 g_free(cbd);
218                 return err;
219         }
220
221         return 0;
222 }
223
224 static void policy_local_destroy(struct connman_session *session)
225 {
226         struct policy_data *policy;
227
228         DBG("session %p", session);
229
230         policy = g_hash_table_lookup(session_hash, session);
231         if (policy == NULL)
232                 return;
233
234         g_hash_table_remove(session_hash, session);
235         policy->session = NULL;
236         policy_unref(policy);
237 }
238
239 static struct connman_session_policy session_policy_local = {
240         .name = "session local policy configuration",
241         .priority = CONNMAN_SESSION_POLICY_PRIORITY_DEFAULT,
242         .create = policy_local_create,
243         .destroy = policy_local_destroy,
244 };
245
246 static int load_keyfile(const char *pathname, GKeyFile **keyfile)
247 {
248         GError *error = NULL;
249         int err;
250
251         DBG("Loading %s", pathname);
252
253         *keyfile = g_key_file_new();
254
255         if (g_key_file_load_from_file(*keyfile, pathname, 0, &error) == FALSE)
256                 goto err;
257
258         return 0;
259
260 err:
261         /*
262          * The fancy G_FILE_ERROR_* codes are identical to the native
263          * error codes.
264          */
265         err = -error->code;
266
267         DBG("Unable to load %s: %s", pathname, error->message);
268         g_clear_error(&error);
269
270         g_key_file_free(*keyfile);
271         *keyfile = NULL;
272
273         return err;
274 }
275
276 static int load_policy(struct policy_data *policy)
277 {
278         struct connman_session_config *config = policy->config;
279         GKeyFile *keyfile;
280         char *pathname;
281         char *str, **tokens;
282         int i, err = 0;
283
284         connman_session_set_default_config(config);
285
286         pathname = g_strdup_printf("%s/%s", POLICYDIR, policy->ident);
287
288         err = load_keyfile(pathname, &keyfile);
289         if (err < 0) {
290                 g_free(pathname);
291
292                 if (err == -ENOENT) {
293                         /* Ignore empty files */
294                         return 0;
295                 }
296
297                 return err;
298         }
299
300         config->priority = g_key_file_get_boolean(keyfile, "Default",
301                                                 "Priority", NULL);
302
303         str = g_key_file_get_string(keyfile, "Default", "RoamingPolicy",
304                                 NULL);
305         if (str != NULL) {
306                 config->roaming_policy = connman_session_parse_roaming_policy(str);
307                 g_free(str);
308         }
309
310         str = g_key_file_get_string(keyfile, "Default", "ConnectionType",
311                                 NULL);
312         if (str != NULL) {
313                 config->type = connman_session_parse_connection_type(str);
314                 g_free(str);
315         }
316
317         config->ecall = g_key_file_get_boolean(keyfile, "Default",
318                                                 "EmergencyCall", NULL);
319
320         str = g_key_file_get_string(keyfile, "Default", "AllowedBearers",
321                                 NULL);
322
323         if (str != NULL) {
324                 g_slist_free(config->allowed_bearers);
325                 config->allowed_bearers = NULL;
326
327                 tokens = g_strsplit(str, " ", 0);
328
329                 for (i = 0; tokens[i] != NULL; i++) {
330                         err = connman_session_parse_bearers(tokens[i],
331                                         &config->allowed_bearers);
332                         if (err < 0)
333                                 break;
334                 }
335
336                 g_free(str);
337                 g_strfreev(tokens);
338         }
339
340         g_key_file_free(keyfile);
341         g_free(pathname);
342
343         return err;
344 }
345
346 static void update_session(struct policy_data *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 remove_policy(struct policy_data *policy)
358 {
359         if (policy->session != NULL)
360                 connman_session_set_default_config(policy->config);
361         update_session(policy);
362
363         policy_unref(policy);
364 }
365
366 static void notify_handler(struct inotify_event *event,
367                                         const char *ident)
368 {
369         struct policy_data *policy;
370         int err;
371
372         if (ident == NULL)
373                 return;
374
375         policy = g_hash_table_lookup(policy_hash, ident);
376
377         if (event->mask & (IN_CREATE | IN_MOVED_TO)) {
378                 connman_info("Policy added for '%s'", ident);
379
380                 if (policy != NULL)
381                         policy_ref(policy);
382                 else
383                         policy = create_policy(ident);
384
385                 err = load_policy(policy);
386                 if (err < 0) {
387                         connman_warn("Loading policy file '%s' failed with %s",
388                                         ident, strerror(-err));
389                         return;
390                 }
391         }
392
393         if (policy == NULL)
394                 return;
395
396         if (event->mask & IN_MODIFY) {
397                 connman_info("Policy modifed for '%s'", ident);
398
399                 err = load_policy(policy);
400                 if (err < 0) {
401                         connman_warn("Loading policy file '%s' failed with %s",
402                                         ident, strerror(-err));
403                         return;
404                 }
405         }
406
407         if (event->mask & (IN_DELETE | IN_MOVED_FROM)) {
408                 connman_info("Policy deleted for '%s'", ident);
409
410                 remove_policy(policy);
411                 return;
412         }
413
414         update_session(policy);
415 }
416
417 static int read_policies(void)
418 {
419         GDir *dir;
420         int err = 0;
421
422         DBG("");
423
424         dir = g_dir_open(POLICYDIR, 0, NULL);
425         if (dir != NULL) {
426                 const gchar *file;
427
428                 while ((file = g_dir_read_name(dir)) != NULL) {
429                         struct policy_data *policy;
430
431                         policy = create_policy(file);
432                         err = load_policy(policy);
433                         if (err < 0)
434                                 break;
435                 }
436
437                 g_dir_close(dir);
438         }
439
440         return err;
441 }
442
443 static int session_policy_local_init(void)
444 {
445         int err;
446
447         DBG("");
448
449         /* If the dir doesn't exist, create it */
450         if (g_file_test(POLICYDIR, G_FILE_TEST_IS_DIR) == FALSE) {
451                 if (mkdir(POLICYDIR, MODE) < 0) {
452                         if (errno != EEXIST)
453                                 return -errno;
454                 }
455         }
456
457         connection = connman_dbus_get_connection();
458         if (connection == NULL)
459                 return -EIO;
460
461         session_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal,
462                                                 NULL, NULL);
463         policy_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
464                                         NULL, cleanup_policy);
465
466         err = connman_inotify_register(POLICYDIR, notify_handler);
467         if (err < 0)
468                 goto err;
469
470         err = read_policies();
471         if (err < 0)
472                 goto err_notify;
473
474         err = connman_session_policy_register(&session_policy_local);
475         if (err < 0)
476                 goto err_notify;
477
478         return 0;
479
480 err_notify:
481
482         connman_inotify_unregister(POLICYDIR, notify_handler);
483
484 err:
485         if (session_hash != NULL)
486                 g_hash_table_destroy(session_hash);
487         if (policy_hash != NULL)
488                 g_hash_table_destroy(policy_hash);
489
490         connman_session_policy_unregister(&session_policy_local);
491
492         dbus_connection_unref(connection);
493
494         return err;
495 }
496
497 static void session_policy_local_exit(void)
498 {
499         DBG("");
500
501         g_hash_table_destroy(session_hash);
502         g_hash_table_destroy(policy_hash);
503
504         connman_session_policy_unregister(&session_policy_local);
505
506         dbus_connection_unref(connection);
507
508         connman_inotify_unregister(POLICYDIR, notify_handler);
509 }
510
511 CONNMAN_PLUGIN_DEFINE(session_policy_local,
512                 "Session local file policy configuration plugin",
513                 VERSION, CONNMAN_PLUGIN_PRIORITY_DEFAULT,
514                 session_policy_local_init, session_policy_local_exit)