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