diff options
author | James Ahlborn <jtahlborn@yahoo.com> | 2010-10-16 02:47:46 +0000 |
---|---|---|
committer | James Ahlborn <jtahlborn@yahoo.com> | 2010-10-16 02:47:46 +0000 |
commit | b512854062133c4d6ecaa373eefc809830137fd3 (patch) | |
tree | bfae64f6560dfd092ffa918e9370d60056d6c241 | |
parent | 6b500b7dbf54df647fad11bf640785e914e9e5c5 (diff) | |
download | jackcess-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
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. */ |