Imported Upstream version 1.24
[platform/upstream/connman.git] / src / firewall.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2013  BMW Car IT GmbH.
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
28 #include <xtables.h>
29 #include <linux/netfilter_ipv4/ip_tables.h>
30
31 #include "connman.h"
32
33 #define CHAIN_PREFIX "connman-"
34
35 static const char *builtin_chains[] = {
36         [NF_IP_PRE_ROUTING]     = "PREROUTING",
37         [NF_IP_LOCAL_IN]        = "INPUT",
38         [NF_IP_FORWARD]         = "FORWARD",
39         [NF_IP_LOCAL_OUT]       = "OUTPUT",
40         [NF_IP_POST_ROUTING]    = "POSTROUTING",
41 };
42
43 struct connman_managed_table {
44         char *name;
45         unsigned int chains[NF_INET_NUMHOOKS];
46 };
47
48 struct fw_rule {
49         char *table;
50         char *chain;
51         char *rule_spec;
52 };
53
54 struct firewall_context {
55         GList *rules;
56 };
57
58 static GSList *managed_tables;
59
60 static bool firewall_is_up;
61
62 static int chain_to_index(const char *chain_name)
63 {
64         if (!g_strcmp0(builtin_chains[NF_IP_PRE_ROUTING], chain_name))
65                 return NF_IP_PRE_ROUTING;
66         if (!g_strcmp0(builtin_chains[NF_IP_LOCAL_IN], chain_name))
67                 return NF_IP_LOCAL_IN;
68         if (!g_strcmp0(builtin_chains[NF_IP_FORWARD], chain_name))
69                 return NF_IP_FORWARD;
70         if (!g_strcmp0(builtin_chains[NF_IP_LOCAL_OUT], chain_name))
71                 return NF_IP_LOCAL_OUT;
72         if (!g_strcmp0(builtin_chains[NF_IP_POST_ROUTING], chain_name))
73                 return NF_IP_POST_ROUTING;
74
75         return -1;
76 }
77
78 static int managed_chain_to_index(const char *chain_name)
79 {
80         if (!g_str_has_prefix(chain_name, CHAIN_PREFIX))
81                 return -1;
82
83         return chain_to_index(chain_name + strlen(CHAIN_PREFIX));
84 }
85
86 static int insert_managed_chain(const char *table_name, int id)
87 {
88         char *rule, *managed_chain;
89         int err;
90
91         managed_chain = g_strdup_printf("%s%s", CHAIN_PREFIX,
92                                         builtin_chains[id]);
93
94         err = __connman_iptables_new_chain(table_name, managed_chain);
95         if (err < 0)
96                 goto out;
97
98         rule = g_strdup_printf("-j %s", managed_chain);
99         err = __connman_iptables_insert(table_name, builtin_chains[id], rule);
100         g_free(rule);
101         if (err < 0) {
102                 __connman_iptables_delete_chain(table_name, managed_chain);
103                 goto out;
104         }
105
106 out:
107         g_free(managed_chain);
108
109         return err;
110 }
111
112 static int delete_managed_chain(const char *table_name, int id)
113 {
114         char *rule, *managed_chain;
115         int err;
116
117         managed_chain = g_strdup_printf("%s%s", CHAIN_PREFIX,
118                                         builtin_chains[id]);
119
120         rule = g_strdup_printf("-j %s", managed_chain);
121         err = __connman_iptables_delete(table_name, builtin_chains[id], rule);
122         g_free(rule);
123
124         if (err < 0)
125                 goto out;
126
127         err =  __connman_iptables_delete_chain(table_name, managed_chain);
128
129 out:
130         g_free(managed_chain);
131
132         return err;
133 }
134
135 static int insert_managed_rule(const char *table_name,
136                                 const char *chain_name,
137                                 const char *rule_spec)
138 {
139         struct connman_managed_table *mtable = NULL;
140         GSList *list;
141         char *chain;
142         int id, err;
143
144         id = chain_to_index(chain_name);
145         if (id < 0) {
146                 /* This chain is not managed */
147                 chain = g_strdup(chain_name);
148                 goto out;
149         }
150
151         for (list = managed_tables; list; list = list->next) {
152                 mtable = list->data;
153
154                 if (g_strcmp0(mtable->name, table_name) == 0)
155                         break;
156
157                 mtable = NULL;
158         }
159
160         if (!mtable) {
161                 mtable = g_new0(struct connman_managed_table, 1);
162                 mtable->name = g_strdup(table_name);
163
164                 managed_tables = g_slist_prepend(managed_tables, mtable);
165         }
166
167         if (mtable->chains[id] == 0) {
168                 DBG("table %s add managed chain for %s",
169                         table_name, chain_name);
170
171                 err = insert_managed_chain(table_name, id);
172                 if (err < 0)
173                         return err;
174         }
175
176         mtable->chains[id]++;
177         chain = g_strdup_printf("%s%s", CHAIN_PREFIX, chain_name);
178
179 out:
180         err = __connman_iptables_append(table_name, chain, rule_spec);
181
182         g_free(chain);
183
184         return err;
185  }
186
187 static int delete_managed_rule(const char *table_name,
188                                 const char *chain_name,
189                                 const char *rule_spec)
190  {
191         struct connman_managed_table *mtable = NULL;
192         GSList *list;
193         int id, err;
194         char *managed_chain;
195
196         id = chain_to_index(chain_name);
197         if (id < 0) {
198                 /* This chain is not managed */
199                 return __connman_iptables_delete(table_name, chain_name,
200                                                         rule_spec);
201         }
202
203         managed_chain = g_strdup_printf("%s%s", CHAIN_PREFIX, chain_name);
204
205         err = __connman_iptables_delete(table_name, managed_chain,
206                                         rule_spec);
207
208         for (list = managed_tables; list; list = list->next) {
209                 mtable = list->data;
210
211                 if (g_strcmp0(mtable->name, table_name) == 0)
212                         break;
213
214                 mtable = NULL;
215         }
216
217         if (!mtable) {
218                 err = -ENOENT;
219                 goto out;
220         }
221
222         mtable->chains[id]--;
223         if (mtable->chains[id] > 0)
224                 goto out;
225
226         DBG("table %s remove managed chain for %s",
227                         table_name, chain_name);
228
229         err = delete_managed_chain(table_name, id);
230
231  out:
232         g_free(managed_chain);
233
234         return err;
235 }
236
237 static void cleanup_managed_table(gpointer user_data)
238 {
239         struct connman_managed_table *table = user_data;
240
241         g_free(table->name);
242         g_free(table);
243 }
244
245 static void cleanup_fw_rule(gpointer user_data)
246 {
247         struct fw_rule *rule = user_data;
248
249         g_free(rule->rule_spec);
250         g_free(rule->chain);
251         g_free(rule->table);
252         g_free(rule);
253 }
254
255 struct firewall_context *__connman_firewall_create(void)
256 {
257         struct firewall_context *ctx;
258
259         ctx = g_new0(struct firewall_context, 1);
260
261         return ctx;
262 }
263
264 void __connman_firewall_destroy(struct firewall_context *ctx)
265 {
266         g_list_free_full(ctx->rules, cleanup_fw_rule);
267         g_free(ctx);
268 }
269
270 int __connman_firewall_add_rule(struct firewall_context *ctx,
271                                 const char *table,
272                                 const char *chain,
273                                 const char *rule_fmt, ...)
274 {
275         va_list args;
276         char *rule_spec;
277         struct fw_rule *rule;
278
279         va_start(args, rule_fmt);
280
281         rule_spec = g_strdup_vprintf(rule_fmt, args);
282
283         va_end(args);
284
285         rule = g_new0(struct fw_rule, 1);
286
287         rule->table = g_strdup(table);
288         rule->chain = g_strdup(chain);
289         rule->rule_spec = rule_spec;
290
291         ctx->rules = g_list_append(ctx->rules, rule);
292
293         return 0;
294 }
295
296 static int firewall_disable(GList *rules)
297 {
298         struct fw_rule *rule;
299         GList *list;
300         int err;
301
302         for (list = rules; list; list = g_list_previous(list)) {
303                 rule = list->data;
304
305                 err = delete_managed_rule(rule->table,
306                                                 rule->chain, rule->rule_spec);
307                 if (err < 0) {
308                         connman_error("Cannot remove previously installed "
309                                 "iptables rules: %s", strerror(-err));
310                         return err;
311                 }
312
313                 err = __connman_iptables_commit(rule->table);
314                 if (err < 0) {
315                         connman_error("Cannot remove previously installed "
316                                 "iptables rules: %s", strerror(-err));
317                         return err;
318                 }
319         }
320
321         return 0;
322 }
323
324 int __connman_firewall_enable(struct firewall_context *ctx)
325 {
326         struct fw_rule *rule;
327         GList *list;
328         int err;
329
330         for (list = g_list_first(ctx->rules); list;
331                         list = g_list_next(list)) {
332                 rule = list->data;
333
334                 DBG("%s %s %s", rule->table, rule->chain, rule->rule_spec);
335
336                 err = insert_managed_rule(rule->table,
337                                                 rule->chain, rule->rule_spec);
338                 if (err < 0)
339                         goto err;
340
341                 err = __connman_iptables_commit(rule->table);
342                 if (err < 0)
343                         goto err;
344         }
345
346         firewall_is_up = true;
347
348         return 0;
349
350 err:
351         connman_warn("Failed to install iptables rules: %s", strerror(-err));
352
353         firewall_disable(g_list_previous(list));
354
355         return err;
356 }
357
358 int __connman_firewall_disable(struct firewall_context *ctx)
359 {
360         return firewall_disable(g_list_last(ctx->rules));
361 }
362
363 bool __connman_firewall_is_up(void)
364 {
365         return firewall_is_up;
366 }
367
368 static void iterate_chains_cb(const char *chain_name, void *user_data)
369 {
370         GSList **chains = user_data;
371         int id;
372
373         id = managed_chain_to_index(chain_name);
374         if (id < 0)
375                 return;
376
377         *chains = g_slist_prepend(*chains, GINT_TO_POINTER(id));
378 }
379
380 static void flush_table(const char *table_name)
381 {
382         GSList *chains = NULL, *list;
383         char *rule, *managed_chain;
384         int id, err;
385
386         __connman_iptables_iterate_chains(table_name, iterate_chains_cb,
387                                                 &chains);
388
389         for (list = chains; list; list = list->next) {
390                 id = GPOINTER_TO_INT(list->data);
391
392                 managed_chain = g_strdup_printf("%s%s", CHAIN_PREFIX,
393                                                 builtin_chains[id]);
394
395                 rule = g_strdup_printf("-j %s", managed_chain);
396                 err = __connman_iptables_delete(table_name,
397                                                 builtin_chains[id], rule);
398                 if (err < 0) {
399                         connman_warn("Failed to delete jump rule '%s': %s",
400                                 rule, strerror(-err));
401                 }
402                 g_free(rule);
403
404                 err = __connman_iptables_flush_chain(table_name, managed_chain);
405                 if (err < 0) {
406                         connman_warn("Failed to flush chain '%s': %s",
407                                 managed_chain, strerror(-err));
408                 }
409                 err = __connman_iptables_delete_chain(table_name, managed_chain);
410                 if (err < 0) {
411                         connman_warn("Failed to delete chain '%s': %s",
412                                 managed_chain, strerror(-err));
413                 }
414
415                 g_free(managed_chain);
416         }
417
418         err = __connman_iptables_commit(table_name);
419         if (err < 0) {
420                 connman_warn("Failed to flush table '%s': %s",
421                         table_name, strerror(-err));
422         }
423
424         g_slist_free(chains);
425 }
426
427 static void flush_all_tables(void)
428 {
429         /* Flush the tables ConnMan might have modified
430          * But do so if only ConnMan has done something with
431          * iptables */
432
433         if (!g_file_test("/proc/net/ip_tables_names",
434                         G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
435                 firewall_is_up = false;
436                 return;
437         }
438
439         firewall_is_up = true;
440
441         flush_table("filter");
442         flush_table("mangle");
443         flush_table("nat");
444 }
445
446 int __connman_firewall_init(void)
447 {
448         DBG("");
449
450         flush_all_tables();
451
452         return 0;
453 }
454
455 void __connman_firewall_cleanup(void)
456 {
457         DBG("");
458
459         g_slist_free_full(managed_tables, cleanup_managed_table);
460 }