Reworked fixing unsecure command-line invocation in templates' |program{}
[claws.git] / src / common / utils.c
index 2e646ba1b48ca232d8fd61fe388ed59286dea6ce..19ef5ab8827bb2301743a2adc64f9535619c9010 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
- * Copyright (C) 1999-2016 Hiroyuki Yamamoto & The Claws Mail Team
+ * Copyright (C) 1999-2020 The Claws Mail Team and Hiroyuki Yamamoto
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 #include <ctype.h>
 #include <errno.h>
 #include <sys/param.h>
-#ifndef G_OS_WIN32
-#include <sys/socket.h>
+#ifdef G_OS_WIN32
+#  include <ws2tcpip.h>
+#else
+#  include <sys/socket.h>
 #endif
 
 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
@@ -298,6 +300,7 @@ gchar *strcrchomp(gchar *str)
        return str;
 }
 
+#ifndef HAVE_STRCASESTR
 /* Similar to `strstr' but this function ignores the case of both strings.  */
 gchar *strcasestr(const gchar *haystack, const gchar *needle)
 {
@@ -305,6 +308,7 @@ gchar *strcasestr(const gchar *haystack, const gchar *needle)
 
        return strncasestr(haystack, haystack_len, needle);
 }
+#endif /* HAVE_STRCASESTR */
 
 gchar *strncasestr(const gchar *haystack, gint haystack_len, const gchar *needle)
 {
@@ -892,8 +896,8 @@ void unfold_line(gchar *str)
 
                len = g_unichar_to_utf8(c, NULL);
 
-               if (!g_unichar_isdefined(c) || !g_unichar_isprint(c) ||
-                               g_unichar_isspace(c)) {
+               if ((!g_unichar_isdefined(c) || !g_unichar_isprint(c) ||
+                               g_unichar_isspace(c)) && c != 173) {
                        /* replace anything bad or whitespacey with a single space */
                        *ch = ' ';
                        ch++;
@@ -1409,7 +1413,8 @@ gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc,
                                          "../",
                                          NULL };
        gint num_attach = 0;
-       gchar **my_att = NULL;
+
+       cm_return_val_if_fail(mailto != NULL, -1);
 
        Xstrdup_a(tmp_mailto, mailto, return -1);
 
@@ -1425,9 +1430,6 @@ gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc,
        if (to && !*to)
                *to = decode_uri_gdup(tmp_mailto);
 
-       my_att = g_malloc(sizeof(char *));
-       my_att[0] = NULL;
-
        while (p) {
                gchar *field, *value;
 
@@ -1454,6 +1456,7 @@ gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc,
                        } else {
                                gchar *tmp = decode_uri_gdup(value);
                                gchar *new_from = g_strdup_printf("%s, %s", *from, tmp);
+                               g_free(tmp);
                                g_free(*from);
                                *from = new_from;
                        }
@@ -1463,6 +1466,7 @@ gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc,
                        } else {
                                gchar *tmp = decode_uri_gdup(value);
                                gchar *new_cc = g_strdup_printf("%s, %s", *cc, tmp);
+                               g_free(tmp);
                                g_free(*cc);
                                *cc = new_cc;
                        }
@@ -1472,6 +1476,7 @@ gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc,
                        } else {
                                gchar *tmp = decode_uri_gdup(value);
                                gchar *new_bcc = g_strdup_printf("%s, %s", *bcc, tmp);
+                               g_free(tmp);
                                g_free(*bcc);
                                *bcc = new_bcc;
                        }
