Merge "Fix SIGSEV on freeing server domains list" into tizen
[platform/upstream/connman.git] / src / firewall-iptables.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2013,2015  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         bool enabled;
50         char *table;
51         char *chain;
52         char *rule_spec;
53 };
54
55 struct firewall_context {
56         GList *rules;
57 };
58
59 static GSList *managed_tables;
60 static struct firewall_context *connmark_ctx;
61 static unsigned int connmark_ref;
62
63 static int chain_to_index(const char *chain_name)
64 {
65         if (!g_strcmp0(builtin_chains[NF_IP_PRE_ROUTING], chain_name))
66                 return NF_IP_PRE_ROUTING;
67         if (!g_strcmp0(builtin_chains[NF_IP_LOCAL_IN], chain_name))
68                 return NF_IP_LOCAL_IN;
69         if (!g_strcmp0(builtin_chains[NF_IP_FORWARD], chain_name))
70                 return NF_IP_FORWARD;
71         if (!g_strcmp0(builtin_chains[NF_IP_LOCAL_OUT], chain_name))
72                 return NF_IP_LOCAL_OUT;
73         if (!g_strcmp0(builtin_chains[NF_IP_POST_ROUTING], chain_name))
74                 return NF_IP_POST_ROUTING;
75
76         return -1;
77 }
78
79 static int managed_chain_to_index(const char *chain_name)
80 {
81         if (!g_str_has_prefix(chain_name, CHAIN_PREFIX))
82                 return -1;
83
84         return chain_to_index(chain_name + strlen(CHAIN_PREFIX));
85 }
86
87 static int insert_managed_chain(const char *table_name, int id)
88 {
89         char *rule, *managed_chain;
90         int err;
91
92         managed_chain = g_strdup_printf("%s%s", CHAIN_PREFIX,
93                                         builtin_chains[id]);
94
95         err = __connman_iptables_new_chain(AF_INET, table_name, managed_chain);
96         if (err < 0)
97                 goto out;
98
99         rule = g_strdup_printf("-j %s", managed_chain);
100         err = __connman_iptables_insert(AF_INET, table_name, builtin_chains[id],
101                                                 rule);
102         g_free(rule);
103         if (err < 0) {
104                 __connman_iptables_delete_chain(AF_INET, table_name,
105                                                         managed_chain);
106                 goto out;
107         }
108
109 out:
110         g_free(managed_chain);
111
112         return err;
113 }
114
115 static int delete_managed_chain(const char *table_name, int id)
116 {
117         char *rule, *managed_chain;
118         int err;
119
120         managed_chain = g_strdup_printf("%s%s", CHAIN_PREFIX,
121                                         builtin_chains[id]);
122
123         rule = g_strdup_printf("-j %s", managed_chain);
124         err = __connman_iptables_delete(AF_INET, table_name, builtin_chains[id],
125                                         rule);
126         g_free(rule);
127
128         if (err < 0)
129                 goto out;
130
131         err =  __connman_iptables_delete_chain(AF_INET, table_name,
132                                                 managed_chain);
133
134 out:
135         g_free(managed_chain);
136
137         return err;
138 }
139
140 static int insert_managed_rule(const char *table_name,
141                                 const char *chain_name,
142                                 const char *rule_spec)
143 {
144         struct connman_managed_table *mtable = NULL;
145         GSList *list;
146         char *chain;
147         int id, err;
148
149         id = chain_to_index(chain_name);
150         if (id < 0) {
151                 /* This chain is not managed */
152                 chain = g_strdup(chain_name);
153                 goto out;
154         }
155
156         for (list = managed_tables; list; list = list->next) {
157                 mtable = list->data;
158
159                 if (g_strcmp0(mtable->name, table_name) == 0)
160                         break;
161
162                 mtable = NULL;
163         }
164
165         if (!mtable) {
166                 mtable = g_new0(struct connman_managed_table, 1);
167                 mtable->name = g_strdup(table_name);
168
169                 managed_tables = g_slist_prepend(managed_tables, mtable);
170         }
171
172         if (mtable->chains[id] == 0) {
173                 DBG("table %s add managed chain for %s",
174                         table_name, chain_name);
175
176                 err = insert_managed_chain(table_name, id);
177                 if (err < 0)
178                         return err;
179         }
180
181         mtable->chains[id]++;
182         chain = g_strdup_printf("%s%s", CHAIN_PREFIX, chain_name);
183
184 out:
185         err = __connman_iptables_append(AF_INET, table_name, chain, rule_spec);
186
187         g_free(chain);
188
189         return err;
190  }
191
192 static int delete_managed_rule(const char *table_name,
193                                 const char *chain_name,
194                                 const char *rule_spec)
195  {
196         struct connman_managed_table *mtable = NULL;
197         GSList *list;
198         int id, err;
199         char *managed_chain;
200
201         id = chain_to_index(chain_name);
202         if (id < 0) {
203                 /* This chain is not managed */
204                 return __connman_iptables_delete(AF_INET, table_name,
205                                                         chain_name, rule_spec);
206         }
207
208         managed_chain = g_strdup_printf("%s%s", CHAIN_PREFIX, chain_name);
209
210         err = __connman_iptables_delete(AF_INET, table_name, managed_chain,
211                                                 rule_spec);
212
213         for (list = managed_tables; list; list = list->next) {
214                 mtable = list->data;
215
216                 if (g_strcmp0(mtable->name, table_name) == 0)
217                         break;
218
219                 mtable = NULL;
220         }
221
222         if (!mtable) {
223                 err = -ENOENT;
224                 goto out;
225         }
226
227         mtable->chains[id]--;
228         if (mtable->chains[id] > 0)
229                 goto out;
230
231         DBG("table %s remove managed chain for %s",
232                         table_name, chain_name);
233
234         err = delete_managed_chain(table_name, id);
235
236  out:
237         g_free(managed_chain);
238
239         return err;
240 }
241
242 static void cleanup_managed_table(gpointer user_data)
243 {
244         struct connman_managed_table *table = user_data;
245
246         g_free(table->name);
247         g_free(table);
248 }
249
250 static void cleanup_fw_rule(gpointer user_data)
251 {
252         struct fw_rule *rule = user_data;
253
254         g_free(rule->rule_spec);
255         g_free(rule->chain);
256         g_free(rule->table);
257         g_free(rule);
258 }
259
260 struct firewall_context *__connman_firewall_create(void)
261 {
262         struct firewall_context *ctx;
263
264         ctx = g_new0(struct firewall_context, 1);
265
266         return ctx;
267 }
268
269 void __connman_firewall_destroy(struct firewall_context *ctx)
270 {
271         g_list_free_full(ctx->rules, cleanup_fw_rule);
272         g_free(ctx);
273 }
274
275 static int enable_rule(struct fw_rule *rule)
276 {
277         int err;
278
279         if (rule->enabled)
280                 return -EALREADY;
281
282         DBG("%s %s %s", rule->table, rule->chain, rule->rule_spec);
283
284         err = insert_managed_rule(rule->table, rule->chain, rule->rule_spec);
285         if (err < 0)
286                 return err;
287
288         err = __connman_iptables_commit(AF_INET, rule->table);
289         if (err < 0)
290                 return err;
291
292         rule->enabled = true;
293
294         return 0;
295 }
296
297 static int disable_rule(struct fw_rule *rule)
298 {
299         int err;
300
301         if (!rule->enabled)
302                 return -EALREADY;
303
304         err = delete_managed_rule(rule->table, rule->chain, rule->rule_spec);
305         if (err < 0) {
306                 connman_error("Cannot remove previously installed "
307                         "iptables rules: %s", strerror(-err));
308                 return err;
309         }
310
311         err = __connman_iptables_commit(AF_INET, rule->table);
312         if (err < 0) {
313                 connman_error("Cannot remove previously installed "
314                         "iptables rules: %s", strerror(-err));
315                 return err;
316         }
317
318         rule->enabled = false;
319
320         return 0;
321 }
322
323 static void firewall_add_rule(struct firewall_context *ctx,
324                                 const char *table,
325                                 const char *chain,
326                                 const char *rule_fmt, ...)
327 {
328         va_list args;
329         char *rule_spec;
330         struct fw_rule *rule;
331
332         va_start(args, rule_fmt);
333
334         rule_spec = g_strdup_vprintf(rule_fmt, args);
335
336         va_end(args);
337
338         rule = g_new0(struct fw_rule, 1);
339
340         rule->enabled = false;
341         rule->table = g_strdup(table);
342         rule->chain = g_strdup(chain);
343         rule->rule_spec = rule_spec;
344
345         ctx->rules = g_list_append(ctx->rules, rule);
346 }
347
348 static void firewall_remove_rules(struct firewall_context *ctx)
349 {
350         g_list_free_full(ctx->rules, cleanup_fw_rule);
351         ctx->rules = NULL;
352 }
353
354 static int firewall_enable_rules(struct firewall_context *ctx)
355 {
356         struct fw_rule *rule;
357         GList *list;
358         int err = -ENOENT;
359
360         for (list = g_list_first(ctx->rules); list; list = g_list_next(list)) {
361                 rule = list->data;
362
363                 err = enable_rule(rule);
364                 if (err < 0)
365                         break;
366         }
367
368         return err;
369 }
370
371 static int firewall_disable_rules(struct firewall_context *ctx)
372 {
373         struct fw_rule *rule;
374         GList *list;
375         int e;
376         int err = -ENOENT;
377
378         for (list = g_list_last(ctx->rules); list;
379                         list = g_list_previous(list)) {
380                 rule = list->data;
381
382                 e = disable_rule(rule);
383
384                 /* Report last error back */
385                 if (e == 0 && err == -ENOENT)
386                         err = 0;
387                 else if (e < 0)
388                         err = e;
389         }
390
391         return err;
392 }
393
394 int __connman_firewall_enable_nat(struct firewall_context *ctx,
395                                 char *address, unsigned char prefixlen,
396                                 char *interface)
397 {
398         int err;
399
400         firewall_add_rule(ctx, "nat", "POSTROUTING",
401                                 "-s %s/%d -o %s -j MASQUERADE",
402                                 address, prefixlen, interface);
403
404         err = firewall_enable_rules(ctx);
405         if (err)
406                 firewall_remove_rules(ctx);
407         return err;
408 }
409
410 int __connman_firewall_disable_nat(struct firewall_context *ctx)
411 {
412         int err;
413
414         err = firewall_disable_rules(ctx);
415         if (err < 0) {
416                 DBG("could not disable NAT rule");
417                 return err;
418         }
419
420         firewall_remove_rules(ctx);
421         return 0;
422 }
423
424 int __connman_firewall_enable_snat(struct firewall_context *ctx,
425                                 int index, const char *ifname,
426                                 const char *addr)
427 {
428         int err;
429
430         firewall_add_rule(ctx, "nat", "POSTROUTING",
431                                 "-o %s -j SNAT --to-source %s",
432                                 ifname, addr);
433
434         err = firewall_enable_rules(ctx);
435         if (err)
436                 firewall_remove_rules(ctx);
437         return err;
438 }
439
440 int __connman_firewall_disable_snat(struct firewall_context *ctx)
441 {
442         int err;
443
444         err = firewall_disable_rules(ctx);
445         if (err < 0) {
446                 DBG("could not disable SNAT rule");
447                 return err;
448         }
449
450         firewall_remove_rules(ctx);
451         return 0;
452 }
453
454 static int firewall_enable_connmark(void)
455 {
456         int err;
457
458         if (connmark_ref > 0) {
459                 connmark_ref++;
460                 return 0;
461         }
462
463         connmark_ctx = __connman_firewall_create();
464
465         firewall_add_rule(connmark_ctx, "mangle", "INPUT",
466                                         "-j CONNMARK --restore-mark");
467         firewall_add_rule(connmark_ctx, "mangle", "POSTROUTING",
468                                         "-j CONNMARK --save-mark");
469         err = firewall_enable_rules(connmark_ctx);
470         if (err) {
471                 __connman_firewall_destroy(connmark_ctx);
472                 connmark_ctx = NULL;
473                 return err;
474         }
475         connmark_ref++;
476         return 0;
477 }
478
479 static void firewall_disable_connmark(void)
480 {
481         connmark_ref--;
482         if (connmark_ref > 0)
483                 return;
484
485         firewall_disable_rules(connmark_ctx);
486         __connman_firewall_destroy(connmark_ctx);
487         connmark_ctx = NULL;
488 }
489
490 int __connman_firewall_enable_marking(struct firewall_context *ctx,
491                                         enum connman_session_id_type id_type,
492                                         char *id, const char *src_ip,
493                                         uint32_t mark)
494 {
495         int err;
496
497         err = firewall_enable_connmark();
498         if (err)
499                 return err;
500
501         switch (id_type) {
502         case CONNMAN_SESSION_ID_TYPE_UID:
503                 firewall_add_rule(ctx, "mangle", "OUTPUT",
504                                 "-m owner --uid-owner %s -j MARK --set-mark %d",
505                                         id, mark);
506                 break;
507         case CONNMAN_SESSION_ID_TYPE_GID:
508                 firewall_add_rule(ctx, "mangle", "OUTPUT",
509                                 "-m owner --gid-owner %s -j MARK --set-mark %d",
510                                         id, mark);
511                 break;
512         case CONNMAN_SESSION_ID_TYPE_UNKNOWN:
513                 break;
514         case CONNMAN_SESSION_ID_TYPE_LSM:
515         default:
516                 return -EINVAL;
517         }
518
519         if (src_ip) {
520                 firewall_add_rule(ctx, "mangle", "OUTPUT",
521                                 "-s %s -j MARK --set-mark %d",
522                                         src_ip, mark);
523         }
524
525         return firewall_enable_rules(ctx);
526 }
527
528 int __connman_firewall_disable_marking(struct firewall_context *ctx)
529 {
530         firewall_disable_connmark();
531         return firewall_disable_rules(ctx);
532 }
533
534 static void iterate_chains_cb(const char *chain_name, void *user_data)
535 {
536         GSList **chains = user_data;
537         int id;
538
539         id = managed_chain_to_index(chain_name);
540         if (id < 0)
541                 return;
542
543         *chains = g_slist_prepend(*chains, GINT_TO_POINTER(id));
544 }
545
546 static void flush_table(const char *table_name)
547 {
548         GSList *chains = NULL, *list;
549         char *rule, *managed_chain;
550         int id, err;
551
552         __connman_iptables_iterate_chains(AF_INET, table_name,
553                                                 iterate_chains_cb, &chains);
554
555         for (list = chains; list; list = list->next) {
556                 id = GPOINTER_TO_INT(list->data);
557
558                 managed_chain = g_strdup_printf("%s%s", CHAIN_PREFIX,
559                                                 builtin_chains[id]);
560
561                 rule = g_strdup_printf("-j %s", managed_chain);
562                 err = __connman_iptables_delete(AF_INET, table_name,
563                                                 builtin_chains[id], rule);
564                 if (err < 0) {
565                         connman_warn("Failed to delete jump rule '%s': %s",
566                                 rule, strerror(-err));
567                 }
568                 g_free(rule);
569
570                 err = __connman_iptables_flush_chain(AF_INET, table_name,
571                                                         managed_chain);
572                 if (err < 0) {
573                         connman_warn("Failed to flush chain '%s': %s",
574                                 managed_chain, strerror(-err));
575                 }
576                 err = __connman_iptables_delete_chain(AF_INET, table_name,
577                                                         managed_chain);
578                 if (err < 0) {
579                         connman_warn("Failed to delete chain '%s': %s",
580                                 managed_chain, strerror(-err));
581                 }
582
583                 g_free(managed_chain);
584         }
585
586         err = __connman_iptables_commit(AF_INET, table_name);
587         if (err < 0) {
588                 connman_warn("Failed to flush table '%s': %s",
589                         table_name, strerror(-err));
590         }
591
592         g_slist_free(chains);
593 }
594
595 static void flush_all_tables(void)
596 {
597         /* Flush the tables ConnMan might have modified
598          * But do so if only ConnMan has done something with
599          * iptables */
600
601         if (!g_file_test("/proc/net/ip_tables_names",
602                         G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
603                 return;
604         }
605
606         flush_table("filter");
607         flush_table("mangle");
608         flush_table("nat");
609 }
610
611 int __connman_firewall_init(void)
612 {
613         DBG("");
614
615         __connman_iptables_init();
616         flush_all_tables();
617
618         return 0;
619 }
620
621 void __connman_firewall_cleanup(void)
622 {
623         DBG("");
624
625         g_slist_free_full(managed_tables, cleanup_managed_table);
626         __connman_iptables_cleanup();
627 }