1 /*
2 * virfirewalld.c: support for firewalld (https://firewalld.org)
3 *
4 * Copyright (C) 2019 Red Hat, Inc.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library 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 GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library. If not, see
18 * <http://www.gnu.org/licenses/>.
19 */
20
21 #include <config.h>
22
23 #include <stdarg.h>
24
25 #include "virfirewall.h"
26 #include "virfirewalld.h"
27 #define LIBVIRT_VIRFIREWALLDPRIV_H_ALLOW
28 #include "virfirewalldpriv.h"
29 #include "viralloc.h"
30 #include "virerror.h"
31 #include "virutil.h"
32 #include "virlog.h"
33 #include "virgdbus.h"
34 #include "virenum.h"
35
36 #define VIR_FROM_THIS VIR_FROM_FIREWALLD
37
38 VIR_LOG_INIT("util.firewalld");
39
40 /* used to convert virFirewallLayer enum values to strings
41 * understood by the firewalld.direct "passthrough" method
42 */
43 VIR_ENUM_DECL(virFirewallLayerFirewallD);
44 VIR_ENUM_IMPL(virFirewallLayerFirewallD,
45 VIR_FIREWALL_LAYER_LAST,
46 "eb",
47 "ipv4",
48 "ipv6",
49 );
50
51
52 VIR_ENUM_DECL(virFirewallDBackend);
53 VIR_ENUM_IMPL(virFirewallDBackend,
54 VIR_FIREWALLD_BACKEND_LAST,
55 "",
56 "iptables",
57 "nftables",
58 );
59
60
61 /**
62 * virFirewallDIsRegistered:
63 *
64 * Returns 0 if service is registered, -1 on fatal error, or -2 if service is not registered
65 */
66 int
virFirewallDIsRegistered(void)67 virFirewallDIsRegistered(void)
68 {
69 return virGDBusIsServiceRegistered(VIR_FIREWALL_FIREWALLD_SERVICE);
70 }
71
72 /**
73 * virFirewallDGetVersion:
74 * @version: pointer to location to save version in the form of:
75 * 1000000 * major + 1000 * minor + micro
76 *
77 * queries the firewalld version property from dbus, and converts it
78 * from a string into a number.
79 *
80 * Returns 0 if version was successfully retrieved, or -1 on error
81 */
82 int
virFirewallDGetVersion(unsigned long * version)83 virFirewallDGetVersion(unsigned long *version)
84 {
85 GDBusConnection *sysbus = virGDBusGetSystemBus();
86 g_autoptr(GVariant) message = NULL;
87 g_autoptr(GVariant) reply = NULL;
88 g_autoptr(GVariant) gvar = NULL;
89 char *versionStr;
90
91 if (!sysbus)
92 return -1;
93
94 message = g_variant_new("(ss)", "org.fedoraproject.FirewallD1", "version");
95
96 if (virGDBusCallMethod(sysbus,
97 &reply,
98 G_VARIANT_TYPE("(v)"),
99 NULL,
100 VIR_FIREWALL_FIREWALLD_SERVICE,
101 "/org/fedoraproject/FirewallD1",
102 "org.freedesktop.DBus.Properties",
103 "Get",
104 message) < 0)
105 return -1;
106
107 g_variant_get(reply, "(v)", &gvar);
108 g_variant_get(gvar, "&s", &versionStr);
109
110 if (virParseVersionString(versionStr, version, false) < 0) {
111 virReportError(VIR_ERR_INTERNAL_ERROR,
112 _("Failed to parse firewalld version '%s'"),
113 versionStr);
114 return -1;
115 }
116
117 VIR_DEBUG("FirewallD version: %s - %lu", versionStr, *version);
118
119 return 0;
120 }
121
122 /**
123 * virFirewallDGetBackend:
124 *
125 * Returns virVirewallDBackendType value representing which packet
126 * filtering backend is currently in use by firewalld, or -1 on error.
127 */
128 int
virFirewallDGetBackend(void)129 virFirewallDGetBackend(void)
130 {
131 GDBusConnection *sysbus = virGDBusGetSystemBus();
132 g_autoptr(GVariant) message = NULL;
133 g_autoptr(GVariant) reply = NULL;
134 g_autoptr(GVariant) gvar = NULL;
135 g_autoptr(virError) error = NULL;
136 char *backendStr = NULL;
137 int backend = -1;
138
139 if (!sysbus)
140 return -1;
141
142 error = g_new0(virError, 1);
143
144 message = g_variant_new("(ss)",
145 "org.fedoraproject.FirewallD1.config",
146 "FirewallBackend");
147
148 if (virGDBusCallMethod(sysbus,
149 &reply,
150 G_VARIANT_TYPE("(v)"),
151 error,
152 VIR_FIREWALL_FIREWALLD_SERVICE,
153 "/org/fedoraproject/FirewallD1/config",
154 "org.freedesktop.DBus.Properties",
155 "Get",
156 message) < 0)
157 return -1;
158
159 if (error->level == VIR_ERR_ERROR) {
160 /* we don't want to log any error in the case that
161 * FirewallBackend isn't implemented in this firewalld, since
162 * that just means that it is an old version, and only has an
163 * iptables backend.
164 */
165 VIR_DEBUG("Failed to get FirewallBackend setting, assuming 'iptables'");
166 return VIR_FIREWALLD_BACKEND_IPTABLES;
167 }
168
169 g_variant_get(reply, "(v)", &gvar);
170 g_variant_get(gvar, "&s", &backendStr);
171
172 VIR_DEBUG("FirewallD backend: %s", backendStr);
173
174 if ((backend = virFirewallDBackendTypeFromString(backendStr)) < 0) {
175 virReportError(VIR_ERR_INTERNAL_ERROR,
176 _("Unrecognized firewalld backend type: %s"),
177 backendStr);
178 return -1;
179 }
180
181 return backend;
182 }
183
184
185 /**
186 * virFirewallDGetZones:
187 * @zones: array of char *, each entry is a null-terminated zone name
188 * @nzones: number of entries in @zones
189 *
190 * Get the number of currently active firewalld zones, and their names
191 * in an array of null-terminated strings. The memory pointed to by
192 * @zones will belong to the caller, and must be freed.
193 *
194 * Returns 0 on success, -1 (and failure logged) on error
195 */
196 int
virFirewallDGetZones(char *** zones,size_t * nzones)197 virFirewallDGetZones(char ***zones, size_t *nzones)
198 {
199 GDBusConnection *sysbus = virGDBusGetSystemBus();
200 g_autoptr(GVariant) reply = NULL;
201 g_autoptr(GVariant) array = NULL;
202
203 *nzones = 0;
204 *zones = NULL;
205
206 if (!sysbus)
207 return -1;
208
209 if (virGDBusCallMethod(sysbus,
210 &reply,
211 G_VARIANT_TYPE("(as)"),
212 NULL,
213 VIR_FIREWALL_FIREWALLD_SERVICE,
214 "/org/fedoraproject/FirewallD1",
215 "org.fedoraproject.FirewallD1.zone",
216 "getZones",
217 NULL) < 0)
218 return -1;
219
220 g_variant_get(reply, "(@as)", &array);
221 *zones = g_variant_dup_strv(array, nzones);
222
223 return 0;
224 }
225
226
227 /**
228 * virFirewallDZoneExists:
229 * @match: name of zone to look for
230 *
231 * Returns true if the requested zone exists, or false if it doesn't exist
232 */
233 bool
virFirewallDZoneExists(const char * match)234 virFirewallDZoneExists(const char *match)
235 {
236 size_t nzones = 0, i;
237 char **zones = NULL;
238 bool result = false;
239
240 if (virFirewallDGetZones(&zones, &nzones) < 0)
241 goto cleanup;
242
243 for (i = 0; i < nzones; i++) {
244 if (STREQ_NULLABLE(zones[i], match))
245 result = true;
246 }
247
248 cleanup:
249 VIR_DEBUG("Requested zone '%s' %s exist",
250 match, result ? "does" : "doesn't");
251 for (i = 0; i < nzones; i++)
252 VIR_FREE(zones[i]);
253 VIR_FREE(zones);
254 return result;
255 }
256
257
258 /**
259 * virFirewallDApplyRule:
260 * @layer: which layer to apply the rule to
261 * @args: list of args to send to this layer's passthrough command.
262 * @argsLen: number of items in @args
263 * @ignoreErrors: true to suppress logging of errors and return success
264 * false to log errors and return actual status
265 * @output: output of the direct passthrough command, if it was successful
266 */
267 int
virFirewallDApplyRule(virFirewallLayer layer,char ** args,size_t argsLen,bool ignoreErrors,char ** output)268 virFirewallDApplyRule(virFirewallLayer layer,
269 char **args, size_t argsLen,
270 bool ignoreErrors,
271 char **output)
272 {
273 const char *ipv = virFirewallLayerFirewallDTypeToString(layer);
274 GDBusConnection *sysbus = virGDBusGetSystemBus();
275 g_autoptr(GVariant) message = NULL;
276 g_autoptr(GVariant) reply = NULL;
277 g_autoptr(virError) error = NULL;
278
279 if (!sysbus)
280 return -1;
281
282 memset(&error, 0, sizeof(error));
283
284 if (!ipv) {
285 virReportError(VIR_ERR_INTERNAL_ERROR,
286 _("Unknown firewall layer %d"),
287 layer);
288 return -1;
289 }
290
291 error = g_new0(virError, 1);
292
293 message = g_variant_new("(s@as)",
294 ipv,
295 g_variant_new_strv((const char * const*)args, argsLen));
296
297 if (virGDBusCallMethod(sysbus,
298 &reply,
299 G_VARIANT_TYPE("(s)"),
300 error,
301 VIR_FIREWALL_FIREWALLD_SERVICE,
302 "/org/fedoraproject/FirewallD1",
303 "org.fedoraproject.FirewallD1.direct",
304 "passthrough",
305 message) < 0)
306 return -1;
307
308 if (error->level == VIR_ERR_ERROR) {
309 /*
310 * As of firewalld-0.3.9.3-1.fc20.noarch the name and
311 * message fields in the error look like
312 *
313 * name="org.freedesktop.DBus.Python.dbus.exceptions.DBusException"
314 * message="COMMAND_FAILED: '/sbin/iptables --table filter --delete
315 * INPUT --in-interface virbr0 --protocol udp --destination-port 53
316 * --jump ACCEPT' failed: iptables: Bad rule (does a matching rule
317 * exist in that chain?)."
318 *
319 * We'd like to only ignore DBus errors precisely related to the failure
320 * of iptables/ebtables commands. A well designed DBus interface would
321 * return specific named exceptions not the top level generic python dbus
322 * exception name. With this current scheme our only option is todo a
323 * sub-string match for 'COMMAND_FAILED' on the message. eg like
324 *
325 * if (ignoreErrors &&
326 * STREQ(error.name,
327 * "org.freedesktop.DBus.Python.dbus.exceptions.DBusException") &&
328 * STRPREFIX(error.message, "COMMAND_FAILED"))
329 * ...
330 *
331 * But this risks our error detecting code being broken if firewalld changes
332 * ever alter the message string, so we're avoiding doing that.
333 */
334 if (ignoreErrors) {
335 VIR_DEBUG("Ignoring error '%s': '%s'",
336 error->str1, error->message);
337 } else {
338 virReportErrorObject(error);
339 return -1;
340 }
341 } else {
342 g_variant_get(reply, "(s)", output);
343 }
344
345 return 0;
346 }
347
348
349 int
virFirewallDInterfaceSetZone(const char * iface,const char * zone)350 virFirewallDInterfaceSetZone(const char *iface,
351 const char *zone)
352 {
353 GDBusConnection *sysbus = virGDBusGetSystemBus();
354 g_autoptr(GVariant) message = NULL;
355
356 if (!sysbus)
357 return -1;
358
359 message = g_variant_new("(ss)", zone, iface);
360
361 return virGDBusCallMethod(sysbus,
362 NULL,
363 NULL,
364 NULL,
365 VIR_FIREWALL_FIREWALLD_SERVICE,
366 "/org/fedoraproject/FirewallD1",
367 "org.fedoraproject.FirewallD1.zone",
368 "changeZoneOfInterface",
369 message);
370 }
371