@@ -1481,19 +1486,42 @@ gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc,
                } else if (body && !*body && !g_ascii_strcasecmp(field, "body")) {
                        *body = decode_uri_gdup(value);
                } else if (body && !*body && !g_ascii_strcasecmp(field, "insert")) {
+                       int i = 0;
                        gchar *tmp = decode_uri_gdup(value);
-                       if (!g_file_get_contents(tmp, body, NULL, NULL)) {
-                               g_warning("couldn't set insert file '%s' in body", value);
+
+                       for (; forbidden_uris[i]; i++) {
+                               if (strstr(tmp, forbidden_uris[i])) {
+                                       g_print("Refusing to insert '%s', potential private data leak\n",
+                                                       tmp);
+                                       g_free(tmp);
+                                       tmp = NULL;
+                                       break;
+                               }
+                       }
+
+                       if (tmp) {
+                               if (!is_file_entry_regular(tmp)) {
+                                       g_warning("Refusing to insert '%s', not a regular file\n", tmp);
+                               } else if (!g_file_get_contents(tmp, body, NULL, NULL)) {
+                                       g_warning("couldn't set insert file '%s' in body", value);
+                               }
+
+                               g_free(tmp);
                        }
-                       g_free(tmp);
                } else if (attach && !g_ascii_strcasecmp(field, "attach")) {
                        int i = 0;
                        gchar *tmp = decode_uri_gdup(value);
+                       gchar **my_att = g_malloc(sizeof(char *));
+
+                       my_att[0] = NULL;
+
                        for (; forbidden_uris[i]; i++) {
                                if (strstr(tmp, forbidden_uris[i])) {
                                        g_print("Refusing to attach '%s', potential private data leak\n",
                                                        tmp);
                                        g_free(tmp);
+                                       g_free(my_att);
+                                       tmp = NULL;
                                        break;
                                }
                        }
@@ -1503,6 +1531,7 @@ gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc,
                                my_att = g_realloc(my_att, (sizeof(char *))*(num_attach+1));
                                my_att[num_attach-1] = tmp;
                                my_att[num_attach] = NULL;
+                               *attach = my_att;
                        }
                } else if (inreplyto && !*inreplyto &&
                           !g_ascii_strcasecmp(field, "in-reply-to")) {
@@ -1510,8 +1539,6 @@ gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc,
                }
        }
 
-       if (attach)
-               *attach = my_att;
        return 0;
 }
 
@@ -1908,6 +1935,29 @@ const gchar *get_domain_name(void)
 #endif
 }
 
+/* Tells whether the given host address string is a valid representation of a
+ * numerical IP (v4 or, if supported, v6) address.
+ */
+gboolean is_numeric_host_address(const gchar *hostaddress)
+{
+       struct addrinfo hints, *res;
+       int err;
+
+       /* See what getaddrinfo makes of the string when told that it is a
+        * numeric IP address representation. */
+       memset(&hints, 0, sizeof(struct addrinfo));
+       hints.ai_family = AF_UNSPEC;
+       hints.ai_socktype = 0;
+       hints.ai_flags = AI_NUMERICHOST;
+       hints.ai_protocol = 0;
+
+       err = getaddrinfo(hostaddress, NULL, &hints, &res);
+       if (err == 0)
+               freeaddrinfo(res);
+
+       return (err == 0);
+}
+
 off_t get_file_size(const gchar *file)
 {
 #ifdef G_OS_WIN32
@@ -2012,6 +2062,14 @@ gboolean is_file_entry_exist(const gchar *file)
        return g_file_test(file, G_FILE_TEST_EXISTS);
 }
 
