1 /* Copyright (C) 2014-2020 Greenbone Networks GmbH
2 *
3 * SPDX-License-Identifier: AGPL-3.0-or-later
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /**
20 * @file manage_utils.c
21 * @brief Module for Greenbone Vulnerability Manager: Manage library utilities.
22 */
23
24 #include "manage_utils.h"
25
26 #include <assert.h> /* for assert */
27 #include <ctype.h>
28 #include <stdlib.h> /* for getenv */
29 #include <stdio.h> /* for sscanf */
30 #include <string.h> /* for strcmp */
31
32 #include <gvm/base/hosts.h>
33 #include <gvm/util/uuidutils.h>
34
35 #undef G_LOG_DOMAIN
36 /**
37 * @brief GLib log domain.
38 */
39 #define G_LOG_DOMAIN "md utils"
40
41 /**
42 * @brief Number of seconds in a day.
43 */
44 #define SECS_PER_DAY 86400
45
46 /**
47 * @file manage_utils.c
48 * @brief The Greenbone Vulnerability Manager management library.
49 *
50 * Utilities used by the manage library that do not depend on anything.
51 */
52
53 /**
54 * @brief Get the current offset from UTC of a timezone.
55 *
56 * @param[in] zone Timezone, or NULL for UTC.
57 *
58 * @return Seconds east of UTC.
59 */
60 long
current_offset(const char * zone)61 current_offset (const char *zone)
62 {
63 gchar *tz;
64 long offset;
65 time_t now;
66 struct tm now_broken;
67
68 if (zone == NULL)
69 return 0;
70
71 /* Store current TZ. */
72 tz = getenv ("TZ") ? g_strdup (getenv ("TZ")) : NULL;
73
74 if (setenv ("TZ", zone, 1) == -1)
75 {
76 g_warning ("%s: Failed to switch to timezone", __func__);
77 if (tz != NULL)
78 setenv ("TZ", tz, 1);
79 g_free (tz);
80 return 0;
81 }
82
83 tzset ();
84
85 time (&now);
86 if (localtime_r (&now, &now_broken) == NULL)
87 {
88 g_warning ("%s: localtime failed", __func__);
89 if (tz != NULL)
90 setenv ("TZ", tz, 1);
91 g_free (tz);
92 return 0;
93 }
94 if (setenv ("TZ", "UTC", 1) == -1)
95 {
96 g_warning ("%s: Failed to switch to UTC", __func__);
97 if (tz != NULL)
98 setenv ("TZ", tz, 1);
99 g_free (tz);
100 return 0;
101 }
102 tzset ();
103 offset = - (now - mktime (&now_broken));
104
105 /* Revert to stored TZ. */
106 if (tz)
107 {
108 if (setenv ("TZ", tz, 1) == -1)
109 {
110 g_warning ("%s: Failed to switch to original TZ", __func__);
111 g_free (tz);
112 return 0;
113 }
114 }
115 else
116 unsetenv ("TZ");
117
118 g_free (tz);
119 return offset;
120 }
121
122 /**
123 * @brief Add months to a time.
124 *
125 * @param[in] time Time.
126 * @param[in] months Months.
127 *
128 * @return Time plus given number of months.
129 */
130 time_t
add_months(time_t time,int months)131 add_months (time_t time, int months)
132 {
133 struct tm broken;
134
135 if (localtime_r (&time, &broken) == NULL)
136 {
137 g_warning ("%s: localtime failed", __func__);
138 return 0;
139 }
140 broken.tm_mon += months;
141 return mktime (&broken);
142 }
143
144 /**
145 * @brief Return number of hosts described by a hosts string.
146 *
147 * @param[in] given_hosts String describing hosts.
148 * @param[in] exclude_hosts String describing hosts excluded from given set.
149 * @param[in] max_hosts Max hosts.
150 *
151 * @return Number of hosts, or -1 on error.
152 */
153 int
manage_count_hosts_max(const char * given_hosts,const char * exclude_hosts,int max_hosts)154 manage_count_hosts_max (const char *given_hosts, const char *exclude_hosts,
155 int max_hosts)
156 {
157 int count;
158 gvm_hosts_t *hosts;
159 gchar *clean_hosts;
160
161 clean_hosts = clean_hosts_string (given_hosts);
162
163 hosts = gvm_hosts_new_with_max (clean_hosts, max_hosts);
164 if (hosts == NULL)
165 {
166 g_free (clean_hosts);
167 return -1;
168 }
169
170 if (exclude_hosts)
171 {
172 gchar *clean_exclude_hosts;
173
174 clean_exclude_hosts = clean_hosts_string (exclude_hosts);
175 if (gvm_hosts_exclude_with_max (hosts,
176 clean_exclude_hosts,
177 max_hosts)
178 < 0)
179 {
180 g_free (clean_hosts);
181 g_free (clean_exclude_hosts);
182 return -1;
183 }
184 g_free (clean_exclude_hosts);
185 }
186
187 count = gvm_hosts_count (hosts);
188 gvm_hosts_free (hosts);
189 g_free (clean_hosts);
190
191 return count;
192 }
193
194 /**
195 * @brief Get the minimum severity for a severity level.
196 *
197 * This function has a database equivalent in manage_pg_server.c.
198 * These two functions must stay in sync.
199 *
200 * @param[in] level The name of the severity level.
201 *
202 * @return The minimum severity.
203 */
204 double
level_min_severity(const char * level)205 level_min_severity (const char *level)
206 {
207 if (strcasecmp (level, "Log") == 0)
208 return SEVERITY_LOG;
209 else if (strcasecmp (level, "False Positive") == 0)
210 return SEVERITY_FP;
211 else if (strcasecmp (level, "Error") == 0)
212 return SEVERITY_ERROR;
213
214 if (strcasecmp (level, "high") == 0)
215 return 7.0;
216 else if (strcasecmp (level, "medium") == 0)
217 return 4.0;
218 else if (strcasecmp (level, "low") == 0)
219 return 0.1;
220 else
221 return SEVERITY_UNDEFINED;
222 }
223
224 /**
225 * @brief Get the maximum severity for a severity level.
226 *
227 * This function has a database equivalent in manage_pg_server.c.
228 * These two functions must stay in sync.
229 *
230 * @param[in] level The name of the severity level.
231 *
232 * @return The maximunm severity.
233 */
234 double
level_max_severity(const char * level)235 level_max_severity (const char *level)
236 {
237 if (strcasecmp (level, "Log") == 0)
238 return SEVERITY_LOG;
239 else if (strcasecmp (level, "False Positive") == 0)
240 return SEVERITY_FP;
241 else if (strcasecmp (level, "Error") == 0)
242 return SEVERITY_ERROR;
243
244 if (strcasecmp (level, "high") == 0)
245 return 10.0;
246 else if (strcasecmp (level, "medium") == 0)
247 return 6.9;
248 else if (strcasecmp (level, "low") == 0)
249 return 3.9;
250 else
251 return SEVERITY_UNDEFINED;
252 }
253
254 /**
255 * @brief Returns whether a host has an equal host in a hosts string.
256 *
257 * For example, 192.168.10.1 has an equal in a hosts string
258 * "192.168.10.1-5, 192.168.10.10-20" string while 192.168.10.7 doesn't.
259 *
260 * @param[in] hosts_str Hosts string to check.
261 * @param[in] find_host_str The host to find.
262 * @param[in] max_hosts Maximum number of hosts allowed in hosts_str.
263 *
264 * @return 1 if host has equal in hosts_str, 0 otherwise.
265 */
266 int
hosts_str_contains(const char * hosts_str,const char * find_host_str,int max_hosts)267 hosts_str_contains (const char* hosts_str, const char* find_host_str,
268 int max_hosts)
269 {
270 gvm_hosts_t *hosts, *find_hosts;
271
272 hosts = gvm_hosts_new_with_max (hosts_str, max_hosts);
273 find_hosts = gvm_hosts_new_with_max (find_host_str, 1);
274
275 if (hosts == NULL || find_hosts == NULL || find_hosts->count != 1)
276 {
277 gvm_hosts_free (hosts);
278 gvm_hosts_free (find_hosts);
279 return 0;
280 }
281
282 int ret = gvm_host_in_hosts (find_hosts->hosts[0], NULL, hosts);
283 gvm_hosts_free (hosts);
284 gvm_hosts_free (find_hosts);
285 return ret;
286 }
287
288 /**
289 * @brief Check whether a resource type table name is valid.
290 *
291 * @param[in] type Type of resource.
292 *
293 * @return 1 yes, 0 no.
294 */
295 int
valid_db_resource_type(const char * type)296 valid_db_resource_type (const char* type)
297 {
298 if (type == NULL)
299 return 0;
300
301 return (strcasecmp (type, "alert") == 0)
302 || (strcasecmp (type, "config") == 0)
303 || (strcasecmp (type, "cpe") == 0)
304 || (strcasecmp (type, "credential") == 0)
305 || (strcasecmp (type, "cve") == 0)
306 || (strcasecmp (type, "cert_bund_adv") == 0)
307 || (strcasecmp (type, "dfn_cert_adv") == 0)
308 || (strcasecmp (type, "filter") == 0)
309 || (strcasecmp (type, "group") == 0)
310 || (strcasecmp (type, "host") == 0)
311 || (strcasecmp (type, "os") == 0)
312 || (strcasecmp (type, "note") == 0)
313 || (strcasecmp (type, "nvt") == 0)
314 || (strcasecmp (type, "ovaldef") == 0)
315 || (strcasecmp (type, "override") == 0)
316 || (strcasecmp (type, "port_list") == 0)
317 || (strcasecmp (type, "permission") == 0)
318 || (strcasecmp (type, "report") == 0)
319 || (strcasecmp (type, "report_format") == 0)
320 || (strcasecmp (type, "result") == 0)
321 || (strcasecmp (type, "role") == 0)
322 || (strcasecmp (type, "scanner") == 0)
323 || (strcasecmp (type, "schedule") == 0)
324 || (strcasecmp (type, "tag") == 0)
325 || (strcasecmp (type, "target") == 0)
326 || (strcasecmp (type, "task") == 0)
327 || (strcasecmp (type, "ticket") == 0)
328 || (strcasecmp (type, "tls_certificate") == 0)
329 || (strcasecmp (type, "user") == 0);
330 }
331
332 /** @brief Replace any control characters in string with spaces.
333 *
334 * @param[in,out] string String to replace in.
335 */
336 void
blank_control_chars(char * string)337 blank_control_chars (char *string)
338 {
339 for (; *string; string++)
340 if (iscntrl (*string) && *string != '\n') *string = ' ';
341 }
342
343 /**
344 * @brief GVM product ID.
345 */
346 #define GVM_PRODID "-//Greenbone.net//NONSGML Greenbone Security Manager " \
347 GVMD_VERSION "//EN"
348
349 /**
350 * @brief Try to get a built-in libical timezone from a tzid or city name.
351 *
352 * @param[in] tzid The tzid or Olson city name.
353 *
354 * @return The built-in timezone if found, else NULL.
355 */
356 icaltimezone*
icalendar_timezone_from_string(const char * tzid)357 icalendar_timezone_from_string (const char *tzid)
358 {
359 if (tzid)
360 {
361 icaltimezone *tz;
362
363 tz = icaltimezone_get_builtin_timezone_from_tzid (tzid);
364 if (tz == NULL)
365 tz = icaltimezone_get_builtin_timezone (tzid);
366 return tz;
367 }
368
369 return NULL;
370 }
371
372 /**
373 * @brief Create an iCalendar component from old schedule data.
374 *
375 * @param[in] first_time The first run time.
376 * @param[in] period The period in seconds.
377 * @param[in] period_months The period in months.
378 * @param[in] duration The duration in seconds.
379 * @param[in] byday_mask The byday mask.
380 *
381 * @return The generated iCalendar component.
382 */
383 icalcomponent *
icalendar_from_old_schedule_data(time_t first_time,time_t period,time_t period_months,time_t duration,int byday_mask)384 icalendar_from_old_schedule_data (time_t first_time,
385 time_t period, time_t period_months,
386 time_t duration,
387 int byday_mask)
388 {
389 gchar *uid;
390 icalcomponent *ical_new, *vevent;
391 icaltimetype dtstart, dtstamp;
392 int has_recurrence;
393 struct icalrecurrencetype recurrence;
394 struct icaldurationtype ical_duration;
395
396 // Setup base calendar component
397 ical_new = icalcomponent_new_vcalendar ();
398 icalcomponent_add_property (ical_new, icalproperty_new_version ("2.0"));
399 icalcomponent_add_property (ical_new,
400 icalproperty_new_prodid (GVM_PRODID));
401
402 // Create event component
403 vevent = icalcomponent_new_vevent ();
404 icalcomponent_add_component (ical_new, vevent);
405
406 // Generate UID for event
407 uid = gvm_uuid_make ();
408 icalcomponent_set_uid (vevent, uid);
409 g_free (uid);
410 uid = NULL;
411
412 // Set timestamp
413 dtstamp = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
414 icalcomponent_set_dtstamp (vevent, dtstamp);
415
416 // Get timezone and set first start time
417 dtstart = icaltime_from_timet_with_zone (first_time, 0,
418 icaltimezone_get_utc_timezone ());
419
420 icalcomponent_set_dtstart (vevent, dtstart);
421
422 // Get recurrence rule if applicable
423 icalrecurrencetype_clear (&recurrence);
424 if (period_months)
425 {
426 if (period_months % 12 == 0)
427 {
428 recurrence.freq = ICAL_YEARLY_RECURRENCE;
429 recurrence.interval = period_months / 12;
430 }
431 else
432 {
433 recurrence.freq = ICAL_MONTHLY_RECURRENCE;
434 recurrence.interval = period_months;
435 }
436 has_recurrence = 1;
437 }
438 else if (period)
439 {
440 if (period % 604800 == 0)
441 {
442 recurrence.freq = ICAL_WEEKLY_RECURRENCE;
443 recurrence.interval = period / 604800;
444 }
445 else if (period % 86400 == 0)
446 {
447 recurrence.freq = ICAL_DAILY_RECURRENCE;
448 recurrence.interval = period / 86400;
449 }
450 else if (period % 3600 == 0)
451 {
452 recurrence.freq = ICAL_HOURLY_RECURRENCE;
453 recurrence.interval = period / 3600;
454 }
455 else if (period % 60 == 0)
456 {
457 recurrence.freq = ICAL_MINUTELY_RECURRENCE;
458 recurrence.interval = period / 60;
459 }
460 else
461 {
462 recurrence.freq = ICAL_SECONDLY_RECURRENCE;
463 recurrence.interval = period;
464 }
465
466 has_recurrence = 1;
467 }
468 else
469 has_recurrence = 0;
470
471 // Add set by_day and add the RRULE if applicable
472 if (has_recurrence)
473 {
474 if (byday_mask)
475 {
476 int ical_day, array_pos;
477
478 // iterate over libical days starting at 1 for Sunday.
479 array_pos = 0;
480 for (ical_day = 1; ical_day <= 7; ical_day++)
481 {
482 int mask_bit;
483 // Convert to GVM byday mask bit index starting at 0 for Monday.
484 mask_bit = (ical_day == 1) ? 1 : (ical_day - 2);
485 if (byday_mask & (1 << mask_bit))
486 {
487 recurrence.by_day[array_pos] = ical_day;
488 array_pos ++;
489 }
490 }
491 }
492
493 icalcomponent_add_property (vevent,
494 icalproperty_new_rrule (recurrence));
495 }
496
497 // Add duration
498 if (duration)
499 {
500 ical_duration = icaldurationtype_from_int (duration);
501 icalcomponent_set_duration (vevent, ical_duration);
502 }
503
504 return ical_new;
505 }
506
507 /**
508 * @brief Simplify an VEVENT iCal component.
509 *
510 * @param[in] vevent The VEVENT component to simplify.
511 * @param[in] zone Timezone.
512 * @param[out] error Output of iCal errors or warnings.
513 * @param[out] warnings_buffer GString buffer to write warnings to.
514 *
515 * @return A newly allocated, simplified VEVENT component.
516 */
517 static icalcomponent *
icalendar_simplify_vevent(icalcomponent * vevent,icaltimezone * zone,gchar ** error,GString * warnings_buffer)518 icalendar_simplify_vevent (icalcomponent *vevent, icaltimezone *zone,
519 gchar **error, GString *warnings_buffer)
520 {
521 icalproperty *error_prop;
522 gchar *uid;
523 icalcomponent *vevent_simplified;
524 icaltimetype original_dtstart, dtstart, dtstamp;
525 struct icaldurationtype duration;
526 icalproperty *rrule_prop, *rdate_prop, *exdate_prop, *exrule_prop;
527
528 // Only handle VEVENT components
529 assert (icalcomponent_isa (vevent) == ICAL_VEVENT_COMPONENT);
530
531 // Check for errors
532 icalrestriction_check (vevent);
533 error_prop = icalcomponent_get_first_property (vevent,
534 ICAL_XLICERROR_PROPERTY);
535 if (error_prop)
536 {
537 if (error)
538 *error = g_strdup_printf ("Error in VEVENT: %s",
539 icalproperty_get_xlicerror (error_prop));
540 return NULL;
541 }
542
543 // Get mandatory first start time
544 original_dtstart = icalcomponent_get_dtstart (vevent);
545 if (icaltime_is_null_time (original_dtstart))
546 {
547 if (error)
548 *error = g_strdup_printf ("VEVENT must have a dtstart property");
549 return NULL;
550 }
551
552 dtstart = icaltime_convert_to_zone (original_dtstart, zone);
553
554 // Get duration or try to calculate it from end time
555 duration = icalcomponent_get_duration (vevent);
556 if (icaldurationtype_is_null_duration (duration))
557 {
558 icaltimetype original_dtend;
559 original_dtend = icalcomponent_get_dtend (vevent);
560
561 if (icaltime_is_null_time (original_dtend))
562 {
563 duration = icaldurationtype_null_duration ();
564 }
565 else
566 {
567 icaltimetype dtend_zone;
568
569 dtend_zone = icaltime_convert_to_zone (original_dtend, zone);
570
571 duration = icaltime_subtract (dtend_zone, dtstart);
572 }
573 }
574
575 /*
576 * Try to get only the first recurrence rule and ignore any others.
577 * Technically there can be multiple ones but behavior is undefined in
578 * the iCalendar specification.
579 */
580 rrule_prop = icalcomponent_get_first_property (vevent,
581 ICAL_RRULE_PROPERTY);
582
583 // Warn about EXRULE being deprecated
584 exrule_prop = icalcomponent_get_first_property (vevent,
585 ICAL_EXRULE_PROPERTY);
586 if (exrule_prop)
587 {
588 g_string_append_printf (warnings_buffer,
589 "<warning>"
590 "VEVENT contains the deprecated EXRULE property,"
591 " which will be ignored."
592 "</warning>");
593 }
594
595 // Create new, simplified VEVENT from collected data.
596 vevent_simplified = icalcomponent_new_vevent ();
597 icalcomponent_set_dtstart (vevent_simplified, dtstart);
598 icalcomponent_set_duration (vevent_simplified, duration);
599 if (rrule_prop)
600 {
601 icalproperty *prop_clone = icalproperty_new_clone (rrule_prop);
602 icalcomponent_add_property (vevent_simplified, prop_clone);
603 }
604
605 // Simplify and copy RDATE properties
606 rdate_prop = icalcomponent_get_first_property (vevent,
607 ICAL_RDATE_PROPERTY);
608 while (rdate_prop)
609 {
610 struct icaldatetimeperiodtype old_datetimeperiod, new_datetimeperiod;
611 icalproperty *new_rdate;
612
613 old_datetimeperiod = icalproperty_get_rdate (rdate_prop);
614
615 // Reduce period to a simple date or datetime.
616 new_datetimeperiod.period = icalperiodtype_null_period ();
617 if (icalperiodtype_is_null_period (old_datetimeperiod.period))
618 {
619 new_datetimeperiod.time
620 = icaltime_convert_to_zone (old_datetimeperiod.time, zone);
621 }
622 else
623 {
624 new_datetimeperiod.time
625 = icaltime_convert_to_zone (old_datetimeperiod.period.start, zone);
626 }
627 new_rdate = icalproperty_new_rdate (new_datetimeperiod);
628 icalcomponent_add_property (vevent_simplified, new_rdate);
629
630 rdate_prop
631 = icalcomponent_get_next_property (vevent, ICAL_RDATE_PROPERTY);
632 }
633
634 // Copy EXDATE properties
635 exdate_prop = icalcomponent_get_first_property (vevent,
636 ICAL_EXDATE_PROPERTY);
637 while (exdate_prop)
638 {
639 icaltimetype original_exdate_time, exdate_time;
640 icalproperty *prop_clone;
641
642 original_exdate_time = icalproperty_get_exdate (exdate_prop);
643 exdate_time
644 = icaltime_convert_to_zone (original_exdate_time, zone);
645
646 prop_clone = icalproperty_new_exdate (exdate_time);
647 icalcomponent_add_property (vevent_simplified, prop_clone);
648
649 exdate_prop
650 = icalcomponent_get_next_property (vevent, ICAL_EXDATE_PROPERTY);
651 }
652
653 // Generate UID for event
654 uid = gvm_uuid_make ();
655 icalcomponent_set_uid (vevent_simplified, uid);
656 g_free (uid);
657 uid = NULL;
658
659 // Set timestamp
660 dtstamp = icaltime_current_time_with_zone (zone);
661 icalcomponent_set_dtstamp (vevent_simplified, dtstamp);
662
663 return vevent_simplified;
664 }
665
666 /**
667 * @brief Error return for icalendar_from_string.
668 */
669 #define ICAL_RETURN_ERROR(message) \
670 do \
671 { \
672 if (error) \
673 *error = message; \
674 icalcomponent_free (ical_parsed); \
675 icalcomponent_free (ical_new); \
676 g_string_free (warnings_buffer, TRUE); \
677 return NULL; \
678 } \
679 while (0)
680
681 /**
682 * @brief Creates a new, simplified VCALENDAR component from a string.
683 *
684 * @param[in] ical_string The ical_string to create the component from.
685 * @param[in] zone Timezone.
686 * @param[out] error Output of iCal errors or warnings.
687 *
688 * @return A newly allocated, simplified VCALENDAR component.
689 */
690 icalcomponent *
icalendar_from_string(const char * ical_string,icaltimezone * zone,gchar ** error)691 icalendar_from_string (const char *ical_string, icaltimezone *zone,
692 gchar **error)
693 {
694 icalcomponent *ical_new, *ical_parsed, *timezone_component;
695 icalproperty *error_prop;
696 GString *warnings_buffer;
697 int vevent_count = 0;
698 int other_component_count = 0;
699 icalcompiter ical_iter;
700
701 // Parse the iCalendar string
702 ical_parsed = icalcomponent_new_from_string (ical_string);
703 if (ical_parsed == NULL)
704 {
705 if (error)
706 *error = g_strdup_printf ("Could not parse iCalendar string");
707 return NULL;
708 }
709
710 // Check for errors
711 icalrestriction_check (ical_parsed);
712 error_prop = icalcomponent_get_first_property (ical_parsed,
713 ICAL_XLICERROR_PROPERTY);
714 if (error_prop)
715 {
716 if (error)
717 *error = g_strdup_printf ("Error in root component: %s",
718 icalproperty_get_xlicerror (error_prop));
719 icalcomponent_free (ical_parsed);
720 return NULL;
721 }
722
723 // Create buffers and new VCALENDAR
724 warnings_buffer = g_string_new ("");
725
726 ical_new = icalcomponent_new_vcalendar ();
727 icalcomponent_add_property (ical_new, icalproperty_new_version ("2.0"));
728 icalcomponent_add_property (ical_new,
729 icalproperty_new_prodid (GVM_PRODID));
730
731 timezone_component
732 = icalcomponent_new_clone (icaltimezone_get_component (zone));
733 icalcomponent_add_component (ical_new, timezone_component);
734
735 switch (icalcomponent_isa (ical_parsed))
736 {
737 case ICAL_NO_COMPONENT:
738 // The text must contain valid iCalendar component
739 ICAL_RETURN_ERROR
740 (g_strdup_printf ("String contains no iCalendar component"));
741 break;
742 case ICAL_XROOT_COMPONENT:
743 case ICAL_VCALENDAR_COMPONENT:
744 // Check multiple components
745 ical_iter = icalcomponent_begin_component (ical_parsed,
746 ICAL_ANY_COMPONENT);
747 icalcomponent *subcomp;
748 while ((subcomp = icalcompiter_deref (&ical_iter)))
749 {
750 icalcomponent *new_vevent;
751 switch (icalcomponent_isa (subcomp))
752 {
753 case ICAL_VEVENT_COMPONENT:
754 // Copy and simplify only the first VEVENT, ignoring all
755 // following ones.
756 if (vevent_count == 0)
757 {
758 new_vevent = icalendar_simplify_vevent
759 (subcomp,
760 zone,
761 error,
762 warnings_buffer);
763 if (new_vevent == NULL)
764 ICAL_RETURN_ERROR (*error);
765 icalcomponent_add_component (ical_new, new_vevent);
766 }
767 vevent_count ++;
768 break;
769 case ICAL_VTIMEZONE_COMPONENT:
770 // Timezones are collected separately
771 break;
772 case ICAL_VJOURNAL_COMPONENT:
773 case ICAL_VTODO_COMPONENT:
774 // VJOURNAL and VTODO components are ignored
775 other_component_count ++;
776 break;
777 default:
778 // Unexpected components
779 ICAL_RETURN_ERROR
780 (g_strdup_printf ("Unexpected component type: %s",
781 icalcomponent_kind_to_string
782 (icalcomponent_isa (subcomp))));
783 }
784 icalcompiter_next (&ical_iter);
785 }
786
787 if (vevent_count == 0)
788 {
789 ICAL_RETURN_ERROR
790 (g_strdup_printf ("iCalendar string must contain a VEVENT"));
791 }
792 else if (vevent_count > 1)
793 {
794 g_string_append_printf (warnings_buffer,
795 "<warning>"
796 "iCalendar contains %d VEVENT components"
797 " but only the first one will be used"
798 "</warning>",
799 vevent_count);
800 }
801
802 if (other_component_count)
803 {
804 g_string_append_printf (warnings_buffer,
805 "<warning>"
806 "iCalendar contains %d VTODO and/or"
807 " VJOURNAL component(s) which will be"
808 " ignored"
809 "</warning>",
810 other_component_count);
811 }
812 break;
813 case ICAL_VEVENT_COMPONENT:
814 {
815 icalcomponent *new_vevent;
816
817 new_vevent = icalendar_simplify_vevent (ical_parsed,
818 zone,
819 error,
820 warnings_buffer);
821 if (new_vevent == NULL)
822 ICAL_RETURN_ERROR (*error);
823 icalcomponent_add_component (ical_new, new_vevent);
824 }
825 break;
826 default:
827 ICAL_RETURN_ERROR
828 (g_strdup_printf ("iCalendar string must be a VCALENDAR or VEVENT"
829 " component or consist of multiple elements."));
830 break;
831 }
832
833 icalcomponent_free (ical_parsed);
834
835 if (error)
836 *error = g_string_free (warnings_buffer, FALSE);
837 else
838 g_string_free (warnings_buffer, TRUE);
839
840 return ical_new;
841 }
842
843 /**
844 * @brief Approximate the recurrence of a VCALENDAR as classic schedule data.
845 * The VCALENDAR must have simplified with icalendar_from_string for this to
846 * work reliably.
847 *
848 * @param[in] vcalendar The VCALENDAR component to get the data from.
849 * @param[out] period Output of the period in seconds.
850 * @param[out] period_months Output of the period in months.
851 * @param[out] byday_mask Output of the GVM byday mask.
852 *
853 * @return 0 success, 1 invalid vcalendar.
854 */
855 int
icalendar_approximate_rrule_from_vcalendar(icalcomponent * vcalendar,time_t * period,time_t * period_months,int * byday_mask)856 icalendar_approximate_rrule_from_vcalendar (icalcomponent *vcalendar,
857 time_t *period,
858 time_t *period_months,
859 int *byday_mask)
860 {
861 icalcomponent *vevent;
862 icalproperty *rrule_prop;
863
864
865 assert (period);
866 assert (period_months);
867 assert (byday_mask);
868
869 *period = 0;
870 *period_months = 0;
871 *byday_mask = 0;
872
873 // Component must be a VCALENDAR
874 if (vcalendar == NULL
875 || icalcomponent_isa (vcalendar) != ICAL_VCALENDAR_COMPONENT)
876 return 1;
877
878 // Process only the first VEVENT
879 // Others should be removed by icalendar_from_string
880 vevent = icalcomponent_get_first_component (vcalendar,
881 ICAL_VEVENT_COMPONENT);
882 if (vevent == NULL)
883 return -1;
884
885 // Process only first RRULE.
886 rrule_prop = icalcomponent_get_first_property (vevent,
887 ICAL_RRULE_PROPERTY);
888 if (rrule_prop)
889 {
890 struct icalrecurrencetype recurrence;
891 recurrence = icalproperty_get_rrule (rrule_prop);
892 int array_pos;
893
894 // Get period or period_months
895 switch (recurrence.freq)
896 {
897 case ICAL_YEARLY_RECURRENCE:
898 *period_months = recurrence.interval * 12;
899 break;
900 case ICAL_MONTHLY_RECURRENCE:
901 *period_months = recurrence.interval;
902 break;
903 case ICAL_WEEKLY_RECURRENCE:
904 *period = recurrence.interval * 604800;
905 break;
906 case ICAL_DAILY_RECURRENCE:
907 *period = recurrence.interval * 86400;
908 break;
909 case ICAL_HOURLY_RECURRENCE:
910 *period = recurrence.interval * 3600;
911 break;
912 case ICAL_MINUTELY_RECURRENCE:
913 *period = recurrence.interval * 60;
914 break;
915 case ICAL_SECONDLY_RECURRENCE:
916 *period = recurrence.interval;
917 case ICAL_NO_RECURRENCE:
918 break;
919 default:
920 return -1;
921 }
922
923 /*
924 * Try to approximate byday mask
925 * - libical days start at 1 for Sunday.
926 * - GVM byday mask bit index starts at 0 for Monday -> Sunday = 6
927 */
928 array_pos = 0;
929 while (recurrence.by_day[array_pos] != ICAL_RECURRENCE_ARRAY_MAX)
930 {
931 int ical_day = icalrecurrencetype_day_day_of_week
932 (recurrence.by_day[array_pos]);
933 int mask_bit = -1;
934
935 if (ical_day == 1)
936 mask_bit = 6;
937 else if (ical_day)
938 mask_bit = ical_day - 2;
939
940 if (mask_bit != -1)
941 {
942 *byday_mask |= (1 << mask_bit);
943 }
944 array_pos ++;
945 }
946 }
947
948 return 0;
949 }
950
951 /**
952 * @brief Collect the times of EXDATE or RDATE properties from an VEVENT.
953 * The returned GPtrArray will contain pointers to icaltimetype structs, which
954 * will be freed with g_ptr_array_free.
955 *
956 * @param[in] vevent The VEVENT component to collect times.
957 * @param[in] type The property to get the times from.
958 *
959 * @return GPtrArray with pointers to collected times or NULL on error.
960 */
961 static GPtrArray*
icalendar_times_from_vevent(icalcomponent * vevent,icalproperty_kind type)962 icalendar_times_from_vevent (icalcomponent *vevent, icalproperty_kind type)
963 {
964 GPtrArray* times;
965 icalproperty *date_prop;
966
967 if (icalcomponent_isa (vevent) != ICAL_VEVENT_COMPONENT
968 || (type != ICAL_EXDATE_PROPERTY && type != ICAL_RDATE_PROPERTY))
969 return NULL;
970
971 times = g_ptr_array_new_with_free_func (g_free);
972
973 date_prop = icalcomponent_get_first_property (vevent, type);
974 while (date_prop)
975 {
976 icaltimetype *time;
977 time = g_malloc0 (sizeof (icaltimetype));
978 if (type == ICAL_EXDATE_PROPERTY)
979 {
980 *time = icalproperty_get_exdate (date_prop);
981 }
982 else if (type == ICAL_RDATE_PROPERTY)
983 {
984 struct icaldatetimeperiodtype datetimeperiod;
985 datetimeperiod = icalproperty_get_rdate (date_prop);
986 // Assume periods have been converted to date or datetime
987 *time = datetimeperiod.time;
988 }
989 g_ptr_array_insert (times, -1, time);
990 date_prop = icalcomponent_get_next_property (vevent, type);
991 }
992
993 return times;
994 }
995
996 /**
997 * @brief Tests if an icaltimetype matches one in a GPtrArray.
998 * When an icaltimetype is a date, only the date must match, otherwise both
999 * date and time must match.
1000 *
1001 * @param[in] time The icaltimetype to try to find a match of.
1002 * @param[in] times_array Array of pointers to check for a matching time.
1003 *
1004 * @return Whether a match was found.
1005 */
1006 static gboolean
icalendar_time_matches_array(icaltimetype time,GPtrArray * times_array)1007 icalendar_time_matches_array (icaltimetype time, GPtrArray *times_array)
1008 {
1009 gboolean found = FALSE;
1010 int index;
1011
1012 if (times_array == NULL)
1013 return FALSE;
1014
1015 for (index = 0;
1016 found == FALSE && index < times_array->len;
1017 index++)
1018 {
1019 int compare_result;
1020 icaltimetype *array_time = g_ptr_array_index (times_array, index);
1021
1022 if (array_time->is_date)
1023 compare_result = icaltime_compare_date_only (time, *array_time);
1024 else
1025 compare_result = icaltime_compare (time, *array_time);
1026
1027 if (compare_result == 0)
1028 found = TRUE;
1029 }
1030 return found;
1031 }
1032
1033 /**
1034 * @brief Get the next or previous time from a list of RDATEs.
1035 *
1036 * @param[in] rdates The list of RDATEs.
1037 * @param[in] tz The icaltimezone to use.
1038 * @param[in] ref_time_ical The reference time (usually the current time).
1039 * @param[in] periods_offset 0 for next, -1 for previous from/before reference.
1040 *
1041 * @return The next or previous time as time_t.
1042 */
1043 static time_t
icalendar_next_time_from_rdates(GPtrArray * rdates,icaltimetype ref_time_ical,icaltimezone * tz,int periods_offset)1044 icalendar_next_time_from_rdates (GPtrArray *rdates,
1045 icaltimetype ref_time_ical,
1046 icaltimezone *tz,
1047 int periods_offset)
1048 {
1049 int index;
1050 time_t ref_time, closest_time;
1051 int old_diff;
1052
1053 closest_time = 0;
1054 ref_time = icaltime_as_timet_with_zone (ref_time_ical, tz);
1055 if (periods_offset < 0)
1056 old_diff = INT_MIN;
1057 else
1058 old_diff = INT_MAX;
1059
1060 for (index = 0; index < rdates->len; index++)
1061 {
1062 icaltimetype *iter_time_ical;
1063 time_t iter_time;
1064 int time_diff;
1065
1066 iter_time_ical = g_ptr_array_index (rdates, index);
1067 iter_time = icaltime_as_timet_with_zone (*iter_time_ical, tz);
1068 time_diff = iter_time - ref_time;
1069
1070 // Cases: previous (offset -1): latest before reference
1071 // next (offset 0): earliest after reference
1072 if ((periods_offset == -1 && time_diff < 0 && time_diff > old_diff)
1073 || (periods_offset == 0 && time_diff > 0 && time_diff < old_diff))
1074 {
1075 closest_time = iter_time;
1076 old_diff = time_diff;
1077 }
1078 }
1079
1080 return closest_time;
1081 }
1082
1083 /**
1084 * @brief Calculate the next time of a recurrence
1085 *
1086 * @param[in] recurrence The recurrence rule to evaluate.
1087 * @param[in] dtstart The start time of the recurrence.
1088 * @param[in] reference_time The reference time (usually the current time).
1089 * @param[in] tz The icaltimezone to use.
1090 * @param[in] exdates GList of EXDATE dates or datetimes to skip.
1091 * @param[in] rdates GList of RDATE datetimes to include.
1092 * @param[in] periods_offset 0 for next, -1 for previous from/before reference.
1093 *
1094 * @return The next time.
1095 */
1096 static time_t
icalendar_next_time_from_recurrence(struct icalrecurrencetype recurrence,icaltimetype dtstart,icaltimetype reference_time,icaltimezone * tz,GPtrArray * exdates,GPtrArray * rdates,int periods_offset)1097 icalendar_next_time_from_recurrence (struct icalrecurrencetype recurrence,
1098 icaltimetype dtstart,
1099 icaltimetype reference_time,
1100 icaltimezone *tz,
1101 GPtrArray *exdates,
1102 GPtrArray *rdates,
1103 int periods_offset)
1104 {
1105 icalrecur_iterator *recur_iter;
1106 icaltimetype recur_time, prev_time, next_time;
1107 time_t rdates_time;
1108
1109 // Start iterating over rule-based times
1110 recur_iter = icalrecur_iterator_new (recurrence, dtstart);
1111 recur_time = icalrecur_iterator_next (recur_iter);
1112
1113 if (icaltime_is_null_time (recur_time))
1114 {
1115 // Use DTSTART if there are no recurrence rule times
1116 if (icaltime_compare (dtstart, reference_time) < 0)
1117 {
1118 prev_time = dtstart;
1119 next_time = icaltime_null_time ();
1120 }
1121 else
1122 {
1123 prev_time = icaltime_null_time ();
1124 next_time = dtstart;
1125 }
1126 }
1127 else
1128 {
1129 /* Handle rule-based recurrence times:
1130 * Get the first rule-based recurrence time, skipping ahead in case
1131 * DTSTART is excluded by EXDATEs. */
1132
1133 while (icaltime_is_null_time (recur_time) == FALSE
1134 && icalendar_time_matches_array (recur_time, exdates))
1135 {
1136 recur_time = icalrecur_iterator_next (recur_iter);
1137 }
1138
1139 // Set the first recur_time as either the previous or next time.
1140 if (icaltime_compare (recur_time, reference_time) < 0)
1141 {
1142 prev_time = recur_time;
1143 }
1144 else
1145 {
1146 prev_time = icaltime_null_time ();
1147 }
1148
1149 /* Iterate over rule-based recurrences up to first time after
1150 * reference time */
1151 while (icaltime_is_null_time (recur_time) == FALSE
1152 && icaltime_compare (recur_time, reference_time) < 0)
1153 {
1154 if (icalendar_time_matches_array (recur_time, exdates) == FALSE)
1155 prev_time = recur_time;
1156
1157 recur_time = icalrecur_iterator_next (recur_iter);
1158 }
1159
1160 // Skip further ahead if last recurrence time is in EXDATEs
1161 while (icaltime_is_null_time (recur_time) == FALSE
1162 && icalendar_time_matches_array (recur_time, exdates))
1163 {
1164 recur_time = icalrecur_iterator_next (recur_iter);
1165 }
1166
1167 // Select last recur_time as the next_time
1168 next_time = recur_time;
1169 }
1170
1171 // Get time from RDATEs
1172 rdates_time = icalendar_next_time_from_rdates (rdates, reference_time, tz,
1173 periods_offset);
1174
1175 // Select appropriate time as the RRULE time, compare it to the RDATEs time
1176 // and return the appropriate time.
1177 if (periods_offset == -1)
1178 {
1179 time_t rrule_time;
1180
1181 rrule_time = icaltime_as_timet_with_zone (prev_time, tz);
1182 if (rdates_time == 0 || rrule_time - rdates_time > 0)
1183 return rrule_time;
1184 else
1185 return rdates_time;
1186 }
1187 else
1188 {
1189 time_t rrule_time;
1190
1191 rrule_time = icaltime_as_timet_with_zone (next_time, tz);
1192 if (rdates_time == 0 || rrule_time - rdates_time < 0)
1193 return rrule_time;
1194 else
1195 return rdates_time;
1196 }
1197 }
1198
1199 /**
1200 * @brief Get the next or previous due time from a VCALENDAR component.
1201 * The VCALENDAR must have simplified with icalendar_from_string for this to
1202 * work reliably.
1203 *
1204 * @param[in] vcalendar The VCALENDAR component to get the time from.
1205 * @param[in] default_tzid Timezone id to use if none is set in the iCal.
1206 * @param[in] periods_offset 0 for next, -1 for previous from/before now.
1207 *
1208 * @return The next or previous time as a time_t.
1209 */
1210 time_t
icalendar_next_time_from_vcalendar(icalcomponent * vcalendar,const char * default_tzid,int periods_offset)1211 icalendar_next_time_from_vcalendar (icalcomponent *vcalendar,
1212 const char *default_tzid,
1213 int periods_offset)
1214 {
1215 icalcomponent *vevent;
1216 icaltimetype dtstart, dtstart_with_tz, ical_now;
1217 icaltimezone *tz;
1218 icalproperty *rrule_prop;
1219 struct icalrecurrencetype recurrence;
1220 GPtrArray *exdates, *rdates;
1221 time_t next_time = 0;
1222
1223 // Only offsets -1 and 0 will work properly
1224 if (periods_offset < -1 || periods_offset > 0)
1225 return 0;
1226
1227 // Component must be a VCALENDAR
1228 if (vcalendar == NULL
1229 || icalcomponent_isa (vcalendar) != ICAL_VCALENDAR_COMPONENT)
1230 return 0;
1231
1232 // Process only the first VEVENT
1233 // Others should be removed by icalendar_from_string
1234 vevent = icalcomponent_get_first_component (vcalendar,
1235 ICAL_VEVENT_COMPONENT);
1236 if (vevent == NULL)
1237 return 0;
1238
1239 // Get start time and timezone
1240 dtstart = icalcomponent_get_dtstart (vevent);
1241 if (icaltime_is_null_time (dtstart))
1242 return 0;
1243
1244 tz = (icaltimezone*) icaltime_get_timezone (dtstart);
1245 if (tz == NULL)
1246 {
1247 tz = icalendar_timezone_from_string (default_tzid);
1248 if (tz == NULL)
1249 tz = icaltimezone_get_utc_timezone ();
1250 }
1251
1252 dtstart_with_tz = dtstart;
1253 // Set timezone in case the original DTSTART did not have any set.
1254 icaltime_set_timezone (&dtstart_with_tz, tz);
1255
1256 // Get current time
1257 ical_now = icaltime_current_time_with_zone (tz);
1258 // Set timezone explicitly because icaltime_current_time_with_zone doesn't.
1259 icaltime_set_timezone (&ical_now, tz);
1260 if (ical_now.zone == NULL)
1261 {
1262 ical_now.zone = tz;
1263 }
1264
1265 // Get EXDATEs and RDATEs
1266 exdates = icalendar_times_from_vevent (vevent, ICAL_EXDATE_PROPERTY);
1267 rdates = icalendar_times_from_vevent (vevent, ICAL_RDATE_PROPERTY);
1268
1269 // Try to get the recurrence from the RRULE property
1270 rrule_prop = icalcomponent_get_first_property (vevent, ICAL_RRULE_PROPERTY);
1271 if (rrule_prop)
1272 recurrence = icalproperty_get_rrule (rrule_prop);
1273 else
1274 icalrecurrencetype_clear (&recurrence);
1275
1276 // Calculate next time.
1277 next_time = icalendar_next_time_from_recurrence (recurrence,
1278 dtstart_with_tz,
1279 ical_now, tz,
1280 exdates, rdates,
1281 periods_offset);
1282
1283 // Cleanup
1284 g_ptr_array_free (exdates, TRUE);
1285 g_ptr_array_free (rdates, TRUE);
1286
1287 return next_time;
1288 }
1289
1290 /**
1291 * @brief Get the next or previous due time from a VCALENDAR string.
1292 * The string must be a VCALENDAR simplified with icalendar_from_string for
1293 * this to work reliably.
1294 *
1295 * @param[in] ical_string The VCALENDAR string to get the time from.
1296 * @param[in] default_tzid Timezone id to use if none is set in the iCal.
1297 * @param[in] periods_offset 0 for next, -1 for previous from/before now.
1298 *
1299 * @return The next or previous time as a time_t.
1300 */
1301 time_t
icalendar_next_time_from_string(const char * ical_string,const char * default_tzid,int periods_offset)1302 icalendar_next_time_from_string (const char *ical_string,
1303 const char *default_tzid,
1304 int periods_offset)
1305 {
1306 time_t next_time;
1307 icalcomponent *ical_parsed;
1308
1309 ical_parsed = icalcomponent_new_from_string (ical_string);
1310 next_time = icalendar_next_time_from_vcalendar (ical_parsed, default_tzid,
1311 periods_offset);
1312 icalcomponent_free (ical_parsed);
1313 return next_time;
1314 }
1315
1316 /**
1317 * @brief Get the duration VCALENDAR component.
1318 * The VCALENDAR must have simplified with icalendar_from_string for this to
1319 * work reliably.
1320 *
1321 * @param[in] vcalendar The VCALENDAR component to get the time from.
1322 *
1323 * @return The duration in seconds.
1324 */
1325 int
icalendar_duration_from_vcalendar(icalcomponent * vcalendar)1326 icalendar_duration_from_vcalendar (icalcomponent *vcalendar)
1327 {
1328 icalcomponent *vevent;
1329 struct icaldurationtype duration;
1330
1331 // Component must be a VCALENDAR
1332 if (vcalendar == NULL
1333 || icalcomponent_isa (vcalendar) != ICAL_VCALENDAR_COMPONENT)
1334 return 0;
1335
1336 // Process only the first VEVENT
1337 // Others should be removed by icalendar_from_string
1338 vevent = icalcomponent_get_first_component (vcalendar,
1339 ICAL_VEVENT_COMPONENT);
1340 if (vevent == NULL)
1341 return 0;
1342
1343 // Get the duration
1344 duration = icalcomponent_get_duration (vevent);
1345
1346 // Convert to time_t
1347 return icaldurationtype_as_int (duration);
1348 }
1349
1350 /**
1351 * @brief Get the first time from a VCALENDAR component.
1352 * The VCALENDAR must have simplified with icalendar_from_string for this to
1353 * work reliably.
1354 *
1355 * @param[in] vcalendar The VCALENDAR component to get the time from.
1356 * @param[in] default_tz Timezone to use if none is set in the iCal.
1357 *
1358 * @return The first time as a time_t.
1359 */
1360 time_t
icalendar_first_time_from_vcalendar(icalcomponent * vcalendar,icaltimezone * default_tz)1361 icalendar_first_time_from_vcalendar (icalcomponent *vcalendar,
1362 icaltimezone *default_tz)
1363 {
1364 icalcomponent *vevent;
1365 icaltimetype dtstart;
1366 icaltimezone *tz;
1367
1368 // Component must be a VCALENDAR
1369 if (vcalendar == NULL
1370 || icalcomponent_isa (vcalendar) != ICAL_VCALENDAR_COMPONENT)
1371 return 0;
1372
1373 // Process only the first VEVENT
1374 // Others should be removed by icalendar_from_string
1375 vevent = icalcomponent_get_first_component (vcalendar,
1376 ICAL_VEVENT_COMPONENT);
1377 if (vevent == NULL)
1378 return 0;
1379
1380 // Get start time and timezone
1381 dtstart = icalcomponent_get_dtstart (vevent);
1382 if (icaltime_is_null_time (dtstart))
1383 return 0;
1384
1385 tz = (icaltimezone*) icaltime_get_timezone (dtstart);
1386 if (tz == NULL)
1387 tz = default_tz;
1388
1389 // Convert to time_t
1390 return icaltime_as_timet_with_zone (dtstart, tz);
1391 }
1392
1393 /**
1394 * @brief Cleans up a hosts string, removing extra zeroes from IPv4 addresses.
1395 *
1396 * @param[in] hosts The hosts string to clean.
1397 *
1398 * @return The newly allocated, cleaned up hosts string.
1399 */
1400 gchar *
clean_hosts_string(const char * hosts)1401 clean_hosts_string (const char *hosts)
1402 {
1403 gchar **hosts_split, **item;
1404 GString *new_hosts;
1405 GRegex *ipv4_match_regex, *ipv4_replace_regex;
1406
1407 if (hosts == NULL)
1408 return NULL;
1409
1410 /*
1411 * Regular expression for matching candidates for IPv4 addresses
1412 * (four groups of digits separated by a dot "."),
1413 * with optional extensions for ranges:
1414 * - Another IP address candidate, separated with a hyphen "-"
1415 * (e.g. "192.168.123.001-192.168.123.005)"
1416 * - A final group of digits, separated with a hyphen "-"
1417 * (short form address range, e.g. "192.168.123.001-005)
1418 * - A final group of digits, separated with a slash "-"
1419 * (CIDR notation, e.g. "192.168.123.001/027)
1420 */
1421 ipv4_match_regex
1422 = g_regex_new ("^[0-9]+(?:\\.[0-9]+){3}"
1423 "(?:\\/[0-9]+|-[0-9]+(?:(?:\\.[0-9]+){3})?)?$",
1424 0, 0, NULL);
1425 /*
1426 * Regular expression matching leading zeroes in groups of digits
1427 * separated by dots or other characters.
1428 * First line matches zeroes before non-zero numbers, e.g. "000" in "000120"
1429 * Second line matches groups of all zeroes except one, e.g. "00" in "000"
1430 */
1431 ipv4_replace_regex
1432 = g_regex_new ("(?<=\\D|^)(0+)(?=(?:(?:[1-9]\\d*)(?:\\D|$)))"
1433 "|(?<=\\D|^)(0+)(?=0(?:\\D|$))",
1434 0, 0, NULL);
1435 new_hosts = g_string_new ("");
1436
1437 hosts_split = g_strsplit (hosts, ",", -1);
1438 item = hosts_split;
1439 while (*item)
1440 {
1441 g_strstrip (*item);
1442 if (g_regex_match (ipv4_match_regex, *item, 0, 0))
1443 {
1444 // IPv4 address, address range or CIDR notation
1445 gchar *new_item;
1446 /* Remove leading zeroes in each group of digits by replacing them
1447 * with empty strings,
1448 * e.g. "000.001.002.003-004" becomes "0.1.2.3-4"
1449 */
1450 new_item = g_regex_replace (ipv4_replace_regex,
1451 *item, -1, 0, "", 0, NULL);
1452 g_string_append (new_hosts, new_item);
1453 g_free (new_item);
1454 }
1455 else
1456 g_string_append (new_hosts, *item);
1457
1458 if (*(item + 1))
1459 g_string_append (new_hosts, ", ");
1460 item++;
1461 }
1462 g_strfreev (hosts_split);
1463
1464 g_regex_unref (ipv4_match_regex);
1465 g_regex_unref (ipv4_replace_regex);
1466
1467 return g_string_free (new_hosts, FALSE);
1468 }
1469