From 769c195e8c65121fac741540560a1aa9ad2c7142 Mon Sep 17 00:00:00 2001
From: Andrew Potter <agpotter@gmail.com>
Date: Mon, 4 Mar 2013 11:30:04 -0800
Subject: [PATCH] xfer: add new config option file.xfer.auto_crc32

If a filename has a CRC32 tag in it, the file is checksumed while
being downloaded. The result of the checksum match is displayed in the
xfer buffer.
---
 src/plugins/xfer/xfer-buffer.c  |  32 +++++++++-
 src/plugins/xfer/xfer-config.c  |   6 ++
 src/plugins/xfer/xfer-config.h  |   1 +
 src/plugins/xfer/xfer-dcc.c     | 132 ++++++++++++++++++++++++++++++++++++++--
 src/plugins/xfer/xfer-network.c |  29 +++++++++
 src/plugins/xfer/xfer.c         |  76 ++++++++++++++++++++++-
 src/plugins/xfer/xfer.h         |  23 +++++++
 7 files changed, 291 insertions(+), 8 deletions(-)

diff --git a/src/plugins/xfer/xfer-buffer.c b/src/plugins/xfer/xfer-buffer.c
index 83967ef..92eb315 100644
--- a/src/plugins/xfer/xfer-buffer.c
+++ b/src/plugins/xfer/xfer-buffer.c
@@ -45,6 +45,7 @@ xfer_buffer_refresh (const char *hotlist)
     struct t_xfer *ptr_xfer, *xfer_selected;
     char str_color[256], suffix[32], status[64], date[128], eta[128];
     char str_ip[32];
+    char str_hash[32];
     char *progress_bar, *str_pos, *str_total, *str_bytes_per_sec;
     int i, length, line, progress_bar_size, num_bars;
     unsigned long long pos, pct_complete;
@@ -103,9 +104,36 @@ xfer_buffer_refresh (const char *hotlist)
                           ptr_xfer->remote_address & 0xff);
             }
 
+            str_hash[0] = '\0';
+            if (ptr_xfer->hash_target && ptr_xfer->hash_handle)
+            {
+                if ( ptr_xfer->status == XFER_STATUS_ACTIVE
+                     || ptr_xfer->status == XFER_STATUS_DONE
+                     || ptr_xfer->status == XFER_STATUS_HASHING )
+                {
+                    switch (ptr_xfer->hash_status)
+                    {
+                    case XFER_HASHING_UNDERWAY:
+	                    snprintf(str_hash, sizeof (str_hash), " (%s)", _("Hashing"));
+                        break;
+                    case XFER_HASHING_MATCH:
+	                    snprintf(str_hash, sizeof (str_hash), " (%s)", _("CRC32 OK"));
+                        break;
+                    case XFER_HASHING_MISMATCH:
+	                    snprintf(str_hash, sizeof (str_hash), " (%s)", _("CRC32 Mismatch"));
+                        break;
+                    case XFER_HASHING_RESUME_ERROR:
+	                    snprintf(str_hash, sizeof (str_hash), " (%s)", _("Hash Error"));
+                        break;
+                    default:
+	                    break;
+                    }
+                }
+            }
+
             /* display first line with remote nick, filename and plugin name/id */
             weechat_printf_y (xfer_buffer, (line * 2) + 2,
-                              "%s%s%-24s %s%s%s%s (%s.%s)%s",
+                              "%s%s%-24s %s%s%s%s (%s.%s)%s%s",
                               weechat_color(str_color),
                               (line == xfer_buffer_selected_line) ?
                               "*** " : "    ",
@@ -117,7 +145,7 @@ xfer_buffer_refresh (const char *hotlist)
                               suffix,
                               ptr_xfer->plugin_name,
                               ptr_xfer->plugin_id,
-                              str_ip);
+                              str_ip, str_hash);
 
             snprintf (status, sizeof (status),
                       "%s", _(xfer_status_string[ptr_xfer->status]));
diff --git a/src/plugins/xfer/xfer-config.c b/src/plugins/xfer/xfer-config.c
index ab9171e..4d07596 100644
--- a/src/plugins/xfer/xfer-config.c
+++ b/src/plugins/xfer/xfer-config.c
@@ -61,6 +61,7 @@ struct t_config_option *xfer_config_file_auto_rename;
 struct t_config_option *xfer_config_file_auto_resume;
 struct t_config_option *xfer_config_file_auto_accept_files;
 struct t_config_option *xfer_config_file_auto_accept_chats;
+struct t_config_option *xfer_config_file_auto_crc32;
 
 
 
