aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames Ahlborn <jtahlborn@yahoo.com>2010-10-16 02:47:46 +0000
committerJames Ahlborn <jtahlborn@yahoo.com>2010-10-16 02:47:46 +0000
commitb512854062133c4d6ecaa373eefc809830137fd3 (patch)
treebfae64f6560dfd092ffa918e9370d60056d6c241
parent6b500b7dbf54df647fad11bf640785e914e9e5c5 (diff)
downloadjackcess-b512854062133c4d6ecaa373eefc809830137fd3.tar.gz
jackcess-b512854062133c4d6ecaa373eefc809830137fd3.zip
add support for de-masking database header and reading database password
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@486 f203690c-595d-4dc9-a70b-905162fa7fd2
-rw-r--r--src/java/com/healthmarketscience/jackcess/Database.java61
-rw-r--r--src/java/com/healthmarketscience/jackcess/JetFormat.java66
-rw-r--r--src/java/com/healthmarketscience/jackcess/PageChannel.java43
3 files changed, 162 insertions, 8 deletions
diff --git a/src/java/com/healthmarketscience/jackcess/Database.java b/src/java/com/healthmarketscience/jackcess/Database.java
index 215d446..2555a7d 100644
--- a/src/java/com/healthmarketscience/jackcess/Database.java
+++ b/src/java/com/healthmarketscience/jackcess/Database.java
@@ -1024,6 +1024,67 @@ public class Database
}
/**
+ * @return the current database password, or {@code null} if none set.
+ */
+ public String getDatabasePassword() throws IOException
+ {
+ _pageChannel.readPage(_buffer, 0);
+
+ byte[] pwdBytes = new byte[_format.SIZE_PASSWORD];
+ _buffer.position(_format.OFFSET_PASSWORD);
+ _buffer.get(pwdBytes);
+
+ // de-mask password
+ applyPasswordMask(pwdBytes);
+
+ boolean hasPassword = false;
+ for(int i = 0; i < pwdBytes.length; ++i) {
+ if(pwdBytes[i] != 0) {
+ hasPassword = true;
+ break;
+ }
+ }
+
+ if(!hasPassword) {
+ return null;
+ }
+
+ String pwd = Column.decodeUncompressedText(pwdBytes, getCharset());
+
+ // remove any trailing null chars
+ int idx = pwd.indexOf('\0');
+ if(idx >= 0) {
+ pwd = pwd.substring(0, idx);
+ }
+
+ return pwd;
+ }
+
+ /**
+ * Applies an extra mask to the password if required for the current
+ * JetFormat.
+ */
+ private void applyPasswordMask(byte[] pwdBytes)
+ {
+ // apply extra password mask if necessary (the extra password mask is
+ // generated from the database creation date stored in the header)
+ int pwdMaskPos = _format.OFFSET_HEADER_DATE;
+ if(pwdMaskPos >= 0) {
+
+ _buffer.position(pwdMaskPos);
+ double dateVal = Double.longBitsToDouble(_buffer.getLong());
+
+ byte[] pwdMask = new byte[4];
+ ByteBuffer.wrap(pwdMask).order(PageChannel.DEFAULT_BYTE_ORDER)
+ .putInt((int)dateVal);
+
+ for(int i = 0; i < pwdBytes.length; ++i) {
+ pwdBytes[i] ^= pwdMask[i % pwdMask.length];
+ }
+ }
+ }
+
+ /**
* Finds the relationships matching the given from and to tables from the
* given cursor and adds them to the given list.
*/
diff --git a/src/java/com/healthmarketscience/jackcess/JetFormat.java b/src/java/com/healthmarketscience/jackcess/JetFormat.java
index 8041dbb..4beb69e 100644
--- a/src/java/com/healthmarketscience/jackcess/JetFormat.java
+++ b/src/java/com/healthmarketscience/jackcess/JetFormat.java
@@ -59,6 +59,31 @@ public abstract class JetFormat {
/** Version code for Jet version 5 */
private static final byte CODE_VERSION_5 = 0x2;
+ /** mask used to obfuscate the db header */
+ private static final byte[] BASE_HEADER_MASK = new byte[]{
+ (byte)0xB5, (byte)0x6F, (byte)0x03, (byte)0x62, (byte)0x61, (byte)0x08,
+ (byte)0xC2, (byte)0x55, (byte)0xEB, (byte)0xA9, (byte)0x67, (byte)0x72,
+ (byte)0x43, (byte)0x3F, (byte)0x00, (byte)0x9C, (byte)0x7A, (byte)0x9F,
+ (byte)0x90, (byte)0xFF, (byte)0x80, (byte)0x9A, (byte)0x31, (byte)0xC5,
+ (byte)0x79, (byte)0xBA, (byte)0xED, (byte)0x30, (byte)0xBC, (byte)0xDF,
+ (byte)0xCC, (byte)0x9D, (byte)0x63, (byte)0xD9, (byte)0xE4, (byte)0xC3,
+ (byte)0x7B, (byte)0x42, (byte)0xFB, (byte)0x8A, (byte)0xBC, (byte)0x4E,
+ (byte)0x86, (byte)0xFB, (byte)0xEC, (byte)0x37, (byte)0x5D, (byte)0x44,
+ (byte)0x9C, (byte)0xFA, (byte)0xC6, (byte)0x5E, (byte)0x28, (byte)0xE6,
+ (byte)0x13, (byte)0xB6, (byte)0x8A, (byte)0x60, (byte)0x54, (byte)0x94,
+ (byte)0x7B, (byte)0x36, (byte)0xF5, (byte)0x72, (byte)0xDF, (byte)0xB1,
+ (byte)0x77, (byte)0xF4, (byte)0x13, (byte)0x43, (byte)0xCF, (byte)0xAF,
+ (byte)0xB1, (byte)0x33, (byte)0x34, (byte)0x61, (byte)0x79, (byte)0x5B,
+ (byte)0x92, (byte)0xB5, (byte)0x7C, (byte)0x2A, (byte)0x05, (byte)0xF1,
+ (byte)0x7C, (byte)0x99, (byte)0x01, (byte)0x1B, (byte)0x98, (byte)0xFD,
+ (byte)0x12, (byte)0x4F, (byte)0x4A, (byte)0x94, (byte)0x6C, (byte)0x3E,
+ (byte)0x60, (byte)0x26, (byte)0x5F, (byte)0x95, (byte)0xF8, (byte)0xD0,
+ (byte)0x89, (byte)0x24, (byte)0x85, (byte)0x67, (byte)0xC6, (byte)0x1F,
+ (byte)0x27, (byte)0x44, (byte)0xD2, (byte)0xEE, (byte)0xCF, (byte)0x65,
+ (byte)0xED, (byte)0xFF, (byte)0x07, (byte)0xC7, (byte)0x46, (byte)0xA1,
+ (byte)0x78, (byte)0x16, (byte)0x0C, (byte)0xED, (byte)0xE9, (byte)0x2D,
+ (byte)0x62, (byte)0xD4};
+
/** value of the "AccessVersion" property for access 2000 dbs:
{@code "08.50"} */
private static final byte[] ACCESS_VERSION_2000 = new byte[] {
@@ -104,7 +129,12 @@ public abstract class JetFormat {
public final int MAX_ROW_SIZE;
public final int PAGE_INITIAL_FREE_SPACE;
-
+
+ public final int OFFSET_MASKED_HEADER;
+ public final byte[] HEADER_MASK;
+ public final int OFFSET_HEADER_DATE;
+ public final int OFFSET_PASSWORD;
+ public final int SIZE_PASSWORD;
public final int OFFSET_NEXT_TABLE_DEF_PAGE;
public final int OFFSET_NUM_ROWS;
public final int OFFSET_NEXT_AUTO_NUMBER;
@@ -221,6 +251,11 @@ public abstract class JetFormat {
MAX_ROW_SIZE = defineMaxRowSize();
PAGE_INITIAL_FREE_SPACE = definePageInitialFreeSpace();
+ OFFSET_MASKED_HEADER = defineOffsetMaskedHeader();
+ HEADER_MASK = defineHeaderMask();
+ OFFSET_HEADER_DATE = defineOffsetHeaderDate();
+ OFFSET_PASSWORD = defineOffsetPassword();
+ SIZE_PASSWORD = defineSizePassword();
OFFSET_NEXT_TABLE_DEF_PAGE = defineOffsetNextTableDefPage();
OFFSET_NUM_ROWS = defineOffsetNumRows();
OFFSET_NEXT_AUTO_NUMBER = defineOffsetNextAutoNumber();
@@ -306,6 +341,11 @@ public abstract class JetFormat {
protected abstract int defineMaxRowSize();
protected abstract int definePageInitialFreeSpace();
+ protected abstract int defineOffsetMaskedHeader();
+ protected abstract byte[] defineHeaderMask();
+ protected abstract int defineOffsetHeaderDate();
+ protected abstract int defineOffsetPassword();
+ protected abstract int defineSizePassword();
protected abstract int defineOffsetNextTableDefPage();
protected abstract int defineOffsetNumRows();
protected abstract int defineOffsetNextAutoNumber();
@@ -414,6 +454,20 @@ public abstract class JetFormat {
protected int definePageInitialFreeSpace() { return PAGE_SIZE - 14; }
@Override
+ protected int defineOffsetMaskedHeader() { return 24; }
+ @Override
+ protected byte[] defineHeaderMask() {
+ byte[] mask = new byte[BASE_HEADER_MASK.length - 2];
+ System.arraycopy(BASE_HEADER_MASK, 0, mask, 0, mask.length);
+ return mask;
+ }
+ @Override
+ protected int defineOffsetHeaderDate() { return -1; }
+ @Override
+ protected int defineOffsetPassword() { return 66; }
+ @Override
+ protected int defineSizePassword() { return 20; }
+ @Override
protected int defineOffsetNextTableDefPage() { return 4; }
@Override
protected int defineOffsetNumRows() { return 12; }
@@ -590,6 +644,16 @@ public abstract class JetFormat {
protected int definePageInitialFreeSpace() { return PAGE_SIZE - 14; }
@Override
+ protected int defineOffsetMaskedHeader() { return 24; }
+ @Override
+ protected byte[] defineHeaderMask() { return BASE_HEADER_MASK; }
+ @Override
+ protected int defineOffsetHeaderDate() { return 114; }
+ @Override
+ protected int defineOffsetPassword() { return 66; }
+ @Override
+ protected int defineSizePassword() { return 40; }
+ @Override
protected int defineOffsetNextTableDefPage() { return 4; }
@Override
protected int defineOffsetNumRows() { return 16; }
diff --git a/src/java/com/healthmarketscience/jackcess/PageChannel.java b/src/java/com/healthmarketscience/jackcess/PageChannel.java
index e72bcd7..92c573c 100644
--- a/src/java/com/healthmarketscience/jackcess/PageChannel.java
+++ b/src/java/com/healthmarketscience/jackcess/PageChannel.java
@@ -161,6 +161,11 @@ public class PageChannel implements Channel, Flushable {
getFormat().PAGE_SIZE + " bytes from page " +
pageNumber + ", only read " + bytesRead);
}
+
+ if(pageNumber == 0) {
+ // de-mask header
+ applyHeaderMask(buffer);
+ }
}
/**
@@ -192,10 +197,21 @@ public class PageChannel implements Channel, Flushable {
"Page buffer is too large, size " + (page.remaining() - pageOffset));
}
- page.position(pageOffset);
- _channel.write(page, (getPageOffset(pageNumber) + pageOffset));
- if(_autoSync) {
- flush();
+ if(pageNumber == 0) {
+ // re-mask header
+ applyHeaderMask(page);
+ }
+ try {
+ page.position(pageOffset);
+ _channel.write(page, (getPageOffset(pageNumber) + pageOffset));
+ if(_autoSync) {
+ flush();
+ }
+ } finally {
+ if(pageNumber == 0) {
+ // de-mask header
+ applyHeaderMask(page);
+ }
}
}
@@ -265,11 +281,11 @@ public class PageChannel implements Channel, Flushable {
}
/**
- * @return A newly-allocated buffer of the given size and LITTLE_ENDIAN byte
- * order
+ * @return A newly-allocated buffer of the given size and DEFAULT_BYTE_ORDER
+ * byte order
*/
public ByteBuffer createBuffer(int size) {
- return createBuffer(size, ByteOrder.LITTLE_ENDIAN);
+ return createBuffer(size, DEFAULT_BYTE_ORDER);
}
/**
@@ -295,6 +311,19 @@ public class PageChannel implements Channel, Flushable {
}
/**
+ * Applies the XOR mask to the database header in the given buffer.
+ */
+ private void applyHeaderMask(ByteBuffer buffer) {
+ // de/re-obfuscate the header
+ byte[] headerMask = _format.HEADER_MASK;
+ for(int idx = 0; idx < headerMask.length; ++idx) {
+ int pos = idx + _format.OFFSET_MASKED_HEADER;
+ byte b = (byte)(buffer.get(pos) ^ headerMask[idx]);
+ buffer.put(pos, b);
+ }
+ }
+
+ /**
* @return a duplicate of the current buffer narrowed to the given position
* and limit. mark will be set at the current position.
*/