|
|
|
/***************************************************************************
|
|
|
|
* Copyright (C) 2005-2008 by Tarek Saidi *
|
|
|
|
* tarek.saidi@arcor.de *
|
|
|
|
* *
|
|
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
|
|
* it under the terms of the GNU General Public License as published by *
|
|
|
|
* the Free Software Foundation; version 2 of the License. *
|
|
|
|
* *
|
|
|
|
* This program 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 General Public License for more details. *
|
|
|
|
* *
|
|
|
|
* You should have received a copy of the GNU General Public License *
|
|
|
|
* along with this program; if not, write to the *
|
|
|
|
* Free Software Foundation, Inc., *
|
|
|
|
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
|
|
***************************************************************************/
|
|
|
|
|
|
|
|
#include "Kdb3Database.h"
|
|
|
|
|
|
|
|
#include "crypto/twoclass.h"
|
|
|
|
#include <QBuffer>
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
#define UNEXP_ERROR error=QString("Unexpected error in: %1, Line:%2").arg(__FILE__).arg(__LINE__);
|
|
|
|
|
|
|
|
const QDateTime Date_Never(QDate(2999,12,28),QTime(23,59,59));
|
|
|
|
|
|
|
|
|
|
|
|
bool Kdb3Database::EntryHandleLessThan(const IEntryHandle* This,const IEntryHandle* Other){
|
|
|
|
if(!This->isValid() && Other->isValid())return true;
|
|
|
|
if(This->isValid() && !Other->isValid())return false;
|
|
|
|
if(!This->isValid() && !Other->isValid())return false;
|
|
|
|
return This->visualIndex()<Other->visualIndex();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Kdb3Database::EntryHandleLessThanStd(const IEntryHandle* This,const IEntryHandle* Other){
|
|
|
|
int comp = This->title().compare(Other->title());
|
|
|
|
if (comp < 0) return true;
|
|
|
|
else if (comp > 0) return false;
|
|
|
|
|
|
|
|
comp = This->username().compare(Other->username());
|
|
|
|
if (comp < 0) return true;
|
|
|
|
else if (comp > 0) return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Kdb3Database::StdEntryLessThan(const Kdb3Database::StdEntry& This,const Kdb3Database::StdEntry& Other){
|
|
|
|
return This.Index<Other.Index;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Kdb3Database::Kdb3Database() : File(NULL), RawMasterKey(32), RawMasterKey_CP1252(32),
|
|
|
|
RawMasterKey_Latin1(32), RawMasterKey_UTF8(32), MasterKey(32){
|
|
|
|
}
|
|
|
|
|
|
|
|
QString Kdb3Database::getError(){
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Kdb3Database::addIcon(const QPixmap& icon){
|
|
|
|
CustomIcons << icon;
|
|
|
|
emit iconsModified();
|
|
|
|
}
|
|
|
|
|
|
|
|
QPixmap& Kdb3Database::icon(int i){
|
|
|
|
if(i>=builtinIcons()+CustomIcons.size())
|
|
|
|
return EntryIcons[0];
|
|
|
|
if(i<builtinIcons())
|
|
|
|
return EntryIcons[i];
|
|
|
|
return CustomIcons[i-builtinIcons()];
|
|
|
|
}
|
|
|
|
|
|
|
|
void Kdb3Database::removeIcon(int id){
|
|
|
|
id-=builtinIcons();
|
|
|
|
if(id < 0 ) return;
|
|
|
|
if(id >= CustomIcons.size()) return;
|
|
|
|
CustomIcons.removeAt(id); // .isNull()==true
|
|
|
|
for(int i=0;i<Entries.size();i++){
|
|
|
|
if(Entries[i].Image == id+builtinIcons())
|
|
|
|
Entries[i].Image=0;
|
|
|
|
if(Entries[i].Image>id+builtinIcons())
|
|
|
|
Entries[i].Image--;
|
|
|
|
}
|
|
|
|
for(int i=0;i<Groups.size();i++){
|
|
|
|
if(Groups[i].Image == id+builtinIcons())
|
|
|
|
Groups[i].Image=0;
|
|
|
|
if(Groups[i].Image>id+builtinIcons())
|
|
|
|
Groups[i].Image--;
|
|
|
|
}
|
|
|
|
emit iconsModified();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Kdb3Database::replaceIcon(int id,const QPixmap& icon){
|
|
|
|
if(id<builtinIcons())return;
|
|
|
|
CustomIcons[id-builtinIcons()]=icon;
|
|
|
|
emit iconsModified();
|
|
|
|
}
|
|
|
|
|
|
|
|
int Kdb3Database::numIcons(){
|
|
|
|
return builtinIcons()+CustomIcons.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Kdb3Database::parseMetaStream(const StdEntry& entry){
|
|
|
|
|
|
|
|
qDebug("Found Metastream: %s", CSTR(entry.Comment));
|
|
|
|
|
|
|
|
if(entry.Comment=="KPX_GROUP_TREE_STATE"){
|
|
|
|
parseGroupTreeStateMetaStream(entry.Binary);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if(entry.Comment=="KPX_CUSTOM_ICONS_4"){
|
|
|
|
parseCustomIconsMetaStream(entry.Binary);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if(entry.Comment=="KPX_CUSTOM_ICONS_3"){
|
|
|
|
if (!hasV4IconMetaStream)
|
|
|
|
parseCustomIconsMetaStreamV3(entry.Binary);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if(entry.Comment=="KPX_CUSTOM_ICONS_2"){
|
|
|
|
qDebug("Removed old CuIcMeSt v2");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if(entry.Comment=="KPX_CUSTOM_ICONS"){
|
|
|
|
qDebug("Removed old CuIcMeSt v1");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false; //unknown MetaStream
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Kdb3Database::isMetaStream(StdEntry& p){
|
|
|
|
if(p.Binary.isNull()) return false;
|
|
|
|
if(p.Comment == "") return false;
|
|
|
|
if(p.BinaryDesc != "bin-stream") return false;
|
|
|
|
if(p.Title != "Meta-Info") return false;
|
|
|
|
if(p.Username != "SYSTEM") return false;
|
|
|
|
if(p.Url != "$") return false;
|
|
|
|
if(p.Image != 0) return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Kdb3Database::parseCustomIconsMetaStream(const QByteArray& dta){
|
|
|
|
//Rev 4 (KeePassX 0.3.2)
|
|
|
|
quint32 NumIcons,NumEntries,NumGroups,offset;
|
|
|
|
memcpyFromLEnd32(&NumIcons,dta.data());
|
|
|
|
memcpyFromLEnd32(&NumEntries,dta.data()+4);
|
|
|
|
memcpyFromLEnd32(&NumGroups,dta.data()+8);
|
|
|
|
offset=12;
|
|
|
|
CustomIcons.clear();
|
|
|
|
for(int i=0;i<NumIcons;i++){
|
|
|
|
CustomIcons << QPixmap();
|
|
|
|
quint32 Size;
|
|
|
|
memcpyFromLEnd32(&Size,dta.data()+offset);
|
|
|
|
if(offset+Size > dta.size()){
|
|
|
|
CustomIcons.clear();
|
|
|
|
qWarning("Discarded metastream KPX_CUSTOM_ICONS_4 because of a parsing error.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
offset+=4;
|
|
|
|
if(!CustomIcons.back().loadFromData((const unsigned char*)dta.data()+offset,Size,"PNG")){
|
|
|
|
CustomIcons.clear();
|
|
|
|
qWarning("Discarded metastream KPX_CUSTOM_ICONS_4 because of a parsing error.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
offset+=Size;
|
|
|
|
if(offset > dta.size()){
|
|
|
|
CustomIcons.clear();
|
|
|
|
qWarning("Discarded metastream KPX_CUSTOM_ICONS_4 because of a parsing error.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for(int i=0;i<NumEntries;i++){
|
|
|
|
quint32 Icon;
|
|
|
|
KpxUuid EntryUuid;
|
|
|
|
EntryUuid.fromRaw(dta.data()+offset);
|
|
|
|
offset+=16;
|
|
|
|
memcpyFromLEnd32(&Icon,dta.data()+offset);
|
|
|
|
offset+=4;
|
|
|
|
StdEntry* entry=getEntry(EntryUuid);
|
|
|
|
if(entry)
|
|
|
|
entry->Image=Icon+BUILTIN_ICONS;
|
|
|
|
}
|
|
|
|
for(int i=0;i<NumGroups;i++){
|
|
|
|
quint32 GroupId,Icon;
|
|
|
|
memcpyFromLEnd32(&GroupId,dta.data()+offset);
|
|
|
|
offset+=4;
|
|
|
|
memcpyFromLEnd32(&Icon,dta.data()+offset);
|
|
|
|
offset+=4;
|
|
|
|
StdGroup* Group=getGroup(GroupId);
|
|
|
|
if(Group)
|
|
|
|
Group->Image=Icon+BUILTIN_ICONS;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Kdb3Database::parseCustomIconsMetaStreamV3(const QByteArray& dta){
|
|
|
|
//Rev 3
|
|
|
|
quint32 NumIcons,NumEntries,NumGroups,offset;
|
|
|
|
memcpyFromLEnd32(&NumIcons,dta.data());
|
|
|
|
memcpyFromLEnd32(&NumEntries,dta.data()+4);
|
|
|
|
memcpyFromLEnd32(&NumGroups,dta.data()+8);
|
|
|
|
offset=12;
|
|
|
|
CustomIcons.clear();
|
|
|
|
for(int i=0;i<NumIcons;i++){
|
|
|
|
CustomIcons << QPixmap();
|
|
|
|
quint32 Size;
|
|
|
|
memcpyFromLEnd32(&Size,dta.data()+offset);
|
|
|
|
if(offset+Size > dta.size()){
|
|
|
|
CustomIcons.clear();
|
|
|
|
qWarning("Discarded metastream KPX_CUSTOM_ICONS_3 because of a parsing error.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
offset+=4;
|
|
|
|
if(!CustomIcons.back().loadFromData((const unsigned char*)dta.data()+offset,Size,"PNG")){
|
|
|
|
CustomIcons.clear();
|
|
|
|
qWarning("Discarded metastream KPX_CUSTOM_ICONS_3 because of a parsing error.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
offset+=Size;
|
|
|
|
if(offset > dta.size()){
|
|
|
|
CustomIcons.clear();
|
|
|
|
qWarning("Discarded metastream KPX_CUSTOM_ICONS_3 because of a parsing error.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for(int i=0;i<NumEntries;i++){
|
|
|
|
quint32 Icon;
|
|
|
|
KpxUuid EntryUuid;
|
|
|
|
EntryUuid.fromRaw(dta.data()+offset);
|
|
|
|
offset+=16;
|
|
|
|
memcpyFromLEnd32(&Icon,dta.data()+offset);
|
|
|
|
offset+=4;
|
|
|
|
StdEntry* entry=getEntry(EntryUuid);
|
|
|
|
if(entry){
|
|
|
|
if (Icon>=65)
|
|
|
|
entry->Image=Icon+4; // Since v0.3.2 the BUILTIN_ICONS number has increased by 4
|
|
|
|
else
|
|
|
|
entry->Image=Icon;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for(int i=0;i<NumGroups;i++){
|
|
|
|
quint32 GroupId,Icon;
|
|
|
|
memcpyFromLEnd32(&GroupId,dta.data()+offset);
|
|
|
|
offset+=4;
|
|
|
|
memcpyFromLEnd32(&Icon,dta.data()+offset);
|
|
|
|
offset+=4;
|
|
|
|
StdGroup* Group=getGroup(GroupId);
|
|
|
|
if(Group){
|
|
|
|
if (Group->Image>=65)
|
|
|
|
Group->Image=Icon+4; // Since v0.3.2 the BUILTIN_ICONS number has increased by 4
|
|
|
|
else
|
|
|
|
Group->Image=Icon;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Kdb3Database::parseGroupTreeStateMetaStream(const QByteArray& dta){
|
|
|
|
if(dta.size()<4){
|
|
|
|
qWarning("Discarded metastream KPX_GROUP_TREE_STATE because of a parsing error.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
quint32 Num;
|
|
|
|
memcpyFromLEnd32(&Num,dta.data());
|
|
|
|
if(Num*5!=dta.size()-4){
|
|
|
|
qWarning("Discarded metastream KPX_GROUP_TREE_STATE because of a parsing error.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
TreeStateMetaStream.clear();
|
|
|
|
for(int i=0;i<Num;i++){
|
|
|
|
quint32 GroupID;
|
|
|
|
quint8 IsExpanded;
|
|
|
|
memcpyFromLEnd32(&GroupID,dta.data()+4+5*i);
|
|
|
|
memcpy(&IsExpanded,dta.data()+8+5*i,1);
|
|
|
|
TreeStateMetaStream.insert(GroupID,(bool)IsExpanded);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Kdb3Database::createGroupTreeStateMetaStream(StdEntry* e){
|
|
|
|
e->BinaryDesc="bin-stream";
|
|
|
|
e->Title="Meta-Info";
|
|
|
|
e->Username="SYSTEM";
|
|
|
|
e->Comment="KPX_GROUP_TREE_STATE";
|
|
|
|
e->Url="$";
|
|
|
|
e->Image=0;
|
|
|
|
if(Groups.size())e->GroupId=Groups[0].Id;
|
|
|
|
QByteArray bin;
|
|
|
|
quint32 Num=Groups.size();
|
|
|
|
bin.resize(Num*5+4);
|
|
|
|
memcpyToLEnd32(bin.data(),&Num);
|
|
|
|
for(int i=0;i<Num;i++){
|
|
|
|
memcpyToLEnd32(bin.data()+4+5*i,&Groups[i].Id);
|
|
|
|
if(Groups[i].IsExpanded)
|
|
|
|
bin.data()[8+5*i]=1;
|
|
|
|
else
|
|
|
|
bin.data()[8+5*i]=0;
|
|
|
|
}
|
|
|
|
e->Binary=bin;
|
|
|
|
}
|
|
|
|
|
|
|
|
Kdb3Database::StdEntry* Kdb3Database::getEntry(const KpxUuid& uuid){
|
|
|
|
for(int i=0; i<Entries.size();i++)
|
|
|
|
if(Entries[i].Uuid==uuid)return &Entries[i];
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
Kdb3Database::StdGroup* Kdb3Database::getGroup(quint32 Id){
|
|
|
|
for(int i=0; i<Groups.size();i++)
|
|
|
|
if(Groups[i].Id==Id)return &Groups[i];
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//! Extracts one entry from raw decrypted data.
|
|
|
|
bool Kdb3Database::readEntryField(StdEntry* entry, quint16 FieldType, quint32 FieldSize, quint8 *pData){
|
|
|
|
switch(FieldType)
|
|
|
|
{
|
|
|
|
case 0x0000:
|
|
|
|
// Ignore field
|
|
|
|
break;
|
|
|
|
case 0x0001:
|
|
|
|
entry->Uuid=KpxUuid(pData);
|
|
|
|
break;
|
|
|
|
case 0x0002:
|
|
|
|
memcpyFromLEnd32(&entry->GroupId, (char*)pData);
|
|
|
|
break;
|
|
|
|
case 0x0003:
|
|
|
|
memcpyFromLEnd32(&entry->Image, (char*)pData);
|
|
|
|
break;
|
|
|
|
case 0x0004:
|
|
|
|
entry->Title=QString::fromUtf8((char*)pData);
|
|
|
|
break;
|
|
|
|
case 0x0005:
|
|
|
|
entry->Url=QString::fromUtf8((char*)pData);
|
|
|
|
break;
|
|
|
|
case 0x0006:
|
|
|
|
entry->Username=QString::fromUtf8((char*)pData);
|
|
|
|
break;
|
|
|
|
case 0x0007:{
|
|
|
|
QString s=QString::fromUtf8((char*)pData);
|
|
|
|
entry->Password.setString(s,true);
|
|
|
|
break;}
|
|
|
|
case 0x0008:
|
|
|
|
entry->Comment=QString::fromUtf8((char*)pData);
|
|
|
|
break;
|
|
|
|
case 0x0009:
|
|
|
|
entry->Creation=dateFromPackedStruct5(pData);
|
|
|
|
break;
|
|
|
|
case 0x000A:
|
|
|
|
entry->LastMod=dateFromPackedStruct5(pData);
|
|
|
|
break;
|
|
|
|
case 0x000B:
|
|
|
|
entry->LastAccess=dateFromPackedStruct5(pData);
|
|
|
|
break;
|
|
|
|
case 0x000C:
|
|
|
|
entry->Expire=dateFromPackedStruct5(pData);
|
|
|
|
break;
|
|
|
|
case 0x000D:
|
|
|
|
entry->BinaryDesc=QString::fromUtf8((char*)pData);
|
|
|
|
break;
|
|
|
|
case 0x000E:
|
|
|
|
if(FieldSize != 0)
|
|
|
|
entry->Binary=QByteArray((char*)pData,FieldSize);
|
|
|
|
else
|
|
|
|
entry->Binary=QByteArray();
|
|
|
|
break;
|
|
|
|
case 0xFFFF:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//! Extracts one group from raw decrypted data.
|
|
|
|
bool Kdb3Database::readGroupField(StdGroup* group,QList<quint32>& Levels,quint16 FieldType, quint8 *pData)
|
|
|
|
{
|
|
|
|
switch(FieldType)
|
|
|
|
{
|
|
|
|
case 0x0000:
|
|
|
|
// Ignore field
|
|
|
|
break;
|
|
|
|
case 0x0001:
|
|
|
|
memcpyFromLEnd32(&group->Id, (char*)pData);
|
|
|
|
break;
|
|
|
|
case 0x0002:
|
|
|
|
group->Title=QString::fromUtf8((char*)pData);
|
|
|
|
break;
|
|
|
|
case 0x0003: //not longer used by KeePassX but part of the KDB format
|
|
|
|
break;
|
|
|
|
case 0x0004: //not longer used by KeePassX but part of the KDB format
|
|
|
|
break;
|
|
|
|
case 0x0005: //not longer used by KeePassX but part of the KDB format
|
|
|
|
break;
|
|
|
|
case 0x0006: //not longer used by KeePassX but part of the KDB format
|
|
|
|
break;
|
|
|
|
case 0x0007:
|
|
|
|
memcpyFromLEnd32(&group->Image, (char*)pData);
|
|
|
|
break;
|
|
|
|
case 0x0008:
|
|
|
|
quint16 Level;
|
|
|
|
memcpyFromLEnd16(&Level, (char*)pData);
|
|
|
|
Levels.append(Level);
|
|
|
|
break;
|
|
|
|
case 0x0009:
|
|
|
|
//not used by KeePassX but part of the KDB format
|
|
|
|
//memcpyFromLEnd32(&Flags, (char*)pData);
|
|
|
|
break;
|
|
|
|
case 0xFFFF:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return false; // Field unsupported
|
|
|
|
}
|
|
|
|
|
|
|
|
return true; // Field supported
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Kdb3Database::createGroupTree(QList<quint32>& Levels){
|
|
|
|
if(Levels[0]!=0) return false;
|
|
|
|
//find the parent for every group
|
|
|
|
for(int i=0;i<Groups.size();i++){
|
|
|
|
if(Levels[i]==0){
|
|
|
|
Groups[i].Parent=&RootGroup;
|
|
|
|
Groups[i].Index=RootGroup.Children.size();
|
|
|
|
RootGroup.Children.append(&Groups[i]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
int j;
|
|
|
|
//the first item with a lower level is the parent
|
|
|
|
for(j=i-1;j>=0;j--){
|
|
|
|
if(Levels[j]<Levels[i]){
|
|
|
|
if(Levels[i]-Levels[j]!=1)return false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if(j==0)return false; //No parent found
|
|
|
|
}
|
|
|
|
Groups[i].Parent=&Groups[j];
|
|
|
|
Groups[i].Index=Groups[j].Children.size();
|
|
|
|
Groups[i].Parent->Children.append(&Groups[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
QList<int> EntryIndexCounter;
|
|
|
|
for(int i=0;i<Groups.size();i++)EntryIndexCounter << 0;
|
|
|
|
|
|
|
|
for(int e=0;e<Entries.size();e++){
|
|
|
|
int groupIndex = -1;
|
|
|
|
for(int g=0;g<Groups.size();g++){
|
|
|
|
if(Entries[e].GroupId==Groups[g].Id){
|
|
|
|
groupIndex = g;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (groupIndex == -1) {
|
|
|
|
qWarning("Orphaned entry found, assigning to first group");
|
|
|
|
for(int g=0;g<Groups.size();g++){
|
|
|
|
if(Groups[g].Id == RootGroup.Children[0]->Id){
|
|
|
|
groupIndex = g;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Groups[groupIndex].Entries.append(&Entries[e]);
|
|
|
|
Entries[e].Group=&Groups[groupIndex];
|
|
|
|
Entries[e].Index=EntryIndexCounter[groupIndex];
|
|
|
|
EntryIndexCounter[groupIndex]++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Kdb3Database::createHandles(){
|
|
|
|
for(int i=0;i<Groups.size();i++){
|
|
|
|
GroupHandles.append(GroupHandle(this));
|
|
|
|
Groups[i].Handle=&GroupHandles.back();
|
|
|
|
GroupHandles.back().Group=&Groups[i];
|
|
|
|
}
|
|
|
|
for(int i=0;i<Entries.size();i++){
|
|
|
|
EntryHandles.append(EntryHandle(this));
|
|
|
|
Entries[i].Handle=&EntryHandles.back();
|
|
|
|
EntryHandles.back().Entry=&Entries[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Kdb3Database::restoreGroupTreeState(){
|
|
|
|
switch (config->groupTreeState()){
|
|
|
|
case KpxConfig::RestoreLast:
|
|
|
|
for(int i=0;i<Groups.size();i++){
|
|
|
|
if(TreeStateMetaStream.contains(Groups[i].Id))
|
|
|
|
Groups[i].IsExpanded=TreeStateMetaStream.value(Groups[i].Id);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case KpxConfig::ExpandAll:
|
|
|
|
for(int i=0;i<Groups.size();i++)
|
|
|
|
Groups[i].IsExpanded=true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case KpxConfig::DoNothing:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Kdb3Database::load(QString identifier, bool readOnly){
|
|
|
|
return loadReal(identifier, readOnly, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define LOAD_RETURN_CLEANUP \
|
|
|
|
delete File; \
|
|
|
|
File = NULL; \
|
|
|
|
delete[] buffer; \
|
|
|
|
return false;
|
|
|
|
|
|
|
|
bool Kdb3Database::loadReal(QString filename, bool readOnly, bool differentEncoding) {
|
|
|
|
File = new QFile(filename);
|
|
|
|
if (readOnly) {
|
|
|
|
if(!File->open(QIODevice::ReadOnly)){
|
|
|
|
error=tr("Could not open file.");
|
|
|
|
delete File;
|
|
|
|
File = NULL;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if(!File->open(QIODevice::ReadWrite)){
|
|
|
|
if(!File->open(QIODevice::ReadOnly)){
|
|
|
|
error=tr("Could not open file.");
|
|
|
|
delete File;
|
|
|
|
File = NULL;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
readOnly = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
openedReadOnly = readOnly;
|
|
|
|
|
|
|
|
unsigned long total_size,crypto_size;
|
|
|
|
quint32 Signature1,Signature2,Version,NumGroups,NumEntries,Flags;
|
|
|
|
quint8 FinalRandomSeed[16];
|
|
|
|
quint8 ContentsHash[32];
|
|
|
|
quint8 EncryptionIV[16];
|
|
|
|
|
|
|
|
total_size=File->size();
|
|
|
|
char* buffer = new char[total_size];
|
|
|
|
File->read(buffer,total_size);
|
|
|
|
|
|
|
|
if(total_size < DB_HEADER_SIZE){
|
|
|
|
error=tr("Unexpected file size (DB_TOTAL_SIZE < DB_HEADER_SIZE)");
|
|
|
|
LOAD_RETURN_CLEANUP
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpyFromLEnd32(&Signature1,buffer);
|
|
|
|
memcpyFromLEnd32(&Signature2,buffer+4);
|
|
|
|
memcpyFromLEnd32(&Flags,buffer+8);
|
|
|
|
memcpyFromLEnd32(&Version,buffer+12);
|
|
|
|
memcpy(FinalRandomSeed,buffer+16,16);
|
|
|
|
memcpy(EncryptionIV,buffer+32,16);
|
|
|
|
memcpyFromLEnd32(&NumGroups,buffer+48);
|
|
|
|
memcpyFromLEnd32(&NumEntries,buffer+52);
|
|
|
|
memcpy(ContentsHash,buffer+56,32);
|
|
|
|
memcpy(TransfRandomSeed,buffer+88,32);
|
|
|
|
memcpyFromLEnd32(&KeyTransfRounds,buffer+120);
|
|
|
|
|
|
|
|
if((Signature1!=PWM_DBSIG_1) || (Signature2!=PWM_DBSIG_2)){
|
|
|
|
error=tr("Wrong Signature");
|
|
|
|
LOAD_RETURN_CLEANUP
|
|
|
|
}
|
|
|
|
|
|
|
|
if((Version & 0xFFFFFF00) != (PWM_DBVER_DW & 0xFFFFFF00)){
|
|
|
|
error=tr("Unsupported File Version.");
|
|
|
|
LOAD_RETURN_CLEANUP
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Flags & PWM_FLAG_RIJNDAEL)
|
|
|
|
Algorithm = Rijndael_Cipher;
|
|
|
|
else if (Flags & PWM_FLAG_TWOFISH)
|
|
|
|
Algorithm = Twofish_Cipher;
|
|
|
|
else{
|
|
|
|
error=tr("Unknown Encryption Algorithm.");
|
|
|
|
LOAD_RETURN_CLEANUP
|
|
|
|
}
|
|
|
|
|
|
|
|
RawMasterKey.unlock();
|
|
|
|
MasterKey.unlock();
|
|
|
|
KeyTransform::transform(*RawMasterKey,*MasterKey,TransfRandomSeed,KeyTransfRounds);
|
|
|
|
|
|
|
|
quint8 FinalKey[32];
|
|
|
|
|
|
|
|
SHA256 sha;
|
|
|
|
sha.update(FinalRandomSeed,16);
|
|
|
|
sha.update(*MasterKey,32);
|
|
|
|
sha.finish(FinalKey);
|
|
|
|
|
|
|
|
RawMasterKey.lock();
|
|
|
|
MasterKey.lock();
|
|
|
|
|
|
|
|
if(Algorithm == Rijndael_Cipher){
|
|
|
|
AESdecrypt aes;
|
|
|
|
aes.key256(FinalKey);
|
|
|
|
aes.cbc_decrypt((unsigned char*)buffer+DB_HEADER_SIZE,(unsigned char*)buffer+DB_HEADER_SIZE,total_size-DB_HEADER_SIZE,(unsigned char*)EncryptionIV);
|
|
|
|
crypto_size=total_size-((quint8*)buffer)[total_size-1]-DB_HEADER_SIZE;
|
|
|
|
}
|
|
|
|
else if(Algorithm == Twofish_Cipher){
|
|
|
|
CTwofish twofish;
|
|
|
|
if (twofish.init(FinalKey, 32, EncryptionIV) != true){
|
|
|
|
error=tr("Unable to initialize the twofish algorithm.");
|
|
|
|
LOAD_RETURN_CLEANUP
|
|
|
|
}
|
|
|
|
crypto_size = (unsigned long)twofish.padDecrypt((quint8 *)buffer + DB_HEADER_SIZE,
|
|
|
|
total_size - DB_HEADER_SIZE, (quint8 *)buffer + DB_HEADER_SIZE);
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
error=tr("Unknown encryption algorithm.");
|
|
|
|
LOAD_RETURN_CLEANUP
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((crypto_size > 2147483446) || (!crypto_size && NumGroups)){
|
|
|
|
error=tr("Decryption failed.\nThe key is wrong or the file is damaged.");
|
|
|
|
KeyError=true;
|
|
|
|
LOAD_RETURN_CLEANUP
|
|
|
|
}
|
|
|
|
SHA256::hashBuffer(buffer+DB_HEADER_SIZE,FinalKey,crypto_size);
|
|
|
|
|
|
|
|
if(memcmp(ContentsHash, FinalKey, 32) != 0){
|
|
|
|
if(PotentialEncodingIssueLatin1){
|
|
|
|
delete[] buffer;
|
|
|
|
delete File;
|
|
|
|
File = NULL;
|
|
|
|
|
|
|
|
RawMasterKey.copyData(RawMasterKey_Latin1);
|
|
|
|
PotentialEncodingIssueLatin1 = false;
|
|
|
|
qDebug("Decryption failed. Retrying with Latin-1.");
|
|
|
|
return loadReal(filename, readOnly, true); // second try
|
|
|
|
}
|
|
|
|
if(PotentialEncodingIssueUTF8){
|
|
|
|
delete[] buffer;
|
|
|
|
delete File;
|
|
|
|
File = NULL;
|
|
|
|
|
|
|
|
RawMasterKey.copyData(RawMasterKey_UTF8);
|
|
|
|
PotentialEncodingIssueUTF8 = false;
|
|
|
|
qDebug("Decryption failed. Retrying with UTF-8.");
|
|
|
|
return loadReal(filename, readOnly, true); // second/third try
|
|
|
|
}
|
|
|
|
error=tr("Hash test failed.\nThe key is wrong or the file is damaged.");
|
|
|
|
KeyError=true;
|
|
|
|
LOAD_RETURN_CLEANUP
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned long pos = DB_HEADER_SIZE;
|
|
|
|
quint16 FieldType;
|
|
|
|
quint32 FieldSize;
|
|
|
|
char* pField;
|
|
|
|
bool bRet;
|
|
|
|
StdGroup group;
|
|
|
|
QList<quint32> Levels;
|
|
|
|
RootGroup.Title="$ROOT$";
|
|
|
|
RootGroup.Parent=NULL;
|
|
|
|
RootGroup.Handle=NULL;
|
|
|
|
|
|
|
|
for(unsigned long CurGroup = 0; CurGroup < NumGroups; )
|
|
|
|
{
|
|
|
|
pField = buffer+pos;
|
|
|
|
|
|
|
|
memcpyFromLEnd16(&FieldType, pField);
|
|
|
|
pField += 2; pos += 2;
|
|
|
|
if (pos >= total_size){
|
|
|
|
error=tr("Unexpected error: Offset is out of range.").append(" [G1]");
|
|
|
|
LOAD_RETURN_CLEANUP
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpyFromLEnd32(&FieldSize, pField);
|
|
|
|
pField += 4; pos += 4;
|
|
|
|
if (pos >= (total_size + FieldSize)){
|
|
|
|
error=tr("Unexpected error: Offset is out of range.").append(" [G2]");
|
|
|
|
LOAD_RETURN_CLEANUP
|
|
|
|
}
|
|
|
|
|
|
|
|
bRet = readGroupField(&group,Levels, FieldType, (quint8 *)pField);
|
|
|
|
if ((FieldType == 0xFFFF) && (bRet == true)){
|
|
|
|
Groups << group;
|
|
|
|
CurGroup++; // Now and ONLY now the counter gets increased
|
|
|
|
}
|
|
|
|
pField += FieldSize;
|
|
|
|
pos += FieldSize;
|
|
|
|
if (pos >= total_size){
|
|
|
|
error=tr("Unexpected error: Offset is out of range.").append(" [G1]");
|
|
|
|
LOAD_RETURN_CLEANUP
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
StdEntry entry;
|
|
|
|
|
|
|
|
for (unsigned long CurEntry = 0; CurEntry < NumEntries;)
|
|
|
|
{
|
|
|
|
pField = buffer+pos;
|
|
|
|
|
|
|
|
memcpyFromLEnd16(&FieldType, pField);
|
|
|
|
pField += 2; pos += 2;
|
|
|
|
if(pos >= total_size){
|
|
|
|
error=tr("Unexpected error: Offset is out of range.").append(" [E1]");
|
|
|
|
LOAD_RETURN_CLEANUP
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpyFromLEnd32(&FieldSize, pField);
|
|
|
|
pField += 4; pos += 4;
|
|
|
|
if (pos >= (total_size + FieldSize)){
|
|
|
|
error=tr("Unexpected error: Offset is out of range.").append(" [E2]");
|
|
|
|
LOAD_RETURN_CLEANUP
|
|
|
|
}
|
|
|
|
|
|
|
|
bRet = readEntryField(&entry,FieldType,FieldSize,(quint8*)pField);
|
|
|
|
|
|
|
|
if((FieldType == 0xFFFF) && (bRet == true)){
|
|
|
|
Entries << entry;
|
|
|
|
if(!entry.GroupId)
|
|
|
|
qDebug("NULL: %i, '%s'", (int)CurEntry, (char*)entry.Title.toUtf8().data());
|
|
|
|
CurEntry++;
|
|
|
|
}
|
|
|
|
|
|
|
|
pField += FieldSize;
|
|
|
|
pos += FieldSize;
|
|
|
|
if (pos >= total_size){
|
|
|
|
error=tr("Unexpected error: Offset is out of range.").append(" [E3]");
|
|
|
|
LOAD_RETURN_CLEANUP
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!createGroupTree(Levels)){
|
|
|
|
error=tr("Invalid group tree.");
|
|
|
|
LOAD_RETURN_CLEANUP
|
|
|
|
}
|
|
|
|
|
|
|
|
delete [] buffer;
|
|
|
|
|
|
|
|
hasV4IconMetaStream = false;
|
|
|
|
for(int i=0;i<Entries.size();i++){
|
|
|
|
if(isMetaStream(Entries[i]) && Entries[i].Comment=="KPX_CUSTOM_ICONS_4"){
|
|
|
|
hasV4IconMetaStream = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//Remove the metastreams from the entry list
|
|
|
|
for(int i=0;i<Entries.size();i++){
|
|
|
|
if(isMetaStream(Entries[i])){
|
|
|
|
if(!parseMetaStream(Entries[i]))
|
|
|
|
UnknownMetaStreams << Entries[i];
|
|
|
|
Entries.removeAt(i);
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int* EntryIndices=new int[Groups.size()];
|
|
|
|
for(int i=0;i<Groups.size();i++)EntryIndices[i]=0;
|
|
|
|
|
|
|
|
for(int g=0;g<Groups.size();g++){
|
|
|
|
for(int e=0;e<Entries.size();e++){
|
|
|
|
if(Entries[e].GroupId==Groups[g].Id){
|
|
|
|
Entries[e].Index=EntryIndices[g];
|
|
|
|
EntryIndices[g]++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
delete [] EntryIndices;
|
|
|
|
createHandles();
|
|
|
|
restoreGroupTreeState();
|
|
|
|
|
|
|
|
passwordEncodingChanged = differentEncoding;
|
|
|
|
if (differentEncoding) {
|
|
|
|
RawMasterKey.copyData(RawMasterKey_CP1252);
|
|
|
|
generateMasterKey();
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
QDateTime Kdb3Database::dateFromPackedStruct5(const unsigned char* pBytes){
|
|
|
|
quint32 dw1, dw2, dw3, dw4, dw5;
|
|
|
|
dw1 = (quint32)pBytes[0]; dw2 = (quint32)pBytes[1]; dw3 = (quint32)pBytes[2];
|
|
|
|
dw4 = (quint32)pBytes[3]; dw5 = (quint32)pBytes[4];
|
|
|
|
int y = (dw1 << 6) | (dw2 >> 2);
|
|
|
|
int mon = ((dw2 & 0x00000003) << 2) | (dw3 >> 6);
|
|
|
|
int d = (dw3 >> 1) & 0x0000001F;
|
|
|
|
int h = ((dw3 & 0x00000001) << 4) | (dw4 >> 4);
|
|
|
|
int min = ((dw4 & 0x0000000F) << 2) | (dw5 >> 6);
|
|
|
|
int s = dw5 & 0x0000003F;
|
|
|
|
return QDateTime(QDate(y,mon,d),QTime(h,min,s));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Kdb3Database::dateToPackedStruct5(const QDateTime& d,unsigned char* pBytes){
|
|
|
|
pBytes[0] = (quint8)(((quint32)d.date().year() >> 6) & 0x0000003F);
|
|
|
|
pBytes[1] = (quint8)((((quint32)d.date().year() & 0x0000003F) << 2) | (((quint32)d.date().month() >> 2) & 0x00000003));
|
|
|
|
pBytes[2] = (quint8)((((quint32)d.date().month() & 0x00000003) << 6) | (((quint32)d.date().day() & 0x0000001F) << 1) | (((quint32)d.time().hour() >> 4) & 0x00000001));
|
|
|
|
pBytes[3] = (quint8)(((( |