+gboolean is_file_entry_regular(const gchar *file)
+{
+       if (file == NULL)
+               return FALSE;
+
+       return g_file_test(file, G_FILE_TEST_IS_REGULAR);
+}
+
 gboolean dirent_is_regular_file(struct dirent *d)
 {
 #if !defined(G_OS_WIN32) && defined(HAVE_DIRENT_D_TYPE)
@@ -2549,6 +2607,8 @@ gint execute_command_line(const gchar *cmdline, gboolean async,
        gchar **argv;
        gint ret;
 
+       cm_return_val_if_fail(cmdline != NULL, -1);
+
        debug_print("execute_command_line(): executing: %s\n", cmdline?cmdline:"(null)");
 
        argv = strsplit_with_quote(cmdline, " ", 0);
@@ -2581,6 +2641,38 @@ gchar *get_command_output(const gchar *cmdline)
        return child_stdout;
 }
 
+FILE *get_command_output_stream(const char* cmdline)
+{
+    GPid pid;
+       GError *err = NULL;
+       gchar **argv = NULL;
+    int fd;
+
+       cm_return_val_if_fail(cmdline != NULL, NULL);
+
+       debug_print("get_command_output_stream(): executing: %s\n", cmdline);
+
+       /* turn the command-line string into an array */
+       if (!g_shell_parse_argv(cmdline, NULL, &argv, &err)) {
+               g_warning("could not parse command line from '%s': %s\n", cmdline, err->message);
+        g_error_free(err);
+               return NULL;
+       }
+
+    if (!g_spawn_async_with_pipes(NULL, argv, NULL, G_SPAWN_SEARCH_PATH,
+                                  NULL, NULL, &pid, NULL, &fd, NULL, &err)
+        && err)
+    {
+        g_warning("could not spawn '%s': %s\n", cmdline, err->message);
+        g_error_free(err);
+               g_strfreev(argv);
+        return NULL;
+    }
+
+       g_strfreev(argv);
+       return fdopen(fd, "r");
+}
+
 static gint is_unchanged_uri_char(char c)
 {
        switch (c) {
@@ -3456,12 +3548,11 @@ static GHashTable *create_domain_tab(void)
 
 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
 {
-       const gint MAX_LVL_DOM_NAME_LEN = 6;
-       gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
-       const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
+       gchar buf[BUFFSIZE + 1];
+       const gchar *m = buf + BUFFSIZE + 1;
        register gchar *p;
 
-       if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
+       if (last - first > BUFFSIZE || first > last)
                return FALSE;
 
        for (p = buf; p < m &&  first < last; *p++ = *first++)
@@ -4209,7 +4300,11 @@ size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt
                                }
                                break;
                        case 'r':
+#ifdef G_OS_WIN32
+                               strftime(subbuf, 64, "%I:%M:%S %p", lt);
+#else
                                strftime(subbuf, 64, "%r", lt);
+#endif
                                len = strlen(subbuf); CHECK_SIZE();
                                strncpy2(curpos, subbuf, buflen - total_done);
                                break;
@@ -4518,7 +4613,7 @@ guchar *g_base64_decode_zero(const gchar *text, gsize *out_len)
        g_free(tmp);
 
        if (strlen(out) != *out_len) {
-               g_warning ("strlen(out) %zd != *out_len %" G_GSIZE_FORMAT, strlen(out), *out_len);
+               g_warning ("strlen(out) %"G_GSIZE_FORMAT" != *out_len %"G_GSIZE_FORMAT, strlen(out), *out_len);
        }
 
        return out;
@@ -4588,7 +4683,7 @@ get_random_bytes(void *buf, size_t count)
        /* Read data from the source into buf. */
 #if defined G_OS_WIN32
        if (!CryptGenRandom(rnd, count, buf)) {
-               debug_print("Could not read %zd random bytes.\n", count);
+               debug_print("Could not read %"G_GSIZE_FORMAT" random bytes.\n", count);
                CryptReleaseContext(rnd, 0);
                return FALSE;
        }
@@ -4652,19 +4747,36 @@ gboolean get_serverportfp_from_filename(const gchar *str, gchar **server, gchar
                return FALSE;
        }
 
-       *server = g_strndup(str, dotport_pos - str);
+       if (server != NULL)
+               *server = g_strndup(str, dotport_pos - str);
        if (dotfp_pos) {
-               *port = g_strndup(dotport_pos + 1, dotfp_pos - dotport_pos - 1);
-               *fp = g_strndup(dotfp_pos + 1, dotcert_pos - dotfp_pos - 1);
+               if (port != NULL)
+                       *port = g_strndup(dotport_pos + 1, dotfp_pos - dotport_pos - 1);
+               if (fp != NULL)
+                       *fp = g_strndup(dotfp_pos + 1, dotcert_pos - dotfp_pos - 1);
        } else {
-               *port = g_strndup(dotport_pos + 1, dotcert_pos - dotport_pos - 1);
-               *fp = NULL;
+               if (port != NULL)
+                       *port = g_strndup(dotport_pos + 1, dotcert_pos - dotport_pos - 1);
+               if (fp != NULL)
+                       *fp = NULL;
        }
 
-       debug_print("filename='%s' => server='%s' port='%s' fp='%s'\n", str, *server, *port, *fp);
-       if (!*server || !*port)
+       debug_print("filename='%s' => server='%s' port='%s' fp='%s'\n",
+                       str,
+                       (server ? *server : "(n/a)"),
+                       (port ? *port : "(n/a)"),
+                       (fp ? *fp : "(n/a)"));
+
+       if (!(server && *server) || !(port && *port))
                return FALSE;
        else
                return TRUE;
 }
 
+#ifdef G_OS_WIN32
+gchar *win32_debug_log_path(void)
+{
+       return g_strconcat(g_get_tmp_dir(), G_DIR_SEPARATOR_S,
+                       "claws-win32.log", NULL);
+}
+#endif