1 /* Copyright (C) 2009-2021 Greenbone Networks GmbH
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the 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 General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 /**
21 * @file
22 * @brief File utilities.
23 */
24
25 /* time.h in glibc2 needs this for strptime. */
26 #define _GNU_SOURCE
27
28 #include "fileutils.h"
29
30 #include <errno.h> /* for errno */
31 #include <gio/gio.h> /* for g_file_new_for_path, GFile */
32 #include <glib/gstdio.h> /* for g_lstat, g_remove */
33 #include <glib/gtypes.h> /* for gsize */
34 #include <string.h> /* for strlen, memset, strcmp */
35 #include <sys/stat.h> /* for stat, S_ISDIR */
36 #include <time.h> /* for tm, strptime, localtime, time, time_t */
37
38 #undef G_LOG_DOMAIN
39 /**
40 * @brief GLib logging domain.
41 */
42 #define G_LOG_DOMAIN "libgvm util"
43
44 /**
45 * @brief Checks whether a file is a directory or not.
46 *
47 * This is a replacement for the g_file_test functionality which is reported
48 * to be unreliable under certain circumstances, for example if this
49 * application and glib are compiled with a different libc.
50 *
51 * Symbolic links are not followed.
52 *
53 * @param[in] name Name of file or directory.
54 *
55 * @return 1 if parameter is directory, 0 if it is not, -1 if it does not
56 * exist or could not be accessed.
57 */
58 int
gvm_file_check_is_dir(const char * name)59 gvm_file_check_is_dir (const char *name)
60 {
61 struct stat sb;
62
63 if (g_lstat (name, &sb))
64 {
65 g_warning ("g_lstat(%s) failed - %s\n", name, g_strerror (errno));
66 return -1;
67 }
68
69 return S_ISDIR (sb.st_mode);
70 }
71
72 /**
73 * @brief Checks whether a file or directory exists.
74 *
75 * Unlike g_file_test this checks the permissions based on the effective
76 * UID and GID instead of the real one.
77 *
78 * Symbolic links are followed.
79 *
80 * @param[in] name Name of file or directory.
81 *
82 * @return 1 if file exists, 0 if it is not.
83 */
84 int
gvm_file_exists(const char * name)85 gvm_file_exists (const char *name)
86 {
87 return eaccess (name, F_OK) == 0;
88 }
89
90 /**
91 * @brief Checks whether a file or directory exists and is executable.
92 *
93 * Unlike g_file_test this checks the permissions based on the effective
94 * UID and GID instead of the real one.
95 *
96 * Symbolic links are followed.
97 *
98 * @param[in] name Name of file or directory.
99 *
100 * @return 1 if file is executable, 0 if it is not.
101 */
102 int
gvm_file_is_executable(const char * name)103 gvm_file_is_executable (const char *name)
104 {
105 return eaccess (name, X_OK) == 0;
106 }
107
108 /**
109 * @brief Checks whether a file or directory exists and is readable.
110 *
111 * Unlike g_file_test this checks the permissions based on the effective
112 * UID and GID instead of the real one.
113 *
114 * Symbolic links are followed.
115 *
116 * @param[in] name Name of file or directory.
117 *
118 * @return 1 if file is readable, 0 if it is not.
119 */
120 int
gvm_file_is_readable(const char * name)121 gvm_file_is_readable (const char *name)
122 {
123 return eaccess (name, R_OK) == 0;
124 }
125
126 /**
127 * @brief Recursively removes files and directories.
128 *
129 * This function will recursively call itself to delete a path and any
130 * contents of this path.
131 *
132 * @param[in] pathname The name of the file to be deleted from the filesystem.
133 *
134 * @return 0 if the name was successfully deleted, -1 if an error occurred.
135 */
136 int
gvm_file_remove_recurse(const gchar * pathname)137 gvm_file_remove_recurse (const gchar *pathname)
138 {
139 if (gvm_file_check_is_dir (pathname) == 1)
140 {
141 GError *error = NULL;
142 GDir *directory = g_dir_open (pathname, 0, &error);
143
144 if (directory == NULL)
145 {
146 g_warning ("g_dir_open(%s) failed - %s\n", pathname, error->message);
147 g_error_free (error);
148 return -1;
149 }
150 else
151 {
152 int ret = 0;
153 const gchar *entry = NULL;
154
155 while ((entry = g_dir_read_name (directory)) && (ret == 0))
156 {
157 gchar *entry_path = g_build_filename (pathname, entry, NULL);
158 ret = gvm_file_remove_recurse (entry_path);
159 g_free (entry_path);
160 if (ret != 0)
161 {
162 g_warning ("Failed to remove %s from %s!", entry, pathname);
163 g_dir_close (directory);
164 return ret;
165 }
166 }
167 g_dir_close (directory);
168 }
169 }
170
171 return g_remove (pathname);
172 }
173
174 /**
175 * @brief Copies a source file into a destination file.
176 *
177 * If the destination file does exist already, it will be overwritten.
178 *
179 * @param[in] source_file Source file name.
180 * @param[in] dest_file Destination file name.
181 *
182 * @return TRUE if successful, FALSE otherwise.
183 */
184 gboolean
gvm_file_copy(const gchar * source_file,const gchar * dest_file)185 gvm_file_copy (const gchar *source_file, const gchar *dest_file)
186 {
187 gboolean rc;
188 GFile *sfile, *dfile;
189 GError *error;
190
191 sfile = g_file_new_for_path (source_file);
192 dfile = g_file_new_for_path (dest_file);
193 error = NULL;
194
195 rc =
196 g_file_copy (sfile, dfile, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &error);
197 if (!rc)
198 {
199 g_warning ("%s: g_file_copy(%s, %s) failed - %s\n", __func__, source_file,
200 dest_file, error->message);
201 g_error_free (error);
202 }
203
204 g_object_unref (sfile);
205 g_object_unref (dfile);
206 return rc;
207 }
208
209 /**
210 * @brief Moves a source file into a destination file.
211 *
212 * If the destination file does exist already, it will be overwritten.
213 *
214 * @param[in] source_file Source file name.
215 * @param[in] dest_file Destination file name.
216 *
217 * @return TRUE if successful, FALSE otherwise.
218 */
219 gboolean
gvm_file_move(const gchar * source_file,const gchar * dest_file)220 gvm_file_move (const gchar *source_file, const gchar *dest_file)
221 {
222 gboolean rc;
223 GFile *sfile, *dfile;
224 GError *error;
225
226 sfile = g_file_new_for_path (source_file);
227 dfile = g_file_new_for_path (dest_file);
228 error = NULL;
229
230 rc =
231 g_file_move (sfile, dfile, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &error);
232 if (!rc)
233 {
234 g_warning ("%s: g_file_move(%s, %s) failed - %s\n", __func__, source_file,
235 dest_file, error->message);
236 g_error_free (error);
237 }
238
239 g_object_unref (sfile);
240 g_object_unref (dfile);
241 return rc;
242 }
243
244 /**
245 * @brief Get the content of a file in base64 format.
246 *
247 * @param[in] path Path to file.
248 *
249 * @return Allocated nul-terminated string, NULL otherwise.
250 */
251 char *
gvm_file_as_base64(const char * path)252 gvm_file_as_base64 (const char *path)
253 {
254 GError *error = NULL;
255 char *content, *encoded;
256 gsize len;
257
258 if (!g_file_get_contents (path, &content, &len, &error))
259 {
260 g_error_free (error);
261 return NULL;
262 }
263 encoded = g_base64_encode ((guchar *) content, len);
264 g_free (content);
265 return encoded;
266 }
267
268 /**
269 * @brief Generates a file name for exporting.
270 *
271 * @param[in] fname_format Format string.
272 * @param[in] username Current user name.
273 * @param[in] type Type of resource.
274 * @param[in] uuid UUID of resource.
275 * @param[in] creation_iso_time Creation time of resource in ISO format.
276 * @param[in] modification_iso_time Modification time of resource (ISO).
277 * @param[in] name Name of resource.
278 * @param[in] format_name Name of format plugin.
279 *
280 * @return The file name.
281 */
282 gchar *
gvm_export_file_name(const char * fname_format,const char * username,const char * type,const char * uuid,const char * creation_iso_time,const char * modification_iso_time,const char * name,const char * format_name)283 gvm_export_file_name (const char *fname_format, const char *username,
284 const char *type, const char *uuid,
285 const char *creation_iso_time,
286 const char *modification_iso_time, const char *name,
287 const char *format_name)
288 {
289 time_t now;
290 struct tm now_broken;
291 gchar *now_date_str, *creation_date_str, *modification_date_str;
292 gchar *now_time_str, *creation_time_str, *modification_time_str;
293 struct tm creation_time, modification_time;
294 gchar *creation_date_short, *modification_date_short;
295 gchar *fname_point;
296 GString *file_name_buf;
297 int format_state = 0;
298 char *ret;
299
300 creation_date_str = NULL;
301 modification_date_str = NULL;
302 creation_time_str = NULL;
303 modification_time_str = NULL;
304
305 now = time (NULL);
306 if (localtime_r (&now, &now_broken) == NULL)
307 {
308 g_warning ("%s: localtime failed", __func__);
309 }
310 now_date_str = g_strdup_printf ("%04d%02d%02d", (now_broken.tm_year + 1900),
311 (now_broken.tm_mon + 1), now_broken.tm_mday);
312 now_time_str = g_strdup_printf ("%02d%02d%02d", now_broken.tm_hour,
313 now_broken.tm_min, now_broken.tm_sec);
314
315 memset (&creation_time, 0, sizeof (struct tm));
316 memset (&modification_time, 0, sizeof (struct tm));
317 creation_date_short = NULL;
318 modification_date_short = NULL;
319
320 if (creation_iso_time && (strlen (creation_iso_time) >= 19))
321 creation_date_short = g_strndup (creation_iso_time, 19);
322
323 if (creation_date_short
324 && (((ret = strptime (creation_date_short, "%Y-%m-%dT%H:%M:%S",
325 &creation_time))
326 == NULL)
327 || (strlen (ret) == 0)))
328 {
329 creation_date_str =
330 g_strdup_printf ("%04d%02d%02d", (creation_time.tm_year + 1900),
331 (creation_time.tm_mon + 1), creation_time.tm_mday);
332 creation_time_str =
333 g_strdup_printf ("%02d%02d%02d", creation_time.tm_hour,
334 creation_time.tm_min, creation_time.tm_sec);
335 }
336
337 if (modification_iso_time && (strlen (modification_iso_time) >= 19))
338 modification_date_short = g_strndup (modification_iso_time, 19);
339
340 if (modification_date_short
341 && (((ret = strptime (modification_date_short, "%Y-%m-%dT%H:%M:%S",
342 &modification_time))
343 == NULL)
344 || (strlen (ret) == 0)))
345 {
346 modification_date_str = g_strdup_printf (
347 "%04d%02d%02d", (modification_time.tm_year + 1900),
348 (modification_time.tm_mon + 1), modification_time.tm_mday);
349
350 modification_time_str =
351 g_strdup_printf ("%02d%02d%02d", modification_time.tm_hour,
352 modification_time.tm_min, modification_time.tm_sec);
353 }
354
355 if (creation_date_str == NULL)
356 creation_date_str = g_strdup (now_date_str);
357 if (modification_date_str == NULL)
358 modification_date_str = g_strdup (creation_date_str);
359 if (creation_time_str == NULL)
360 creation_time_str = g_strdup (now_time_str);
361 if (modification_time_str == NULL)
362 modification_time_str = g_strdup (creation_time_str);
363
364 file_name_buf = g_string_new ("");
365
366 fname_point = (char *) fname_format;
367
368 while (format_state >= 0 && *fname_point != '\0')
369 {
370 if (format_state == 0)
371 {
372 if (*fname_point == '%')
373 format_state = 1;
374 else if (*fname_point == '"')
375 g_string_append (file_name_buf, "\\\"");
376 else if (*fname_point <= ' ')
377 g_string_append_c (file_name_buf, '_');
378 else
379 g_string_append_c (file_name_buf, *fname_point);
380 }
381 else if (format_state == 1)
382 {
383 format_state = 0;
384 switch (*fname_point)
385 {
386 case 'C':
387 g_string_append (file_name_buf, creation_date_str);
388 break;
389 case 'c':
390 g_string_append (file_name_buf, creation_time_str);
391 break;
392 case 'd':
393 g_string_append_printf (file_name_buf, "%02d",
394 modification_time.tm_mday);
395 break;
396 case 'D':
397 g_string_append (file_name_buf, now_date_str);
398 break;
399 case 'F':
400 g_string_append (file_name_buf,
401 format_name ? format_name : "XML");
402 break;
403 case 'M':
404 g_string_append (file_name_buf, modification_date_str);
405 break;
406 case 'm':
407 g_string_append (file_name_buf, modification_time_str);
408 break;
409 case 'N':
410 g_string_append (file_name_buf,
411 name ? name : (type ? type : "unnamed"));
412 break;
413 case 'o':
414 g_string_append_printf (file_name_buf, "%02d",
415 modification_time.tm_mon + 1);
416 break;
417 case 'T':
418 g_string_append (file_name_buf, type ? type : "resource");
419 break;
420 case 't':
421 g_string_append (file_name_buf, now_time_str);
422 break;
423 case 'U':
424 g_string_append (file_name_buf, uuid ? uuid : "list");
425 break;
426 case 'u':
427 g_string_append (file_name_buf, username ? username : "");
428 break;
429 case 'Y':
430 g_string_append_printf (file_name_buf, "%04d",
431 modification_time.tm_year + 1900);
432 break;
433 case '%':
434 g_string_append_c (file_name_buf, '%');
435 break;
436 default:
437 g_warning ("%s : Unknown file name format placeholder: %%%c.",
438 __func__, *fname_point);
439 format_state = -1;
440 }
441 }
442 fname_point += sizeof (char);
443 }
444
445 if (format_state || strcmp (file_name_buf->str, "") == 0)
446 {
447 g_warning ("%s : Invalid file name format", __func__);
448 g_string_free (file_name_buf, TRUE);
449 return NULL;
450 }
451
452 fname_point = file_name_buf->str;
453 while (*fname_point != '\0')
454 {
455 if (*fname_point <= ' ')
456 *fname_point = '_';
457 fname_point++;
458 }
459
460 g_free (now_date_str);
461 g_free (creation_date_str);
462 g_free (creation_time_str);
463 g_free (modification_date_str);
464 return g_string_free (file_name_buf, FALSE);
465 }
466