ifcfg-rh: allow handling complex routing rules via dispatcher (rh #1160013)
[NetworkManager.git] / src / settings / plugins / ifcfg-rh / utils.c
1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 /* NetworkManager system settings service
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  *
18  * (C) Copyright 2008 - 2012 Red Hat, Inc.
19  */
20
21 #include "config.h"
22
23 #include <glib.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include "nm-core-internal.h"
28 #include "nm-utils-internal.h"
29 #include "NetworkManagerUtils.h"
30
31 #include "utils.h"
32 #include "shvar.h"
33
34 /*
35  * utils_single_quote_string
36  *
37  * Put string inside single quotes and remove CR, LF characters. If single quote
38  * is present, escape it with a backslash and prepend the whole string with $
39  * in order to have $'string'. That allows us to use single quote inside
40  * single quotes without breaking bash syntax. (man bash, section QUOTING).
41  *
42  * Caller is responsible for freeing the returned string.
43  */
44 char *
45 utils_single_quote_string (const char *str)
46 {
47         static const char *drop_chars = "\r\n"; /* drop CR and LF */
48         static const char escape_char = '\\'; /* escape char is backslash */
49         static const char quote_char = '\'';  /* quote char is single quote */
50         size_t i, slen, j = 0;
51         size_t drop = 0, extra = 0;
52         char *new_str;
53
54         slen = strlen (str);
55         for (i = 0; i < slen; i++) {
56                 if (str[i] == quote_char)
57                         extra++;
58                 if (strchr (drop_chars, str[i]))
59                         drop++;
60         }
61         new_str = g_malloc0 (slen + extra - drop + 4); /* 4 is for $''\0*/
62
63         if (extra > 0)
64                 new_str[j++] = '$';
65         new_str[j++] = quote_char;
66         for (i = 0; i < slen; i++) {
67                 if (strchr (drop_chars, str[i]))
68                         continue;
69                 if (str[i] == quote_char)
70                         new_str[j++] = escape_char;
71                 new_str[j++] = str[i];
72         }
73         new_str[j] = quote_char;
74
75         return new_str;
76 }
77
78 /*
79  * utils_single_unquote_string
80  *
81  * Remove string from single (or double) quotes, and remove escaping of '.
82  * Also remove first $ if the string is in the form of $'string'.
83  *
84  * Caller is responsible for freeing the returned string.
85  */
86 char *
87 utils_single_unquote_string (const char *str)
88 {
89         static const char escape_char = '\\'; /* escape char is backslash */
90         static const char q_char = '\''; /* quote char is single quote */
91         static const char dq_char = '"'; /* double quote char */
92         size_t i, slen, j = 0, quote = 0, dollar = 0;
93         char *new_str;
94
95         slen = strlen (str);
96         new_str = g_malloc0 (slen + 1);
97
98         if (   (slen >= 2 && (str[0] == dq_char || str[0] == q_char) && str[0] == str[slen-1])
99             || (slen >= 3 && str[0] == '$' && str[1] == q_char && str[1] == str[slen-1])) {
100                 quote = 1;
101                 if (str[0] == '$') dollar = 1;
102         }
103
104         i = quote + dollar;
105         while (i < slen - quote) {
106                 if (str[i] == escape_char && str[i+1] == q_char && i+1 < slen-quote)
107                         i++;
108                 new_str[j++] = str[i++];
109         }
110         new_str[j] = '\0';
111
112         return new_str;
113 }
114
115 /*
116  * Check ';[a-fA-F0-9]{8}' file suffix used for temporary files by rpm when
117  * installing packages.
118  *
119  * Implementation taken from upstart.
120  */
121 static gboolean
122 check_rpm_temp_suffix (const char *path)
123 {
124         const char *ptr;
125
126         g_return_val_if_fail (path != NULL, FALSE);
127
128         /* Matches *;[a-fA-F0-9]{8}; used by rpm */
129         ptr = strrchr (path, ';');
130         if (ptr && (strspn (ptr + 1, "abcdefABCDEF0123456789") == 8)
131             && (! ptr[9]))
132                 return TRUE;
133         return FALSE;
134 }
135
136 static gboolean
137 check_suffix (const char *base, const char *tag)
138 {
139         int len, tag_len;
140
141         g_return_val_if_fail (base != NULL, TRUE);
142         g_return_val_if_fail (tag != NULL, TRUE);
143
144         len = strlen (base);
145         tag_len = strlen (tag);
146         if ((len > tag_len) && !strcasecmp (base + len - tag_len, tag))
147                 return TRUE;
148         return FALSE;
149 }
150
151 gboolean
152 utils_should_ignore_file (const char *filename, gboolean only_ifcfg)
153 {
154         char *base;
155         gboolean ignore = TRUE;
156         gboolean is_ifcfg = FALSE;
157         gboolean is_other = FALSE;
158
159         g_return_val_if_fail (filename != NULL, TRUE);
160
161         base = g_path_get_basename (filename);
162         g_return_val_if_fail (base != NULL, TRUE);
163
164         /* Only handle ifcfg, keys, and routes files */
165         if (!strncmp (base, IFCFG_TAG, strlen (IFCFG_TAG)))
166                 is_ifcfg = TRUE;
167
168         if (only_ifcfg == FALSE) {
169                 if (   !strncmp (base, KEYS_TAG, strlen (KEYS_TAG))
170                     || !strncmp (base, ROUTE_TAG, strlen (ROUTE_TAG))
171                     || !strncmp (base, ROUTE6_TAG, strlen (ROUTE6_TAG)))
172                                 is_other = TRUE;
173         }
174
175         /* But not those that have certain suffixes */
176         if (   (is_ifcfg || is_other)
177             && !check_suffix (base, BAK_TAG)
178             && !check_suffix (base, TILDE_TAG)
179             && !check_suffix (base, ORIG_TAG)
180             && !check_suffix (base, REJ_TAG)
181             && !check_suffix (base, RPMNEW_TAG)
182             && !check_suffix (base, AUGNEW_TAG)
183             && !check_suffix (base, AUGTMP_TAG)
184             && !check_rpm_temp_suffix (base))
185                 ignore = FALSE;
186
187         g_free (base);
188         return ignore;
189 }
190
191 char *
192 utils_cert_path (const char *parent, const char *suffix)
193 {
194         const char *name;
195         char *dir, *path;
196
197         g_return_val_if_fail (parent != NULL, NULL);
198         g_return_val_if_fail (suffix != NULL, NULL);
199
200         name = utils_get_ifcfg_name (parent, FALSE);
201         dir = g_path_get_dirname (parent);
202         path = g_strdup_printf ("%s/%s-%s", dir, name, suffix);
203         g_free (dir);
204         return path;
205 }
206
207 const char *
208 utils_get_ifcfg_name (const char *file, gboolean only_ifcfg)
209 {
210         const char *name;
211
212         g_return_val_if_fail (file != NULL, NULL);
213
214         name = strrchr (file, '/');
215         if (!name)
216                 name = file;
217         else
218                 name++;
219         if (!*name)
220                 return NULL;
221
222 #define MATCH_TAG_AND_RETURN(name, TAG) \
223         G_STMT_START { \
224                 if (strncmp (name, TAG, STRLEN (TAG)) == 0) { \
225                         name += STRLEN (TAG); \
226                         if (name[0] == '\0') \
227                                 return NULL; \
228                         else \
229                                 return name; \
230                 } \
231         } G_STMT_END
232
233         MATCH_TAG_AND_RETURN (name, IFCFG_TAG);
234         if (!only_ifcfg) {
235                 MATCH_TAG_AND_RETURN (name, KEYS_TAG);
236                 MATCH_TAG_AND_RETURN (name, ROUTE_TAG);
237                 MATCH_TAG_AND_RETURN (name, ROUTE6_TAG);
238         }
239
240         return NULL;
241 }
242
243 /* Used to get any ifcfg/extra file path from any other ifcfg/extra path
244  * in the form <tag><name>.
245  */
246 static char *
247 utils_get_extra_path (const char *parent, const char *tag)
248 {
249         char *item_path = NULL, *dirname;
250         const char *name;
251
252         g_return_val_if_fail (parent != NULL, NULL);
253         g_return_val_if_fail (tag != NULL, NULL);
254
255         dirname = g_path_get_dirname (parent);
256         if (!dirname)
257                 return NULL;
258
259         name = utils_get_ifcfg_name (parent, FALSE);
260         if (name) {
261                 if (!strcmp (dirname, "."))
262                         item_path = g_strdup_printf ("%s%s", tag, name);
263                 else
264                         item_path = g_strdup_printf ("%s/%s%s", dirname, tag, name);
265         }
266         g_free (dirname);
267
268         return item_path;
269 }
270
271 char *
272 utils_get_ifcfg_path (const char *parent)
273 {
274         return utils_get_extra_path (parent, IFCFG_TAG);
275 }
276
277 char *
278 utils_get_keys_path (const char *parent)
279 {
280         return utils_get_extra_path (parent, KEYS_TAG);
281 }
282
283 char *
284 utils_get_route_path (const char *parent)
285 {
286         return utils_get_extra_path (parent, ROUTE_TAG);
287 }
288
289 char *
290 utils_get_route6_path (const char *parent)
291 {
292         return utils_get_extra_path (parent, ROUTE6_TAG);
293 }
294
295 shvarFile *
296 utils_get_extra_ifcfg (const char *parent, const char *tag, gboolean should_create)
297 {
298         shvarFile *ifcfg = NULL;
299         char *path;
300
301         path = utils_get_extra_path (parent, tag);
302         if (!path)
303                 return NULL;
304
305         if (should_create && !g_file_test (path, G_FILE_TEST_EXISTS))
306                 ifcfg = svCreateFile (path);
307
308         if (!ifcfg)
309                 ifcfg = svOpenFile (path, NULL);
310
311         g_free (path);
312         return ifcfg;
313 }
314
315 shvarFile *
316 utils_get_keys_ifcfg (const char *parent, gboolean should_create)
317 {
318         return utils_get_extra_ifcfg (parent, KEYS_TAG, should_create);
319 }
320
321 shvarFile *
322 utils_get_route_ifcfg (const char *parent, gboolean should_create)
323 {
324         return utils_get_extra_ifcfg (parent, ROUTE_TAG, should_create);
325 }
326
327 shvarFile *
328 utils_get_route6_ifcfg (const char *parent, gboolean should_create)
329 {
330         return utils_get_extra_ifcfg (parent, ROUTE6_TAG, should_create);
331 }
332
333 /* Finds out if route file has new or older format
334  * Returns TRUE  - new syntax (ADDRESS<n>=a.b.c.d ...), error opening file or empty
335  *         FALSE - older syntax, i.e. argument to 'ip route add' (1.2.3.0/24 via 11.22.33.44)
336  */
337 gboolean
338 utils_has_route_file_new_syntax (const char *filename)
339 {
340         char *contents = NULL;
341         gsize len = 0;
342         gboolean ret = FALSE;
343         const char *pattern = "^[[:space:]]*ADDRESS[0-9]+=";
344
345         g_return_val_if_fail (filename != NULL, TRUE);
346
347         if (!g_file_get_contents (filename, &contents, &len, NULL))
348                 return TRUE;
349
350         if (len <= 0) {
351                 ret = TRUE;
352                 goto gone;
353         }
354
355         if (g_regex_match_simple (pattern, contents, G_REGEX_MULTILINE, 0))
356                 ret = TRUE;
357
358 gone:
359         g_free (contents);
360         return ret;
361 }
362
363 gboolean
364 utils_has_complex_routes (const char *filename)
365 {
366         char *rules;
367
368         g_return_val_if_fail (filename != NULL, TRUE);
369
370         rules = utils_get_extra_path (filename, RULE_TAG);
371         if (g_file_test (rules, G_FILE_TEST_EXISTS)) {
372                 g_free (rules);
373                 return TRUE;
374         }
375         g_free (rules);
376
377         rules = utils_get_extra_path (filename, RULE6_TAG);
378         if (g_file_test (rules, G_FILE_TEST_EXISTS)) {
379                 g_free (rules);
380                 return TRUE;
381         }
382         g_free (rules);
383
384         return FALSE;
385 }
386
387 gboolean
388 utils_ignore_ip_config (NMConnection *connection)
389 {
390         NMSettingConnection *s_con;
391
392         s_con = nm_connection_get_setting_connection (connection);
393         g_assert (s_con);
394
395         /* bonding slaves have no IP configuration, and the system
396          * scripts just ignore it if it's there.
397          */
398         if (   nm_setting_connection_is_slave_type (s_con, NM_SETTING_BOND_SETTING_NAME)
399             || nm_setting_connection_is_slave_type (s_con, NM_SETTING_BRIDGE_SETTING_NAME)
400             || nm_setting_connection_is_slave_type (s_con, NM_SETTING_TEAM_SETTING_NAME))
401                 return TRUE;
402
403         return FALSE;
404 }
405
406 /* Find out if the 'alias' file name might be an alias file for 'ifcfg' file name,
407  * or any alias when 'ifcfg' is NULL. Does not check that it's actually a valid
408  * alias name; that happens in reader.c
409  */
410 gboolean
411 utils_is_ifcfg_alias_file (const char *alias, const char *ifcfg)
412 {
413         g_return_val_if_fail (alias != NULL, FALSE);
414
415         if (strncmp (alias, IFCFG_TAG, strlen (IFCFG_TAG)))
416                 return FALSE;
417
418         if (ifcfg) {
419                 size_t len = strlen (ifcfg);
420
421                 return (strncmp (alias, ifcfg, len) == 0 && alias[len] == ':');
422         } else {
423                 return (strchr (alias, ':') != NULL);
424         }
425 }
426
427 char *
428 utils_get_ifcfg_from_alias (const char *alias)
429 {
430         char *base, *ptr, *ifcfg = NULL;
431
432         g_return_val_if_fail (alias != NULL, NULL);
433
434         base = g_path_get_basename (alias);
435         g_return_val_if_fail (base != NULL, NULL);
436
437         if (utils_is_ifcfg_alias_file (base, NULL)) {
438                 ifcfg = g_strdup (alias);
439                 ptr = strrchr (ifcfg, ':');
440                 if (ptr)
441                         *ptr = '\0';
442                 else {
443                         g_free (ifcfg);
444                         ifcfg = NULL;
445                 }
446         }
447
448         g_free (base);
449         return ifcfg;
450 }