)
{
chomp;
# print ">>> $_\n";
if (m##)
{
$rec= {};
$status= 'record';
$counter++;
$b= '';
}
elsif (m##)
{
if ($status ne 'record' && $status ne 'data')
{
print "WARNING: unexpected status $status\n";
}
# analyze header if necessary:
# filters:length:type:off:status:flags:idx
# print ">>> insert record: ", join (':', %$rec), "\n";
# &hex_dump ($b);
&analyze_record ($self, $rec, $counter, $b);
$status= 'undef';
}
elsif (m##)
{
$format= $1;
$status= 'data';
}
elsif (m##)
{
$status= 'record';
}
elsif (m#<(type|idx|length|status|filters|flags)>(.*)#)
{
$rec->{$1}= $2;
}
elsif ($status eq 'data')
{
if ($format eq 'QP')
{
s/=$//;
s/=([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$b .= $_;
}
elsif ($format eq 'HEX')
{
my @x= split (/\|/);
@x= split (' ', $x[0]);
shift (@x);
# print "[", join (':', @x), "]\n";
$b .= pack ("C*", map { hex ($_); } @x);
}
else
{
print "WARNING: unexpected data format: '$format'\n";
}
}
elsif (/^#/ || /^[ \t]*$/) {} # comment
else
{
print "WARNING: unexpected data: '$_'\n";
}
}
}
# ----------------------------------------------------------------------------
sub TIEARRAY
{
return $_[1];
}
# ----------------------------------------------------------------------------
sub FETCH
{
my $db= shift;
my $idx= shift;
my $T= $db->{Types} || die 'not a database';
my $D= $T->[11]; # array of data records
return undef if ($idx > $#$D);
my $Dx= $D->[$idx]; # data record for the given index
my $rv;
unless (defined ($rv= $Dx->{obj}))
{ # no record data was previously stored, fetch that
my $N= $T->[9]; # array of note records
my $F= $db->{fielddef};
my $b= $Dx->{data};
my $APT= $db->{APT};
# print "FETCH: T=$T D=$D N=$N F=$F b=$b\n";
my ($ok, $o)= &fetch_data ($b, $F, $N, $APT);
# &dump_data_record ($b, $ok, $o);
$Dx->{obj}= $rv= $o;
$Dx->{ok}= $ok;
}
return $rv;
}
# ----------------------------------------------------------------------------
sub FETCH_data_raw
{
my $db= shift;
my $idx= shift;
my $T= $db->{Types} || return undef;
my $D= $T->[11]; # array of data records
return undef if ($idx > $#$D);
$D->[$idx]->{data}; # data record for the given index
}
# ----------------------------------------------------------------------------
sub FETCH_note_raw
{
my $db= shift;
my $idx= shift;
my $T= $db->{Types} || return undef;
my $N= $T->[9]; # array of note records
return undef if ($idx > $#$N);
$N->[$idx]->{data}; # data record for the given index
}
# ----------------------------------------------------------------------------
sub STORE
{
my $db= shift;
my $idx= shift;
my $val= shift;
# print "STORE: ", join (':', %$val), "\n";
my $T= $db->{Types} || die;
my $D= $T->[11]; # array of data records
my $N= $T->[9]; # array of note records
my $F= $db->{fielddef};
my $APT= $db->{APT};
my $Dx;
if ($idx > $#$D)
{
# print "adding records: num=$#$D idx=$idx\n";
$Dx= { 'data' => '' };
}
else
{
$Dx= $D->[$idx]; # data record for the given index
}
my ($ok, $b)= &store_data ($val, $F, $N, $APT, $db->{rec_size});
$Dx->{data}= $b;
undef ($Dx->{obj});
undef ($Dx->{ok});
$D->[$idx]= $Dx;
# T2D: unfinished
# missing items: refreshing and/or invalidating view points
$db->{update}++;
}
# ----------------------------------------------------------------------------
sub STORE_data_raw
{
my $db= shift;
my $idx= shift;
my $data= shift;
my $T= $db->{Types} || die;
my $D= $T->[11]; # array of data records
$D->[$idx]->{data}= $data;
$db->{update}++;
}
# ----------------------------------------------------------------------------
sub STORE_note_raw
{
my $db= shift;
my $idx= shift;
my $data= shift;
my $T= $db->{Types} || die;
my $N= $T->[9]; # array of note records
$N->[$idx]->{data}= $data;
$db->{update}++;
}
# ----------------------------------------------------------------------------
sub FETCHSIZE
{
my $db= shift;
return 1 + $db->get_last_index();
}
# ----------------------------------------------------------------------------
sub get_last_index
{
my $db= shift;
my $T= $db->{Types} || die;
my $D= $T->[11]; # array of data records
return $#$D;
}
# ----------------------------------------------------------------------------
sub get_str
{
my $b= shift;
my $off= shift;
my $res= substr ($$b, $off);
my $idx= index ($res, "\000");
$res= substr ($res, 0, $idx) if ($idx >= 0);
$res;
}
# ----------------------------------------------------------------------------
sub fmt_date
{
my $str= shift;
my ($year, $mon, $day)= unpack ('CCC', $str);
($year == $no_year && $mon == $no_mon && $day == $no_day)
? '' # empty date field
: sprintf ("%d-%02d-%02d", 1900 + $year, $mon+1, $day+1);
}
# ----------------------------------------------------------------------------
sub pack_date
{
my $val= shift;
my ($year, $mon, $day);
$year= $mon= $day= $no_date;
if ($val =~ /(\d+)-(\d+)-(\d+)/)
{
($year, $mon, $day)= ($1, $2, $3);
# check for valid dates otherwise set no_date value
$year= $mon= $day= $no_date
if ($year < 1900 || $year > 2155
|| $mon < 1 || $mon > 12
|| $day < 1 || $day > 31);
$year -= 1900;
$mon--;
$day--;
}
pack ('CCC', $year, $mon, $day);
}
# ----------------------------------------------------------------------------
sub fmt_time
{
my $str= shift;
my $val= unpack ('v', $str);
return '' if ($val == $no_time || $val == $no_val);
my $min= $val % 60;
my $xval= int ($val / 60);
sprintf ("%d:%02d", $xval, $min);
}
# ----------------------------------------------------------------------------
sub fetch_data
{
my $b= shift; # raw binary data
my $Fdef= shift; # Field Definitions
my $N= shift; # Notes Data
my $APT= shift; # application type
my $ok= 1;
my %o;
my %RB; # radio button at offset
my $field;
my @Fdef= @$Fdef; # Field Definition List
my $APT2;
if ($APT eq 'ADB')
{ # For appointment book entries we have to analyze if
# the record describes a to-do item or a date or event
my $val= unpack ('C', substr ($b, 0x0E, 1));
my @TLT= ();
# if ($val & 0x02) { $APT2= 'Done'; } # checked to-do entry
if ($val & 0x10) { $APT2= 'To-Do'; @TLT= (0, 1, 8..12); }
elsif ($val & 0x20) { $APT2= 'Event'; @TLT= (0..7, 12, 14, 15); }
elsif ($val & 0x80) { $APT2= 'Date'; @TLT= (0..7, 12, 14, 15); }
$o{'type'}= $APT2;
$o{'repeat'}= unpack ('C', substr ($b, 0x1A, 1));
@Fdef= map { $Fdef[$_] } @TLT;
}
FIELD: foreach $field (@Fdef)
{
my $type= $field->{ftype};
my $off= $field->{off};
my $name= $field->{name};
my $res;
# printf ("APT= 0x%02X %2d '%s'\n", $off, $type, $name);
if ($type == 0) # BYTE_BOOL
{
my $val= unpack ('C', substr ($b, $off, 1));
$res= ($val) ? 'X' : '';
}
elsif ($type == 1) # WORD_BOOL
{
my $val= unpack ('v', substr ($b, $off, 2));
$res= ($val) ? 'X' : '';
}
elsif ($type == 2 && $APT eq 'ADB' && $off eq 0x1B)
{ # Beschreibung bei ADB geht ohne Offset!
$res= &get_str (\$b, $off);
}
elsif ($type == 2 # STRING
|| $type == 3 # PHONE
|| $type == 4 # NUMBER
|| $type == 6 # CATEGORY
)
{
my $offs= unpack ('v', substr ($b, $off, 2));
$res= &get_str (\$b, $offs);
}
elsif ($type == 7 # TIME
|| ($type == 24 && $APT eq 'ADB') # Vorlauf
)
{
#??? next if ($APT eq 'APT' && $APT2 eq 'To-Do'); # overlapping fields
$res= &fmt_time (substr ($b, $off, 2));
}
elsif ($type == 8 # DATE
|| ($type == 19 && $APT eq 'ADB') # Beginndatum
)
{
$res= &fmt_date (substr ($b, $off, 3));
}
elsif ($type == 9) # RADIO_BUTTON
{
my $val= unpack ('C', substr ($b, $off, 1)); # 2 or 1 byte??
my $cnt= ++$RB{$off};
$res= ($cnt == $val) ? 'X' : '';
}
elsif ($type == 10) # NOTE
{
my $note_number= unpack ('v', substr ($b, $off, 2));
$o{"$name&nr"}= $note_number;
unless ($note_number eq $no_note)
{
my $nr;
$nr= $N->[$note_number]; # $nr should be a valid reference!
$res= (defined ($nr)) ? $nr->{data} : '';
}
}
elsif ($type == 11 # GROUP
|| $type == 12 # STATIC (e.g. Label)
|| $type == 14 # LIST
|| $type == 15 # COMBO
|| ($type == 18 && $APT eq 'ADB') # repeat factor
) # no action ?!?!?
{
next FIELD;
}
elsif ($type == 16 && $APT == 'WDB')
{
$res= unpack ('v', substr ($b, $off, 2));
}
elsif ($APT eq 'ADB'
&& ($type == 23 # number of days
|| $type == 20 # date due Faelligkeitsdatum
)
)
{
next if ($type == 23 && $APT2 eq 'To-Do');
next if ($type == 20 && $APT2 ne 'To-Do');
$res= unpack ('v', substr ($b, $off, 2)); # 2 byte integer value
}
elsif ($APT eq 'ADB' && $type == 22)
{
# print "\n", $delim, "\n>>> U22: APT2='$APT2'\n";
next unless ($APT2 eq 'To-Do'); # priority code
$res= substr ($b, $off, 2);
$res=~ s/\x00//g;
}
else
{
$res= "unknown type $type";
&show_field_def (*STDOUT, $field, -1);
$ok= 0;
}
# print "fetch: name=$name res=$res\n";
$o{$name}= $res;
}
return ($ok, \%o);
}
# ----------------------------------------------------------------------------
sub store_data
{
my $data= shift; # record data to be stored into the database
my $Fdef= shift; # Field Definitions
my $N= shift; # Notes Data; array of references
my $APT= shift; # application type
my $rec_size= shift; # standard record size and next string position
my $b_off= 0; # offset into binary data
my @b= # binary data at each offset
my $b; # final binary data
my $nil_addr; # address of the NIL string record
# this is set up when there are actually strings
# see notes below
my $ok= 1;
my %RB;
my $field;
# print "rec_size= $rec_size\n";
# NOTE: ADB records should possibly not be handled here at all!!!
FIELD: foreach $field (@$Fdef)
{
my $type= $field->{ftype};
my $off= $field->{off};
my $name= $field->{name};
my $ex= (exists ($data->{$name})) ? 1 : 0; # data value present?
my $val= $data->{$name}; # actual value
my $APT2;
$APT2= $data->{type} if ($APT eq 'ADB');
# print "offset= $off type=$type name=$name val='$val'\n";
if ($type == 0) # BYTEBOOL
{
$b [$off]= pack ('C', ($val) ? 1 : 0);
}
elsif ($type == 1) # WORDBOOL
{
$b [$off]= pack ('v', ($val) ? 1 : 0);
}
elsif ($type == 2 # STRING
|| $type == 3 # PHONE
|| $type == 4 # NUMBER
|| $type == 6 # CATEGORY
)
{
if ($nil_addr eq '')
{ # create empty string which is used for all other empty strings
# see note below
$nil_addr= $rec_size;
$b [$rec_size++]= "\000";
# print "insert nil at $nil_addr, rec_size=$rec_size\n";
}
if ($val)
{
$b [$off] = pack ('v', $rec_size);
$b [$rec_size]= $val . "\000";
$rec_size += length ($val) + 1;
}
else
{ # store pointer to the empty string record
$b [$off] = pack ('v', $nil_addr);
}
# &hex_dump ($b[$off]);
}
elsif ($type == 7) # TIME
{
next if ($APT eq 'ADB' && $APT2 eq 'To-Do');
my ($h, $m, $t);
$h= $val;
($h, $m)= ($1, $2) if ($val =~ /(\d+)[:\.](\d+)/);
$t= $h*60+$m;
$t= $no_time if (!$ex || $t < 0 || $t > $no_time);
$b [$off]= pack ('v', $t);
}
elsif ($type == 8) # DATE
{
$b [$off]= &pack_date ($val);
}
elsif ($type == 9) # RADIO_BUTTON
{ # several radio buttons point to the same offset
# the value can be the number of the button pointing there
# or 0 when no button is checked
my $v; # value to be stored
my $checked= ($val) ? 1 : 0;
$checked= 0 if ($v= $RB{$off}); # only the first button is valid
$RB{$off}= $v= $field->{res} if ($checked);
$b [$off]= pack ('v', $v); # Note: should be 'c' ?!?!
}
elsif ($type == 10) # NOTE
{ # store note record
# possible cases:
# stored | new | action
# no | no | no action, $no_note is already stored
# no | yes | store new note number
# yes | no | T2D: delete old note, but how??
# yes | yes | store note number and replace the note
my $note_nr= $no_note;
my $xn= "$name&nr";
$note_nr= $data->{$xn} if (defined ($data->{$xn})); # stored note
if ($note_nr == $no_note && $val ne '')
{ # no note before but a valid note: create new note record
push (@$N, { data => $val });
$data->{$xn}= $note_nr= $#$N;
}
elsif ($note_nr != $no_note && $val eq '')
{ # T2D: delete note!!
# this leaves an empty note record in the database !!!
undef ($N->[$note_nr]->{data}); # T2D, Test
$data->{$xn}= $note_nr= $no_note;
}
elsif ($note_nr != $no_note && $val ne '')
{ # replace existing note
$N->[$note_nr]->{data}= $val;
}
$b [$off]= pack ('v', $note_nr);
}
elsif ($type == 11 # GROUP
|| $type == 12 # STATIC
|| $type == 14 # LIST
|| $type == 15 # COMBO
) # no action ?!?!?
{
next FIELD;
}
else
{
print "store_data: ERROR! unknown type $type\n";
&show_field_def (*STDOUT, $field, -1);
print "value: $val\n";
$ok= 0;
}
}
if ($ok)
{
$b= join ('', @b);
if (length ($b) != $rec_size)
{
print "ERROR: resulting record size does not match!\n",
"length=", length ($b), " rec_size=$rec_size\n";
&hex_dump ($b);
my ($x, $y);
for ($x= 0; $x <= $#b; $x++)
{
next unless ($y= $b[$x]);
printf ("[%02d] %2d '%s'\n", $x, length ($y), $y);
}
}
}
# T2D: unfinished
return ($ok, $b);
}
# NOTES:
# Empty Strings are stored as null character at the beginning of the
# extended data record. All empty strings point to the same address.
# An empty string is stored even when all strings have a value.
# ----------------------------------------------------------------------------
# read a 6 byte record header
sub get_recheader
{
local *F= shift;
my $b;
read (F, $b, 6) || return undef;
my ($type, $status, $length, $idx)= unpack ('CCvv', $b);
my $rec=
{
'type' => $type,
'status' => $status,
'length' => $length,
'idx' => $idx,
};
$rec;
}
# ----------------------------------------------------------------------------
# write a 6 byte record header
sub put_recheader
{
local *F= shift;
my $r= shift;
my $b= pack ('CCvv', $r->{'type'}, $r->{'status'},
$r->{'length'}, $r->{'idx'});
print F $b;
}
# ----------------------------------------------------------------------------
sub fmt_time_stamp
{
my $time= shift;
my $Time= sprintf ("%d-%02d-%02d %2d:%02d",
$time->{'year'}, $time->{'mon'}+1, $time->{'day'}+1,
$time->{'min'} / 60, $time->{'min'} % 60);
$Time;
}
# ----------------------------------------------------------------------------
sub get_carddef
{
my $def= shift;
my @wins;
my $num= 0;
# print ">>> processing card definition\n";
while ($def)
{
my $pw= substr ($def, 0, 20);
$def= substr ($def, 20);
my ($u, $x, $y, $w, $h, $Lsize, $style, $parent)=
unpack ('VvvvvvVv', $pw);
# printf ("[%3d] x=%3d y=%3d w=%3d h=%3d L=%3d S=0x%08lX P=0x%04X\n",
# $num, $x, $y, $w, $h, $Lsize, $style, $parent);
$num++;
my $win=
{
'x' => $x,
'y' => $y,
'w' => $w,
'h' => $h,
'Lsize' => $Lsize,
'Style' => $style,
'Parent' => $parent,
};
push (@wins, $win);
}
\@wins;
}
# ----------------------------------------------------------------------------
sub get_fielddef
{
my $def= shift;
my ($ftype, $fid, $off, $flg, $res)= unpack ('CCvCv', $def);
my $name= substr ($def, 7, length ($def)-8);
$name=~ s/\&//g;
my $fd=
{
'ftype' => $ftype,
'Ftype' => $FIELD_TYPE [$ftype]->{Desc},
'fid' => $fid,
'off' => $off,
'flg' => $flg,
'res' => $res,
'name' => $name,
};
$off += $FIELD_TYPE [$ftype]->{Size};
($fd, $off);
}
# ----------------------------------------------------------------------------
sub get_cardpagedef
{
my $def= shift;
# print ">>> processing card page definition\n";
my @pages;
my ($PW, $CP, $PC, @ps, @pc, $i);
($PW, $CP, $PC,
$ps[1], $ps[2], $ps[3], $ps[4],
$pc[1], $pc[2], $pc[3], $pc[4])= unpack ('vvvvvvvvvv', $def);
# print ">>>> CP=$CP PC=$PC\n";
for ($i= 1; $i <= $PC; $i++)
{
push (@pages, { 'num' => $i, 'start' => $ps[$i], 'size' => $pc[$i] });
# print ">>>>> [$i] start=$ps[$i] size=$pc[$i]\n";
}
\@pages;
}
# ----------------------------------------------------------------------------
sub show_field_def
{
local *FO= shift;
my $fdef= shift;
my $num= shift;
my $type= $fdef->{'ftype'};
my $ftype= $FIELD_TYPE[$type];
my $ttype= $ftype->{Desc} || "USER$type";
my $x_siz= $ftype->{Size};
my $x_off= sprintf ('0x%02X', $fdef->{off});
my $x_flg= sprintf ('0x%02X', $fdef->{flg});
my $x_name= $fdef->{name};
$x_name=~ s/[\x80-\xFF]/?/g;
printf FO "[%02d] %2d %-12s %3s %-24s %3d %s 0x%02X %s\n",
$num, $type, $ttype, $x_siz, "'$x_name'",
$fdef->{fid}, $x_off, $fdef->{res}, $x_flg;
# print FO "| '$x_name'\n";
# print FO "[$num] type= $ttype ($type) name='$fdef->{name}'"
# " id=$fdef->{fid} off=$x_off res=$fdef->{res} flg=$x_flg\n";
$x_off;
}
# ----------------------------------------------------------------------------
sub decode_type14 # analyze application specific field type 14
{
my $obj= shift;
local *FO= shift;
my $b= shift;
my $AD= $obj->{APT_Data};
my $lng= length ($b);
my ($off, $d, $v);
if (defined ($AD->{View_Table}))
{
print <{Header}))
{
my @View_Table;
for ($off= 0; $off+5 <= $lng; $off += 5)
{
$d= &fmt_date (substr ($b, $off, 3));
$v= unpack ('v', substr ($b, $off+3, 2));
last if ($v eq $no_val); # end marker
push (@View_Table, { 'date' => $d, num => $v } );
# print FO " date=$d num=$v\n";
}
$AD->{View_Table}= \@View_Table;
# &hex_dump ($b);
}
else
{
$d= &fmt_date (substr ($b, 0, 3));
$AD->{Head_Date}= $d;
$AD->{Header}= $b;
}
}
# ----------------------------------------------------------------------------
sub print_recheader
{
local *FH= shift;
my $txt= shift;
my $r= shift;
my @extra= @_;
my $fld;
my $type= $r->{'type'};
my $ttype= $REC_TYPE[$type] || "USER$type";
print "$txt\n";
print " type= $ttype ($type)\n";
foreach $fld ('status', 'length', 'idx', @extra)
{
print " $fld= $r->{$fld}\n";
}
}
# ----------------------------------------------------------------------------
sub dump_def
{
my $self= shift;
local *FO= shift;
my $level= shift;
my $hdr= $self->{Header};
my $Time= &fmt_time_stamp ($hdr->{'time'});
my $fld;
my $sig= substr ($hdr->{sig}, 0, 3);
my $x_ltable= sprintf ("0x%08lX", $hdr->{lookup_table_offset});
my $APT= &decode_apt ($hdr->{file_type});
print FO <{Filename}
Meta: $self->{Meta}
DB Header:
sig= $sig
time= $Time
lookup_table_offset= $x_ltable
file_type= $hdr->{file_type} $APT
EOX
foreach $fld (sort keys %$hdr)
{
print FO " $fld= $hdr->{$fld}\n" unless (defined ($XHDR{$fld}));
}
# &print_recheader (*FO, 'record header:', $hdr->{recheader});
# print FO 'self:: ', join (',', sort keys %$self), "\n";
# $level= 0 if ($self->{Meta} eq 'Encrypted' && $level < 10);
if ($level > 0)
{
$self->show_db_def (*FO);
# $self-> show_card_def (*FO);
}
if ($level > 1)
{
print FO $delim, "\n\n";
for ($fld= 0; $fld < 32; $fld++)
{
$self->dump_db (*FO, $fld);
}
}
}
# ----------------------------------------------------------------------------
sub dump_db
{
my $self= shift;
local *FO= shift;
my $type= shift;
my $idx= shift;
my $Types= $self->{Types};
my $Data= $Types->[$type];
my ($el, $i);
if (defined ($idx))
{
$el= $Data->[$idx];
&dump_db_rec (*FO, $idx, $el);
return;
}
$idx= 0;
foreach $el (@$Data)
{
&dump_db_rec (*FO, $idx, $el);
$idx++;
}
}
# ----------------------------------------------------------------------------
sub dump_db_rec
{
local *FO= shift;
my $i= shift;
my $el= shift;
unless (defined ($el))
{
print FO "data record [$i] not defined!\n";
return;
}
&print_recheader (*FO, "data record [$i]", $el, 'filters', 'flags');
# print FO "el= ", join (':', keys %$el), "\n";
print FO "data=\n";
&hex_dump ($el->{data}, *FO);
print FO $delim, "\n\n";
}
# ----------------------------------------------------------------------------
sub dump_data_record
{
my $b= shift;
my $ok= shift;
my $o= shift;
print "dump_data_record:\n";
print join (':', %$o), "\n";
# print "note: $nd\n" if ($nd);
unless ($ok && 0)
{
&hex_dump ($b);
}
}
# ----------------------------------------------------------------------------
sub hex_dump
{
my $data= shift;
local *FX= shift || *STDOUT;
my $off= 0;
my ($i, $c, $v);
while ($data)
{
my $char= '';
my $hex= '';
my $offx= sprintf ('%08X', $off);
$off += 0x10;
for ($i= 0; $i < 16; $i++)
{
$c= substr ($data, 0, 1);
if ($c ne '')
{
$data= substr ($data, 1);
$v= unpack ('C', $c);
$c= '.' if ($v < 0x20 || $v >= 0x7F);
$char .= $c;
$hex .= sprintf (' %02X', $v);
}
else
{
$char .= ' ';
$hex .= ' ';
}
}
print FX "$offx $hex |$char|\n";
}
}
# ----------------------------------------------------------------------------
# Decrypt the password of a HP 200LX Database.
# This function implements the algorithm in Curtis Cameron's dbcheck program.
# Returns a session key and the original password. I'm not quite sure
# if the original password is correct in all cases, this needs more testing.
sub decrypt_password
{
my ($b, $siz)= @_;
my ($pass, $key);
if ($siz != 17)
{
print "WARNING: decrypt_password (siz=$siz): ",
"password block size should be 17 byte!\n";
}
my ($i, $c, $k, $p);
for ($i= 0; $i < 17; $i++)
{
$c= unpack ('C', substr ($b, $i, 1));
$k= $c ^ $i ^ $CODE_A[$i];
# my $diag= sprintf ("%02X ^ %02X ^ A[%3d]=%02X", $c, $i, $i, $CODE_A[$i]);
# this CODE_B round cancels the effect of the same thing in decrypt_data
# $k ^= $CODE_B[$i];
# $diag .= sprintf (" ^ B[%2d]=%02X", $i, $CODE_B[$i]);
push (@$key, $k);
# push (@DIAG_K, $diag);
$p= $PW_CODE [$i] ^ $c;
$pass .= pack ('C', $p) if ($p > 0x00);
}
print "database is encrypted\npassword record, encrypted, siz=$siz\n";
&hex_dump ($b);
print "password record, decryption attempted (1)\n";
&hex_dump ($pass);
print "password= '$pass'\n";
($pass, $key);
}
# ----------------------------------------------------------------------------
# Decrypt the data portion of a HP 200LX Database record.
# This function implements the algorithm in Curtis Cameron's dbcheck program.
sub decrypt_data
{
my ($b, $siz, $code_ref)= @_;
my ($cc, $c0, $bb);
my ($c_a, $c_b, $c_k);
my ($ii, $i_127, $i_17);
for ($ii= 0; $ii < $siz; $ii++)
{
$c0= unpack ('C', substr ($b, $ii, 1));
$c_a= $CODE_A [$i_127];
$c_k= $code_ref->[$i_17];
$cc= $c0 ^ $c_k ^ $c_a;
# my $diag= sprintf ("[%4d] %02X ^ K[%2d]=(%s)=%02X ^ A[%3d]=%02X",
# $ii, $c0,
# $i_17, $DIAG_K[$i_17], $c_k,
# $i_127, $c_a);
# this CODE_B round cancels the effect of the same thing in decrypt_password
# $c_b= $CODE_B [$i_17];
# $cc ^= $c_b;
# $diag .= sprintf (" ^ B[%3d]=%02X", $i_17, $c_b);
if ($ii > 126)
{
my $ti;
for ($ti= $ii-127; $ti >= 0; $ti -= 127)
{
$c_b= $CODE_B [$ti % 17];
$cc ^= $c_b;
# $diag .= sprintf (" ^ B[%3d]=%02X", $ti%17, $c_b);
}
}
# $diag .= sprintf (" =: %02X %c", $cc, $cc); print $diag, "\n";
$bb .= pack ('C', $cc);
$i_17= 0 if (++$i_17 >= 17);
$i_127= 0 if (++$i_127 >= 127);
}
$bb;
}
# ----------------------------------------------------------------------------
sub recover_password
{
my $self= shift;
my $note_nr= shift;
my $ptx_fnm= shift;
my $key_fnm= shift;
# fetch encrypted note
my $T= $self->{Types} || die;
my $D= $T->[11]; # array of data records
my $N= $T->[9]; # array of note records
my $enc_txt= $N->[$note_nr]->{data};
# print "encrypted text:\n"; &hex_dump ($enc_txt);
# fetch plain text
my $ptx_txt;
open (FI, $ptx_fnm) || die;
while () { $ptx_txt .= $_; }
close (FI);
# print "plain text:\n"; &hex_dump ($ptx_txt);
# recover the key
my ($pp, $cc, $ee, $ii, $key);
my $ll_enc= length ($enc_txt);
my $ll_ptx= length ($ptx_txt);
print "text size enc=$ll_enc plain=$ll_ptx\n";
for ($ii= 0; $ii < $ll_ptx; $ii++)
{
$pp= unpack ('C', substr ($ptx_txt, $ii, 1));
$ee= unpack ('C', substr ($enc_txt, $ii, 1));
$cc= $pp ^ $ee ^ $ii;
$key .= pack ('C', $cc);
}
# print "the key is\n"; &hex_dump ($key);
print "dumping key to $key_fnm\n";
open (FO, ">$key_fnm") || die;
binmode (FI); # MS-DOS systems need this, T2D: how about Mac?
print FO $key;
close (FO);
}
# ----------------------------------------------------------------------------
sub get_field_type
{
my $ty= shift;
$FIELD_TYPE[$ty];
}
# Autoload methods go after =cut, and are processed by the autosplit program.
1;
__END__
# POD Section
=head1 NAME
HP200LX::DB - Perl module to access HP-200 LX database files
=head1 SYNOPSIS
use HP200LX::DB;
interface functions:
$db= HP200LX::DB::openDB ($fnm) read database and return an DB object
$db= new HP200LX::DB ($fnm) create database and return an DB object
$db->saveDB ($fnm) save DB object as a (new) file
array tie implementation to access database data records:
tie (@dbd, HP200LX::DB, $db); access database data in array form
TIEARRAY stub to get an tie for the database
FETCH retrieve a record
STORE store a record
additional data retrieval and storage methods:
$db->FETCH_data_raw ($idx) retrieve raw data record
$db->FETCH_note_raw ($idx) retrieve raw note record
$db->STORE_data_raw ($idx, $data) store raw data record
$db->STORE_note_raw ($idx, $note) store raw note record
$db->get_last_index () return highest index
internal methods:
$db->show_db_def (*FH) show database definition
$db->show_card_def (*FH) show card layout definition
$db->get_field_def ($num) retrieve field definition
$db->print_summary ($header) print DB summary line;
print also header if $header==1
show_field_def show a field definition
fetch_data used by FETCH to get db record
store_data used by STORE to save db record
get_recheader read gdb internal record structure
put_recheader store gdb internal record structure
fmt_time_stamp create a readable date and time string
get_fielddef decode a field definition record
get_carddef decode a card definiton record
Diagnostics and Debugging methods:
$db->dump_db (*FH, $type) dump a complete data base
$db->dump_data dump all data records
$db->recover_password attempt to reconstruct DB password
Diagnostics and Debugging functions:
print_recheader (*FH, $txt, $rec) print details about a record
dump_def dump database definition
dump_data_record print and dump data record
hex_dump perform a hex dump of some data
decrypt_password attempt to decote the DB password
decrypt_data attempt to decode a DB recrod
=head1 DESCRIPTION
DB.pm implements the Perl package HP200LX::DB which is intended
to provide a Perl 5 interface for files in the generic database
format of the HP 200LX palmtop computer. The Perl modules are
intended to be used on a work station such as a PC or a Unix
machine to read and write data records from and to a database
file. These modules are not intended to be run directly on the
palmtop!
Please see the README file for a few more details or consult
the examples which can be found at the web site mentioned below.
=head1 Copyright
Copyright (c) 1998-2001 Gerhard Gonter. All rights reserved.
This is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
=head1 AUTHOR
Gerhard Gonter, g.gonter@ieee.org
=head1 SEE ALSO
http://sourceforge.net/projects/hp200lx-db/,
perl(1).
=cut
|