@@ -304,6 +305,11 @@ xfer_config_init ()
         "auto_accept_chats", "boolean",
         N_("automatically accept chat requests (use carefully!)"),
         NULL, 0, 0, "off", NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL);
+    xfer_config_file_auto_crc32 = weechat_config_new_option (
+        xfer_config_file, ptr_section,
+        "auto_crc32", "boolean",
+        N_("automatically check a CRC32 hash if a tag is in the filename"),
+        NULL, 0, 0, "off", NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL);
 
     return 1;
 }
diff --git a/src/plugins/xfer/xfer-config.h b/src/plugins/xfer/xfer-config.h
index 051e4d8..8b8e4ca 100644
--- a/src/plugins/xfer/xfer-config.h
+++ b/src/plugins/xfer/xfer-config.h
@@ -49,6 +49,7 @@ extern struct t_config_option *xfer_config_file_auto_rename;
 extern struct t_config_option *xfer_config_file_auto_resume;
 extern struct t_config_option *xfer_config_file_auto_accept_files;
 extern struct t_config_option *xfer_config_file_auto_accept_chats;
+extern struct t_config_option *xfer_config_file_auto_crc32;
 
 extern int xfer_config_init ();
 extern int xfer_config_read ();
diff --git a/src/plugins/xfer/xfer-dcc.c b/src/plugins/xfer/xfer-dcc.c
index acc95ea..31d47fd 100644
--- a/src/plugins/xfer/xfer-dcc.c
+++ b/src/plugins/xfer/xfer-dcc.c
@@ -30,6 +30,7 @@
 #include <time.h>
 #include <netdb.h>
 #include <errno.h>
+#include <stdio.h>
 
 #include "../weechat-plugin.h"
 #include "xfer.h"
@@ -218,6 +219,72 @@ xfer_dcc_recv_file_send_ack (struct t_xfer *xfer)
 }
 
 /*
+ * Reads a resumed xfer from disk for hashing
+ *
+ * Returns 0 on success, 1 on failure
+ */
+
+static int
+xfer_dcc_resume_hash(struct t_xfer *xfer)
+{
+    ssize_t end_pos = xfer->start_resume;
+    const size_t buf_len = 1024*1024;
+    char *buf = malloc(buf_len);
+    ssize_t total_read = 0;
+    int ret = 0;
+    int fd = 0;
+
+    if (!buf) return 1;
+
+    while (fd <= 0)
+    {
+        fd = open(xfer->local_filename, O_RDONLY);
+        if (fd < 0)
+        {
+            if (errno == EINTR) continue;
+            fd = 0;
+            ret = 1;
+            break;
+        }
+    }
+
+    if (fd)
+    {
+        while ( total_read < end_pos )
+        {
+            size_t to_read = end_pos - total_read;
+            ssize_t num_read = 0;
+            if (to_read > buf_len)
+            {
+                num_read = read(fd, buf, buf_len);
+            } else {
+                num_read = read(fd, buf, to_read);
+            }
+            if (num_read > 0)
+            {
+                gcry_md_write(xfer->hash_handle, buf, num_read);
+                total_read += num_read;
+            } else {
+                if (num_read < 0)
+                {
+                    if (errno == EINTR) continue;
+                    ret = 1;
+                    break;
+                }
+            }
+        }
+
+        while( close(fd) < 0 )
+        {
+	        if (errno != EINTR) break;
+        }
+    }
+    free(buf);
+
+    return ret;
+}
+
+/*
  * Child process for receiving file with DCC protocol.
  */
 
@@ -230,6 +297,22 @@ xfer_dcc_recv_file_child (struct t_xfer *xfer)
     unsigned long long pos_last_ack;
     fd_set read_fds, write_fds, except_fds;
 
+    /* If resuming, hash the portion of the file we have */
+    if (xfer->start_resume > 0 && xfer->hash_handle)
+    {
+        xfer_network_write_pipe(xfer, XFER_STATUS_HASHING,
+                                XFER_NO_ERROR);
+        if (xfer_dcc_resume_hash(xfer))
+        {
+            gcry_md_close(xfer->hash_handle);
+            xfer->hash_handle = NULL;
+            xfer_network_write_pipe (xfer, XFER_STATUS_HASHING,
+                                     XFER_ERROR_HASH_RESUME_ERROR);
+        }
+        xfer_network_write_pipe(xfer, XFER_STATUS_CONNECTING,
+                                XFER_NO_ERROR);
+    }
+
     /* first connect to sender (blocking) */
     if (!weechat_network_connect_to (xfer->proxy, xfer->sock,
                                      xfer->remote_address, xfer->port))
