aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVsevolod Stakhov <vsevolod@highsecure.ru>2016-07-04 19:13:03 +0100
committerVsevolod Stakhov <vsevolod@highsecure.ru>2016-07-04 19:13:03 +0100
commit17f8e21b65a52aa9fcb9ac84829996c36b8809e9 (patch)
tree4663390521b43595d85b451bf3a527d79b28fc2e
parentf5aac8de010eaf3221dd152e98a5f1a5208ecb2b (diff)
downloadrspamd-17f8e21b65a52aa9fcb9ac84829996c36b8809e9.tar.gz
rspamd-17f8e21b65a52aa9fcb9ac84829996c36b8809e9.zip
[Feature] Add preliminary rarv5 support
-rw-r--r--src/libmime/archives.c234
-rw-r--r--src/libmime/archives.h5
2 files changed, 234 insertions, 5 deletions
diff --git a/src/libmime/archives.c b/src/libmime/archives.c
index 99b004afa..98a15070f 100644
--- a/src/libmime/archives.c
+++ b/src/libmime/archives.c
@@ -75,13 +75,13 @@ rspamd_archive_process_zip (struct rspamd_task *task,
if (eocd == NULL) {
/* Not a zip file */
- msg_debug_task ("zip archive is invalid (no EOCD): %s", part->boundary);
+ msg_debug_task ("zip archive is invalid (no EOCD): %s", part->filename);
return;
}
if (end - eocd < 21) {
- msg_debug_task ("zip archive is invalid (short EOCD): %s", part->boundary);
+ msg_debug_task ("zip archive is invalid (short EOCD): %s", part->filename);
return;
}
@@ -95,7 +95,7 @@ rspamd_archive_process_zip (struct rspamd_task *task,
/* We need to check sanity as well */
if (cd_offset + cd_size != (guint)(eocd - start)) {
msg_debug_task ("zip archive is invalid (bad size/offset for CD): %s",
- part->boundary);
+ part->filename);
return;
}
@@ -113,7 +113,7 @@ rspamd_archive_process_zip (struct rspamd_task *task,
if (eocd - cd < cd_basic_len ||
memcmp (cd, cd_magic, sizeof (cd_magic)) != 0) {
msg_debug_task ("zip archive is invalid (bad cd record): %s",
- part->boundary);
+ part->filename);
return;
}
@@ -127,7 +127,7 @@ rspamd_archive_process_zip (struct rspamd_task *task,
if (cd + fname_len + comment_len + extra_len + cd_basic_len > eocd) {
msg_debug_task ("zip archive is invalid (too large cd record): %s",
- part->boundary);
+ part->filename);
return;
}
@@ -145,6 +145,224 @@ rspamd_archive_process_zip (struct rspamd_task *task,
arch->size = part->content->len;
}
+static inline gint
+rspamd_archive_rar_read_vint (const guchar *start, gsize remain, guint64 *res)
+{
+ /*
+ * From http://www.rarlab.com/technote.htm:
+ * Variable length integer. Can include one or more bytes, where
+ * lower 7 bits of every byte contain integer data and highest bit
+ * in every byte is the continuation flag.
+ * If highest bit is 0, this is the last byte in sequence.
+ * So first byte contains 7 least significant bits of integer and
+ * continuation flag. Second byte, if present, contains next 7 bits and so on.
+ */
+ guint64 t = 0;
+ guint shift = 0;
+ const guchar *p = start;
+
+ while (remain > 0 && shift <= 57) {
+ if (*p & 0x80) {
+ t |= (*p & 0x7f) << shift;
+ }
+ else {
+ t |= (*p & 0x7f) << shift;
+ break;
+ }
+
+ shift += 7;
+ p++;
+ remain --;
+ }
+
+ if (remain == 0 || shift > 64) {
+ return -1;
+ }
+
+ *res = GUINT64_FROM_LE (t);
+
+ return p - start;
+}
+
+#define RAR_SKIP_BYTES(n) do { \
+ if ((n) <= 0) { \
+ msg_debug_task ("rar archive is invalid (bad skip value): %s", part->filename); \
+ return; \
+ } \
+ if ((gsize)(end - p) < (n)) { \
+ msg_debug_task ("rar archive is invalid (truncated): %s", part->filename); \
+ return; \
+ } \
+ p += (n); \
+} while (0)
+
+#define RAR_READ_VINT() do { \
+ r = rspamd_archive_rar_read_vint (p, end - p, &vint); \
+ if (r == -1) { \
+ msg_debug_task ("rar archive is invalid (bad vint): %s", part->filename); \
+ return; \
+ } \
+ else if (r == 0) { \
+ msg_debug_task ("rar archive is invalid (BAD vint offset): %s", part->filename); \
+ return; \
+ }\
+} while (0)
+
+#define RAR_READ_VINT_SKIP() do { \
+ r = rspamd_archive_rar_read_vint (p, end - p, &vint); \
+ if (r == -1) { \
+ msg_debug_task ("rar archive is invalid (bad vint): %s", part->filename); \
+ return; \
+ } \
+ p += r; \
+} while (0)
+
+static void
+rspamd_archive_process_rar (struct rspamd_task *task,
+ struct rspamd_mime_part *part)
+{
+ const guchar *p, *end;
+ const guchar rar_v5_magic[] = {0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x01, 0x00},
+ rar_v4_magic[] = {0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00};
+ const guint rar_encrypted_header = 4, rar_main_header = 1,
+ rar_file_header = 2;
+ guint64 vint, sz;
+ struct rspamd_archive *arch;
+ gint r;
+
+ p = part->content->data;
+ end = p + part->content->len;
+
+ if ((gsize)(end - p) <= sizeof (rar_v5_magic)) {
+ msg_debug_task ("rar archive is invalid (too small): %s", part->filename);
+
+ return;
+ }
+
+ if (memcmp (p, rar_v5_magic, sizeof (rar_v5_magic)) == 0) {
+ p += sizeof (rar_v5_magic);
+ }
+ else if (memcmp (p, rar_v4_magic, sizeof (rar_v4_magic)) == 0) {
+ p += sizeof (rar_v4_magic);
+ }
+ else {
+ msg_debug_task ("rar archive is invalid (no rar magic): %s", part->filename);
+
+ return;
+ }
+
+ arch = rspamd_mempool_alloc0 (task->task_pool, sizeof (*arch));
+ arch->files = g_ptr_array_new ();
+ arch->type = RSPAMD_ARCHIVE_RAR;
+ rspamd_mempool_add_destructor (task->task_pool, rspamd_archive_dtor,
+ arch);
+
+ /* Now we can have either encryption header or archive header */
+ /* Crc 32 */
+ RAR_SKIP_BYTES (sizeof (guint32));
+ /* Size */
+ RAR_READ_VINT_SKIP ();
+ sz = vint;
+ /* Type (not skip) */
+ RAR_READ_VINT ();
+
+ if (vint == rar_encrypted_header) {
+ /* We can't read any further information as archive is encrypted */
+ arch->flags |= RSPAMD_ARCHIVE_ENCRYPTED;
+ goto end;
+ }
+ else if (vint != rar_main_header) {
+ msg_debug_task ("rar archive is invalid (bad main header): %s", part->filename);
+
+ return;
+ }
+
+ /* Nothing useful in main header */
+ RAR_SKIP_BYTES (sz);
+
+ while (p < end) {
+ /* Read the next header */
+ /* Crc 32 */
+ RAR_SKIP_BYTES (sizeof (guint32));
+ /* Size */
+ RAR_READ_VINT_SKIP ();
+ sz = vint;
+ /* Type (not skip) */
+ RAR_READ_VINT ();
+
+ if (vint != rar_file_header) {
+ RAR_SKIP_BYTES (sz);
+ }
+ else {
+ /* We have a file header, go forward */
+ const guchar *section_type_start = p;
+ guint64 flags, fname_len;
+ GString *s;
+
+ p += r; /* Remain from type */
+ /* Header flags */
+ RAR_READ_VINT_SKIP ();
+ flags = vint;
+
+ /* Now we have two optional fields (fuck rar!) */
+ if (flags & 0x1) {
+ /* Extra flag */
+ RAR_READ_VINT_SKIP ();
+ }
+ if (flags & 0x2) {
+ /* Data size */
+ RAR_READ_VINT_SKIP ();
+ }
+
+ /* File flags */
+ RAR_READ_VINT_SKIP ();
+
+ flags = vint;
+
+ /* Unpacked size */
+ RAR_READ_VINT_SKIP ();
+ /* Attributes */
+ RAR_READ_VINT_SKIP ();
+
+ if (flags & 0x2) {
+ /* Unix mtime */
+ RAR_SKIP_BYTES (sizeof (guint32));
+ }
+ if (flags & 0x4) {
+ /* Crc32 */
+ RAR_SKIP_BYTES (sizeof (guint32));
+ }
+
+ /* Compression */
+ RAR_READ_VINT_SKIP ();
+ /* Host OS */
+ RAR_READ_VINT_SKIP ();
+ /* Filename length (finally!) */
+ RAR_READ_VINT_SKIP ();
+ fname_len = vint;
+
+ if (fname_len == 0 || fname_len > (gsize)(end - p)) {
+ msg_debug_task ("rar archive is invalid (bad fileame size): %s", part->filename);
+
+ return;
+ }
+
+ s = g_string_new_len (p, fname_len);
+ g_ptr_array_add (arch->files, s);
+ /* Restore p to the beginning of the header */
+ p = section_type_start;
+ RAR_SKIP_BYTES (sz);
+ }
+ }
+
+ return;
+end:
+ part->flags |= RSPAMD_MIME_PART_ARCHIVE;
+ part->specific_data = arch;
+ arch->archive_name = part->filename;
+ arch->size = part->content->len;
+}
+
static gboolean
rspamd_archive_cheat_detect (struct rspamd_mime_part *part, const gchar *str)
{
@@ -189,6 +407,9 @@ rspamd_archives_process (struct rspamd_task *task)
if (rspamd_archive_cheat_detect (part, "zip")) {
rspamd_archive_process_zip (task, part);
}
+ else if (rspamd_archive_cheat_detect (part, "rar")) {
+ rspamd_archive_process_rar (task, part);
+ }
}
}
}
@@ -203,6 +424,9 @@ rspamd_archive_type_str (enum rspamd_archive_type type)
case RSPAMD_ARCHIVE_ZIP:
ret = "zip";
break;
+ case RSPAMD_ARCHIVE_RAR:
+ ret = "rar";
+ break;
}
return ret;
diff --git a/src/libmime/archives.h b/src/libmime/archives.h
index 3e8c8a46c..87caeced1 100644
--- a/src/libmime/archives.h
+++ b/src/libmime/archives.h
@@ -20,13 +20,18 @@
enum rspamd_archive_type {
RSPAMD_ARCHIVE_ZIP,
+ RSPAMD_ARCHIVE_RAR,
};
+enum rspamd_archive_flags {
+ RSPAMD_ARCHIVE_ENCRYPTED = (1 << 0),
+};
struct rspamd_archive {
enum rspamd_archive_type type;
const gchar *archive_name;
gsize size;
+ enum rspamd_archive_flags flags;
GPtrArray *files; /* Array of GStrings */
};