From 631669274078916a601ae1a4d56f41cb5236d842 Mon Sep 17 00:00:00 2001
From: Nikias Bassen <nikias@gmx.li>
Date: Mon, 4 Oct 2010 01:15:39 +0200
Subject: [PATCH 1/3] add hashAB support via external module
---
configure.ac | 9 ++
src/Makefile.am | 1 +
src/db-itunes-parser.h | 2 +
src/itdb_device.c | 1 +
src/itdb_hashAB.c | 196 ++++++++++++++++++++++++++++++++++++++++++++++++
src/itdb_itunesdb.c | 30 ++++++--
src/itdb_private.h | 8 ++
src/itdb_sqlite.c | 54 ++++++++++----
8 files changed, 278 insertions(+), 23 deletions(-)
create mode 100644 src/itdb_hashAB.c
diff --git a/configure.ac b/configure.ac
index 370a192..5858c57 100644
--- a/configure.ac
+++ b/configure.ac
@@ -217,6 +217,15 @@ AC_SUBST(TMPMOUNTDIR)
AH_TEMPLATE([TMPMOUNTDIR], [Directory where HAL/udev will create a sub-directory to mount iPods])
AC_DEFINE_UNQUOTED(TMPMOUNTDIR, "$with_temp_mount_dir", [Directory where HAL/udev will create a sub-directory to mount iPods])
+dnl ***********************************************************************
+dnl * provide a $l{ibdir}/libgpod directory for external modules
+dnl * available via config.h as LIBGPOD_LIBDIR
+dnl ***********************************************************************
+
+test "x${exec_prefix}" = "xNONE" && exec_prefix='${prefix}'
+LIBGPOD_LIBDIR=$(eval "echo \"$(eval "echo \"${libdir}\"")\"")/libgpod
+AC_DEFINE_UNQUOTED(LIBGPOD_LIBDIR,"$LIBGPOD_LIBDIR", [libgpod libdir])
+
dnl **************************************************
dnl * TagLib is only used by test-rebuild-db
dnl **************************************************
diff --git a/src/Makefile.am b/src/Makefile.am
index ce02831..1790d4f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -11,6 +11,7 @@ libgpod_la_SOURCES = \
itdb_device.c \
itdb_hash58.c \
itdb_hash72.c \
+ itdb_hashAB.c \
itdb_iphone.c \
itdb_itunesdb.c \
itdb_photoalbum.c \
diff --git a/src/db-itunes-parser.h b/src/db-itunes-parser.h
index cc71bcc..80d5213 100644
--- a/src/db-itunes-parser.h
+++ b/src/db-itunes-parser.h
@@ -102,6 +102,8 @@ struct _MhbdHeader {
guint16 unk_0xa4;
guint16 unk_0xa6;
guint16 unk_0xa8;
+ guchar align_0xa9;
+ guchar hashAB[57];
guchar padding[];
} __attribute__((__packed__));
diff --git a/src/itdb_device.c b/src/itdb_device.c
index fd256b4..bdc3dc3 100644
--- a/src/itdb_device.c
+++ b/src/itdb_device.c
@@ -1920,6 +1920,7 @@ G_GNUC_INTERNAL gboolean itdb_device_write_checksum (Itdb_Device *device,
case ITDB_CHECKSUM_HASH72:
return itdb_hash72_write_hash (device, itdb_data, itdb_len, error);
case ITDB_CHECKSUM_HASHAB:
+ return itdb_hashAB_write_hash (device, itdb_data, itdb_len, error);
case ITDB_CHECKSUM_UNKNOWN:
g_set_error (error, 0, -1, "Unsupported checksum type");
return FALSE;
diff --git a/src/itdb_hashAB.c b/src/itdb_hashAB.c
new file mode 100644
index 0000000..6b64adb
--- /dev/null
+++ b/src/itdb_hashAB.c
@@ -0,0 +1,196 @@
+/*
+| Copyright (c) 2010 Nikias Bassen <nikias@gmx.li>
+|
+| The code contained in this file is free software; you can redistribute
+| it and/or modify it under the terms of the GNU Lesser General Public
+| License as published by the Free Software Foundation; either version
+| 2.1 of the License, or (at your option) any later version.
+|
+| This file is distributed in the hope that it will be useful,
+| but WITHOUT ANY WARRANTY; without even the implied warranty of
+| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+| Lesser General Public License for more details.
+|
+| You should have received a copy of the GNU Lesser General Public
+| License along with this code; if not, write to the Free Software
+| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+| USA
+|
+| iTunes and iPod are trademarks of Apple
+|
+| This product is not supported/written/published by Apple!
+*/
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include "rijndael.h"
+
+#include <glib/gstdio.h>
+
+#include <gmodule.h>
+
+#include "itdb_device.h"
+#include "db-itunes-parser.h"
+#include "itdb_private.h"
+
+typedef void (*calcHashAB_t)(unsigned char target[57], const unsigned char sha1[20], const unsigned char uuid[20], const unsigned char rnd_bytes[23]);
+static calcHashAB_t calc_hashAB = NULL;
+
+static gboolean load_libhashab()
+{
+ gchar *path;
+ GModule *handle;
+
+ if (!g_module_supported()) {
+ return FALSE;
+ }
+ path = g_module_build_path(LIBGPOD_LIBDIR, "hashab");
+ handle = g_module_open(path, G_MODULE_BIND_LAZY);
+ if (!handle) {
+ g_free(path);
+ return FALSE;
+ }
+ g_free(path);
+
+ if (!g_module_symbol(handle, "calcHashAB", (void**)&calc_hashAB)) {
+ g_module_close(handle);
+ g_warning("symbol 'calcHashAB' not found");
+ return FALSE;
+ }
+ g_module_make_resident(handle);
+
+ printf("***** hashAB support successfully loaded *****\n");
+
+ return TRUE;
+}
+
+static void itdb_hashAB_compute_itunesdb_sha1 (unsigned char *itdb_data,
+ gsize itdb_len,
+ unsigned char sha1[20])
+{
+ guchar backup18[8];
+ guchar backup32[20];
+ guchar hashAB[57];
+ gsize sha1_len;
+ GChecksum *checksum;
+ MhbdHeader *header;
+
+ g_assert (itdb_len >= 0x6c);
+
+ header = (MhbdHeader *)itdb_data;
+ g_assert (strncmp (header->header_id, "mhbd", strlen ("mhbd")) == 0);
+ memcpy (backup18, &header->db_id, sizeof (backup18));
+ memcpy (backup32, &header->unk_0x32, sizeof (backup32));
+ memcpy (hashAB, &header->hashAB, sizeof (hashAB));
+
+ /* Those fields must be zero'ed out for the sha1 calculation */
+ memset(&header->db_id, 0, sizeof (header->db_id));
+
+ memset(&header->hash58, 0, sizeof (header->hash58));
+ memset(&header->hash72, 0, sizeof (header->hash72));
+ memset(&header->hashAB, 0, sizeof (header->hashAB));
+
+ sha1_len = g_checksum_type_get_length (G_CHECKSUM_SHA1);
+ checksum = g_checksum_new (G_CHECKSUM_SHA1);
+ g_checksum_update (checksum, itdb_data, itdb_len);
+ g_checksum_get_digest (checksum, sha1, &sha1_len);
+ g_checksum_free (checksum);
+
+ memcpy (&header->db_id, backup18, sizeof (backup18));
+ memcpy (&header->unk_0x32, backup32, sizeof (backup32));
+}
+
+static int ord_from_hex_char(const char c)
+{
+ if ('0' <= c && c <= '9')
+ return c - '0';
+ else if ('a' <= c && c <= 'f')
+ return 10 + (c - 'a');
+ else if ('A' <= c && c <= 'F')
+ return 10 + (c - 'A');
+ else
+ return -1;
+}
+
+static int string_to_hex(unsigned char *dest, const int array_size,
+ const char *s)
+{
+ /* skip optional '0x' prefix */
+ if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
+ s += 2;
+
+ if (strlen(s) > array_size*2)
+ return -8;
+
+ do {
+ int low, high;
+ if((high = ord_from_hex_char(s[0])) == -1 ||
+ (low = ord_from_hex_char(s[1])) == -1)
+ return -9;
+ *dest++ = high<<4 | low;
+ } while(*(s+=2));
+ return 0;
+}
+
+static gboolean get_uuid (const Itdb_Device *device, unsigned char uuid[20])
+{
+ const char *uuid_str;
+ int result;
+
+ uuid_str = itdb_device_get_uuid (device);
+ if (uuid_str == NULL) {
+ uuid_str = itdb_device_get_firewire_id (device);
+ }
+ if (uuid_str == NULL) {
+ return FALSE;
+ }
+ memset (uuid, 0, 20);
+ result = string_to_hex (uuid, 20, uuid_str);
+
+ return (result == 0);
+}
+
+gboolean itdb_hashAB_compute_hash_for_sha1 (const Itdb_Device *device,
+ const guchar sha1[20],
+ guchar signature[57],
+ GError **error)
+{
+ unsigned char uuid[20];
+ unsigned char rndpart[23] = "ABCDEFGHIJKLMNOPQRSTUVW";
+
+ if (calc_hashAB == NULL) {
+ if (!load_libhashab()) {
+ g_set_error (error, 0, -1, "Unsupported checksum type");
+ return FALSE;
+ }
+ }
+
+ if (!get_uuid(device, uuid)) return FALSE;
+
+ calc_hashAB(signature, sha1, uuid, rndpart);
+
+ return TRUE;
+}
+
+gboolean itdb_hashAB_write_hash (const Itdb_Device *device,
+ unsigned char *itdb_data,
+ gsize itdb_len,
+ GError **error)
+{
+ guchar sha1[20];
+ MhbdHeader *header;
+
+ if (itdb_len < 0xF4) {
+ g_set_error (error, 0, -1, "iTunesDB file too small to write checksum");
+ return FALSE;
+ }
+
+ header = (MhbdHeader *)itdb_data;
+ header->hashing_scheme = GUINT16_FROM_LE (ITDB_CHECKSUM_HASHAB);
+ itdb_hashAB_compute_itunesdb_sha1 (itdb_data, itdb_len, sha1);
+ return itdb_hashAB_compute_hash_for_sha1 (device, sha1, header->hashAB, error);
+}
diff --git a/src/itdb_itunesdb.c b/src/itdb_itunesdb.c
index 18fc780..bc55f4a 100644
--- a/src/itdb_itunesdb.c
+++ b/src/itdb_itunesdb.c
@@ -3731,7 +3731,7 @@ static void mk_mhbd (FExport *fexp, guint32 children)
cts = fexp->wcontents;
put_header (cts, "mhbd");
- put32lint (cts, 188); /* header size */
+ put32lint (cts, 244); /* header size */
put32lint (cts, -1); /* size of whole mhdb -- fill in later */
if (itdb_device_supports_compressed_itunesdb (fexp->itdb->device)) {
/* 2 on iPhone 3.0 and Nano 5G, 1 on iPod Color and iPod Classic
@@ -3760,10 +3760,12 @@ static void mk_mhbd (FExport *fexp, guint32 children)
0x19 = iTunes 7.4
0x28 = iTunes 8.2.1
0x2a = iTunes 9.0.1
+ 0x2e = iTunes 9.1
+ 0x30 = iTunes 9.2
Be aware that newer ipods won't work if the library version number is too
old
*/
- fexp->itdb->version = 0x2a;
+ fexp->itdb->version = 0x30;
put32lint (cts, fexp->itdb->version);
put32lint (cts, children);
put64lint (cts, fexp->itdb->id);
@@ -3787,20 +3789,32 @@ static void mk_mhbd (FExport *fexp, guint32 children)
/* seen: 0x01 for iPod Color */
put32lint (cts, fexp->itdb->priv->unk_0x54); /* unknown: seen: 0x4d for nano 3G */
/* seen: 0x0f for iPod Color */
- put32_n0 (cts, 5); /* 20 bytes hash */
+ put32_n0 (cts, 5); /* 20 bytes hash58 */
put32lint (cts, fexp->itdb->tzoffset); /* timezone offset in seconds */
/* 0x70 */
- put16lint (cts, 2); /* without it, iTunes thinks iPhone databases
- are corrupted, 0 on iPod Color */
- put16lint (cts, 0);
- put32_n0 (cts, 11); /* new hash */
+ switch (itdb_device_get_checksum_type(fexp->itdb->device)) {
+ case ITDB_CHECKSUM_HASHAB:
+ put16lint (cts, 4); /* new on i4 */
+ break;
+ case ITDB_CHECKSUM_HASH72:
+ put16lint (cts, 2);
+ break;
+ default:
+ put16lint (cts, 0);
+ break;
+ }
+ put16lint (cts, 0); /* hash72 */
+ put32_n0 (cts, 11); /* hash72 */
/* 0xa0 */
put16lint (cts, fexp->itdb->priv->audio_language); /* audio_language */
put16lint (cts, fexp->itdb->priv->subtitle_language); /* subtitle_language */
put16lint (cts, fexp->itdb->priv->unk_0xa4); /* unknown */
put16lint (cts, fexp->itdb->priv->unk_0xa6); /* unknown */
put16lint (cts, fexp->itdb->priv->unk_0xa8); /* unknown */
- put16lint (cts, 0);
+ put8int (cts, 0);
+ /* 0xab */
+ put8int (cts, 0); /* hashAB */
+ put32_n0 (cts, 14); /* hashAB */
put32_n0 (cts, 4); /* dummy space */
}
diff --git a/src/itdb_private.h b/src/itdb_private.h
index 7f78967..4eec250 100644
--- a/src/itdb_private.h
+++ b/src/itdb_private.h
@@ -229,6 +229,10 @@ G_GNUC_INTERNAL guint64 device_time_time_t_to_mac (Itdb_Device *device,
time_t timet);
G_GNUC_INTERNAL gint itdb_musicdirs_number_by_mountpoint (const gchar *mountpoint);
G_GNUC_INTERNAL int itdb_sqlite_generate_itdbs(FExport *fexp);
+G_GNUC_INTERNAL gboolean itdb_hashAB_write_hash (const Itdb_Device *device,
+ unsigned char *itdb_data,
+ gsize itdb_len,
+ GError **error);
G_GNUC_INTERNAL gboolean itdb_hash72_extract_hash_info(const Itdb_Device *device,
unsigned char *itdb_data,
gsize itdb_len);
@@ -240,6 +244,10 @@ G_GNUC_INTERNAL gboolean itdb_hash58_write_hash (Itdb_Device *device,
unsigned char *itdb_data,
gsize itdb_len,
GError **error);
+G_GNUC_INTERNAL gboolean itdb_hashAB_compute_hash_for_sha1 (const Itdb_Device *device,
+ const guchar sha1[20],
+ guchar signature[57],
+ GError **error);
G_GNUC_INTERNAL gboolean itdb_hash72_compute_hash_for_sha1 (const Itdb_Device *device,
const guchar sha1[20],
guchar signature[46],
diff --git a/src/itdb_sqlite.c b/src/itdb_sqlite.c
index b107e4e..974aa6c 100644
--- a/src/itdb_sqlite.c
+++ b/src/itdb_sqlite.c
@@ -1860,24 +1860,22 @@ error:
return FALSE;
}
-static const guint CBK_HEADER_SIZE = 46;
-
-static void cbk_calc_sha1_of_sha1s(GArray *cbk)
+static void cbk_calc_sha1_of_sha1s(GArray *cbk, guint cbk_header_size)
{
GChecksum *checksum;
unsigned char* final_sha1;
unsigned char* sha1s;
gsize final_sha1_len;
- g_assert (cbk->len > CBK_HEADER_SIZE + 20);
+ g_assert (cbk->len > cbk_header_size + 20);
- final_sha1 = &g_array_index(cbk, guchar, CBK_HEADER_SIZE);
- sha1s = &g_array_index(cbk, guchar, CBK_HEADER_SIZE + 20);
+ final_sha1 = &g_array_index(cbk, guchar, cbk_header_size);
+ sha1s = &g_array_index(cbk, guchar, cbk_header_size + 20);
final_sha1_len = g_checksum_type_get_length(G_CHECKSUM_SHA1);
g_assert (final_sha1_len == 20);
checksum = g_checksum_new(G_CHECKSUM_SHA1);
- g_checksum_update(checksum, sha1s, cbk->len - (CBK_HEADER_SIZE + 20));
+ g_checksum_update(checksum, sha1s, cbk->len - (cbk_header_size + 20));
g_checksum_get_digest(checksum, final_sha1, &final_sha1_len);
g_checksum_free(checksum);
}
@@ -1888,12 +1886,30 @@ static gboolean mk_Locations_cbk(Itdb_iTunesDB *itdb, const char *dirname)
char *cbk_filename;
GArray *cbk;
gboolean success;
- guchar *cbk_hash72;
+ guchar *cbk_hash;
guchar *final_sha1;
+ guint cbk_header_size = 0;
+ guint checksum_type;
+
+ checksum_type = itdb_device_get_checksum_type(itdb->device);
+ switch (checksum_type) {
+ case ITDB_CHECKSUM_HASHAB:
+ cbk_header_size = 57;
+ break;
+ case ITDB_CHECKSUM_HASH72:
+ cbk_header_size = 46;
+ break;
+ default:
+ break;
+ }
+ if (cbk_header_size == 0) {
+ fprintf(stderr, "ERROR: Unsupported checksum type '%d' in cbk file generation!\n", checksum_type);
+ return FALSE;
+ }
cbk = g_array_sized_new(FALSE, TRUE, 1,
- CBK_HEADER_SIZE + 20);
- g_array_set_size(cbk, CBK_HEADER_SIZE + 20);
+ cbk_header_size + 20);
+ g_array_set_size(cbk, cbk_header_size + 20);
locations_filename = g_build_filename(dirname, "Locations.itdb", NULL);
success = cbk_calc_sha1s(locations_filename, cbk);
@@ -1902,11 +1918,19 @@ static gboolean mk_Locations_cbk(Itdb_iTunesDB *itdb, const char *dirname)
g_array_free(cbk, TRUE);
return FALSE;
}
- cbk_calc_sha1_of_sha1s(cbk);
- final_sha1 = &g_array_index(cbk, guchar, CBK_HEADER_SIZE);
- cbk_hash72 = &g_array_index(cbk, guchar, 0);
- success = itdb_hash72_compute_hash_for_sha1 (itdb->device, final_sha1,
- cbk_hash72, NULL);
+ cbk_calc_sha1_of_sha1s(cbk, cbk_header_size);
+ final_sha1 = &g_array_index(cbk, guchar, cbk_header_size);
+ cbk_hash = &g_array_index(cbk, guchar, 0);
+ switch (checksum_type) {
+ case ITDB_CHECKSUM_HASHAB:
+ success = itdb_hashAB_compute_hash_for_sha1 (itdb->device, final_sha1, cbk_hash, NULL);
+ break;
+ case ITDB_CHECKSUM_HASH72:
+ success = itdb_hash72_compute_hash_for_sha1 (itdb->device, final_sha1, cbk_hash, NULL);
+ break;
+ default:
+ break;
+ }
if (!success) {
g_array_free(cbk, TRUE);
return FALSE;
--
1.7.3.2