@@ -288,6 +371,8 @@ xfer_dcc_recv_file_child (struct t_xfer *xfer)
             }
             else
             {
+                ssize_t total_written = 0;
+
                 if (num_read == 0)
                 {
                     xfer_network_write_pipe (xfer, XFER_STATUS_FAILED,
@@ -295,11 +380,27 @@ xfer_dcc_recv_file_child (struct t_xfer *xfer)
                     return;
                 }
 
-                if (write (xfer->file, buffer, num_read) == -1)
+                /* bytes received, write to disk */
+                while (total_written < num_read)
                 {
-                    xfer_network_write_pipe (xfer, XFER_STATUS_FAILED,
-                                             XFER_ERROR_WRITE_LOCAL);
-                    return;
+                    ssize_t written = write (xfer->file,
+                                             buffer + total_written,
+                                             num_read - total_written);
+                    if ( written < 0 )
+                    {
+	                    if ( errno == EINTR ) continue;
+                        xfer_network_write_pipe (xfer, XFER_STATUS_FAILED,
+                                                 XFER_ERROR_WRITE_LOCAL);
+                        return;
+                    } else {
+                        if (xfer->hash_handle)
+                        {
+                            gcry_md_write (xfer->hash_handle,
+                                           buffer + total_written,
+                                           written);
+                        }
+                        total_written += written;
+                    }
                 }
 
                 xfer->pos += (unsigned long long) num_read;
@@ -307,10 +408,33 @@ xfer_dcc_recv_file_child (struct t_xfer *xfer)
                 /* file received OK? */
                 if (xfer->pos >= xfer->size)
                 {
+                    /* Check hash and report result to pipe */
+                    if (xfer->hash_handle)
+                    {
+                        unsigned char* bin_hash;
+                        char hash[9];
+                        gcry_md_final(xfer->hash_handle);
+                        bin_hash = gcry_md_read(xfer->hash_handle, 0);
+                        if (bin_hash)
+                        {
+                            snprintf(hash, 9, "%.2X%.2X%.2X%.2X", bin_hash[0],
+                                     bin_hash[1], bin_hash[2], bin_hash[3]);
+                            if (weechat_strcasecmp(hash, xfer->hash_target) == 0)
+                            {
+                                xfer_network_write_pipe (xfer, XFER_STATUS_HASHED,
+                                                         XFER_NO_ERROR);
+                            } else {
+                                xfer_network_write_pipe (xfer, XFER_STATUS_HASHED,
+                                                         XFER_ERROR_HASH_MISMATCH);
+                            }
+                        }
+                    }
+
                     /*
                      * extra delay before sending ACK, otherwise the send of ACK
                      * may fail
                      */
+                    fsync(xfer->file);
                     usleep (100000);
 
                     /* send ACK to sender without checking return code (file OK) */
diff --git a/src/plugins/xfer/xfer-network.c b/src/plugins/xfer/xfer-network.c
index 8aea101..69943af 100644
--- a/src/plugins/xfer/xfer-network.c
+++ b/src/plugins/xfer/xfer-network.c
@@ -151,11 +151,29 @@ xfer_network_child_read_cb (void *arg_xfer, int fd)
                                 _("%s%s: unable to send ACK to sender"),
                                 weechat_prefix ("error"), XFER_PLUGIN_NAME);
                 break;
+            case XFER_ERROR_HASH_MISMATCH:
+                weechat_printf (NULL,
+                                _("%s%s: CRC32 mismatch vs. %s on file %s"),
+                                weechat_prefix ("error"), XFER_PLUGIN_NAME,
+                                xfer->hash_target, xfer->filename);
+                xfer->hash_status = XFER_HASHING_MISMATCH;
+                break;
+            case XFER_ERROR_HASH_RESUME_ERROR:
+                weechat_printf (NULL,
+                                _("%s%s: CRC32 error while resuming, unable "
+                                  "to complete hash"),
+                                weechat_prefix ("error"), XFER_PLUGIN_NAME);
+                xfer->hash_status = XFER_HASHING_RESUME_ERROR;
+                break;
         }
 
         /* read new DCC status */
         switch (bufpipe[0] - '0')
         {
+            case XFER_STATUS_CONNECTING:
+                xfer->status = XFER_STATUS_CONNECTING;
+                xfer_buffer_refresh (WEECHAT_HOTLIST_MESSAGE);
+                break;
             case XFER_STATUS_ACTIVE:
                 if (xfer->status == XFER_STATUS_CONNECTING)
                 {
@@ -176,6 +194,17 @@ xfer_network_child_read_cb (void *arg_xfer, int fd)
                 xfer_close (xfer, XFER_STATUS_FAILED);
                 xfer_buffer_refresh (WEECHAT_HOTLIST_MESSAGE);
                 break;
+            case XFER_STATUS_HASHING:
+                xfer->status = XFER_STATUS_HASHING;
+                xfer_buffer_refresh (WEECHAT_HOTLIST_MESSAGE);
+                break;
+            case XFER_STATUS_HASHED:
+                if (bufpipe[1] - '0' == XFER_NO_ERROR)
+                {
+                    xfer->hash_status = XFER_HASHING_MATCH;
+                }
+                xfer_buffer_refresh (WEECHAT_HOTLIST_MESSAGE);
+                break;
         }
     }
 
diff --git a/src/plugins/xfer/xfer.c b/src/plugins/xfer/xfer.c
index c27e52e..5bfcf26 100644
--- a/src/plugins/xfer/xfer.c
+++ b/src/plugins/xfer/xfer.c
@@ -63,7 +63,7 @@ char *xfer_protocol_string[] =             /* strings for protocols         */
 char *xfer_status_string[] =               /* strings for status            */
 { N_("waiting"), N_("connecting"),
   N_("active"), N_("done"), N_("failed"),
-  N_("aborted")
+  N_("aborted"), N_("hashing")
 };
 
 struct t_xfer *xfer_list = NULL;       /* list of files/chats               */
@@ -72,6 +72,7 @@ int xfer_count = 0;                    /* number of xfer                    */
 
 int xfer_signal_upgrade_received = 0;  /* signal "upgrade" received ?       */
 
+regex_t *xfer_crc32_preg = NULL;       /* crc32 filename regex              */
 
 void xfer_disconnect_all ();
 
@@ -586,6 +587,47 @@ xfer_new (const char *plugin_name, const char *plugin_id,
     else
         xfer_file_find_filename (new_xfer);
 
+    new_xfer->hash_handle = NULL;
+    new_xfer->hash_target = NULL;
+    new_xfer->hash_status = XFER_HASHING_INVALID;
+
+    if ( type == XFER_TYPE_FILE_RECV
+         && weechat_config_boolean (xfer_config_file_auto_crc32)
+         && xfer_crc32_preg )
+    {
+        regmatch_t match;
+        int found = regexec(xfer_crc32_preg, new_xfer->filename,
+                            1, &match, 0);
+        if (found != REG_NOMATCH)
+        {
+            gcry_error_t err;
+            err = gcry_md_open(&(new_xfer->hash_handle), GCRY_MD_CRC32, 0);
+            if (err)
+            {
+                weechat_printf (NULL,
+                                _("%s%s: Hashing error: %s/%s"),
+                                weechat_prefix ("error"), XFER_PLUGIN_NAME,
+                                gcry_strsource (err), gcry_strerror(err));
+            } else {
+                /* The regex can match with the checksum at the
+                 * beginning of the filename. So we check if that's
+                 * the case. Then copy the checksum from the filename
+                 * to hash_target.
+                 */
+                regoff_t offset;
+                char c = new_xfer->filename[match.rm_so];
+                if ( ( c >= '0' && c <= '9' ) || ( c >= 'A' && c <= 'F' )
+                     || ( c>= 'a' && c <= 'f' ) )
+                    offset = match.rm_so;
+                else
+                    offset = match.rm_so + 1;
+                new_xfer->hash_target = weechat_strndup(new_xfer->filename
+                                                        + offset, 8);
+                new_xfer->hash_status = XFER_HASHING_UNDERWAY;
+            }
+        }
+    }
+
     /* write info message on core buffer */
     switch (type)
     {
@@ -731,6 +773,10 @@ xfer_free (struct t_xfer *xfer)
         free (xfer->unterminated_message);
     if (xfer->local_filename)
         free (xfer->local_filename);
+    if (xfer->hash_handle)
+        gcry_md_close(xfer->hash_handle);
+    if (xfer->hash_target)
+        free (xfer->hash_target);
 
     free (xfer);
 
@@ -1497,7 +1543,7 @@ xfer_debug_dump_cb (void *data, const char *signal, const char *type_data,
 int
 weechat_plugin_init (struct t_weechat_plugin *plugin, int argc, char *argv[])
 {
-    int i, upgrading;
+    int i, upgrading, re_error;
 
     weechat_plugin = plugin;
 
@@ -1534,6 +1580,27 @@ weechat_plugin_init (struct t_weechat_plugin *plugin, int argc, char *argv[])
     if (upgrading)
         xfer_upgrade_load ();
 
+    /* init filename crc32 regex */
+    xfer_crc32_preg = malloc(sizeof(regex_t));
+    if( (re_error = regcomp(xfer_crc32_preg, "([^0-9A-Z]|^)[0-9A-F]{8}([^0-9A-Z]|$)",
+                            REG_EXTENDED | REG_ICASE)) )
+    {
+        size_t size;
+        char *buf;
+
+        size = regerror(re_error, xfer_crc32_preg, NULL, 0);
+        buf = malloc(size);
+        regerror(re_error, xfer_crc32_preg, buf, size);
+        weechat_printf (NULL,
+                        "%s%s: Failed to compile regex crc32_preg: %s",
+                        weechat_prefix ("error"), XFER_PLUGIN_NAME,
+                        buf);
+
+        free(buf);
+        free(xfer_crc32_preg);
+        xfer_crc32_preg = NULL;
+    }
+
     return WEECHAT_RC_OK;
 }
 
@@ -1554,5 +1621,10 @@ weechat_plugin_end (struct t_weechat_plugin *plugin)
     else
         xfer_disconnect_all ();
 
+    if (xfer_crc32_preg) {
+        regfree(xfer_crc32_preg);
+        free(xfer_crc32_preg);
+    }
+
     return WEECHAT_RC_OK;
 }
diff --git a/src/plugins/xfer/xfer.h b/src/plugins/xfer/xfer.h
index a0773ed..24a4b77 100644
--- a/src/plugins/xfer/xfer.h
+++ b/src/plugins/xfer/xfer.h
@@ -21,6 +21,8 @@
 #define __WEECHAT_XFER_H 1
 
 #include <unistd.h>
+#include <gcrypt.h>
+#include <regex.h>
 
 #define weechat_plugin weechat_xfer_plugin
 #define XFER_PLUGIN_NAME "xfer"
@@ -57,6 +59,8 @@ enum t_xfer_status
     XFER_STATUS_DONE,                  /* transfer done                     */
     XFER_STATUS_FAILED,                /* transfer failed                   */
     XFER_STATUS_ABORTED,               /* transfer aborded by user          */
+    XFER_STATUS_HASHING,               /* the partial local file is being hashed */
+    XFER_STATUS_HASHED,                /* the received file has been hashed */
     /* number of xfer status */
     XFER_NUM_STATUS,
 };
@@ -75,10 +79,25 @@ enum t_xfer_error
     XFER_ERROR_RECV_BLOCK,             /* unable to recv block from sender  */
     XFER_ERROR_WRITE_LOCAL,            /* unable to write to local file     */
     XFER_ERROR_SEND_ACK,               /* unable to send ACK to sender      */
+    XFER_ERROR_HASH_MISMATCH,          /* CRC32 does not match              */
+    XFER_ERROR_HASH_RESUME_ERROR,      /* Other error with CRC32 hash       */
     /* number of errors */
     XFER_NUM_ERRORS,
 };
 
+/* hash status */
+
+enum t_hashing_status
+{
+    XFER_HASHING_INVALID = 0,          /* Hashing not being performed       */
+    XFER_HASHING_UNDERWAY,             /* Hash in progress                  */
+    XFER_HASHING_MATCH,                /* Hash finished and matches target  */
+    XFER_HASHING_MISMATCH,             /* Hash finished with mismatch       */
+    XFER_HASHING_RESUME_ERROR,         /* Hash failed while resuming        */
+    /* number of hash status */
+    XFER_NUM_HASHING,
+};
+
 /* xfer blocksize */
 
 #define XFER_BLOCKSIZE_MIN    1024     /* min blocksize                     */
@@ -153,6 +172,9 @@ struct t_xfer
     time_t last_activity;              /* time of last byte received/sent   */
     unsigned long long bytes_per_sec;  /* bytes per second                  */
     unsigned long long eta;            /* estimated time of arrival         */
+    gcry_md_hd_t hash_handle;          /* Handle for crc32 hash             */
+    char *hash_target;                 /* The crc32 hash to check against   */
+    enum t_hashing_status hash_status; /* Hash status (hashing, match, ...) */
     struct t_xfer *prev_xfer;          /* link to previous xfer             */
     struct t_xfer *next_xfer;          /* link to next xfer                 */
 };
@@ -163,6 +185,7 @@ extern char *xfer_protocol_string[];
 extern char *xfer_status_string[];
 extern struct t_xfer *xfer_list, *last_xfer;
 extern int xfer_count;
+extern regex_t *xfer_crc32_preg;
 
 extern int xfer_valid (struct t_xfer *xfer);
 extern struct t_xfer *xfer_search_by_number (int number);
-- 
1.8.1.4

