[BACK]Return to Keyring.pm CVS log [TXT][DIR] Up to [local] / palm / Palm-Keyring / lib / Palm

Annotation of palm/Palm-Keyring/lib/Palm/Keyring.pm, Revision 1.23

1.14      andrew      1: package Palm::Keyring;
                      2:
1.23    ! andrew      3: # $RedRiver: Keyring.pm,v 1.22 2007/02/01 01:56:11 andrew Exp $
1.1       andrew      4: #
1.14      andrew      5: # Perl class for dealing with Keyring for Palm OS databases.
1.1       andrew      6: #
                      7: #   This started as Memo.pm, I just made it work for Keyring.
                      8:
                      9: use strict;
1.14      andrew     10: use warnings;
                     11: use Carp;
                     12:
                     13: use base qw/ Palm::StdAppInfo /;
1.1       andrew     14:
                     15: use Digest::MD5 qw(md5);
1.2       andrew     16: use Crypt::DES;
1.14      andrew     17: use Readonly;
                     18:
                     19: Readonly my $ENCRYPT    => 1;
                     20: Readonly my $DECRYPT    => 0;
                     21: Readonly my $MD5_CBLOCK => 64;
                     22: Readonly my $kSalt_Size => 4;
                     23: Readonly my $EMPTY      => q{};
                     24: Readonly my $SPACE      => q{ };
                     25: Readonly my $NULL       => chr 0;
                     26:
                     27: # One liner, to allow MakeMaker to work.
1.19      andrew     28: our $VERSION = 0.92;
1.1       andrew     29:
1.14      andrew     30: sub new {
                     31:     my $classname = shift;
                     32:     my $pass      = shift;
1.1       andrew     33:
1.14      andrew     34:     # Create a generic PDB. No need to rebless it, though.
                     35:     my $self = $classname->SUPER::new(@_);
1.1       andrew     36:
1.14      andrew     37:     $self->{'name'}    = 'Keys-Gtkr';    # Default
                     38:     $self->{'creator'} = 'Gtkr';
                     39:     $self->{'type'}    = 'Gkyr';
                     40:
                     41:     # The PDB is not a resource database by
                     42:     # default, but it's worth emphasizing,
                     43:     # since MemoDB is explicitly not a PRC.
                     44:     $self->{'attributes'}{'resource'} = 0;
1.1       andrew     45:
1.14      andrew     46:     # Initialize the AppInfo block
                     47:     $self->{'appinfo'} = {};
1.1       andrew     48:
1.14      andrew     49:     # Add the standard AppInfo block stuff
                     50:     Palm::StdAppInfo::seed_StdAppInfo( $self->{'appinfo'} );
1.1       andrew     51:
1.14      andrew     52:     # Set the version
                     53:     $self->{'version'} = 4;
1.1       andrew     54:
1.14      andrew     55:     if ( defined $pass ) {
1.16      andrew     56:         $self->Password($pass);
1.14      andrew     57:     }
1.1       andrew     58:
1.14      andrew     59:     return $self;
                     60: }
1.1       andrew     61:
1.14      andrew     62: sub import {
                     63:     Palm::PDB::RegisterPDBHandlers( __PACKAGE__, [ 'Gtkr', 'Gkyr' ], );
                     64:     return 1;
                     65: }
1.1       andrew     66:
1.16      andrew     67: sub ParseRecord {
1.14      andrew     68:     my $self     = shift;
                     69:
1.16      andrew     70:     my $rec = $self->SUPER::ParseRecord(@_);
                     71:
                     72:     # skip the 0 record that holds the password
                     73:     return $rec if ! exists $self->{'records'};
                     74:     return $rec if ! exists $rec->{'data'};
1.14      andrew     75:
1.16      andrew     76:     my ( $name, $encrypted ) = split /$NULL/xm, $rec->{'data'}, 2;
1.1       andrew     77:
1.16      andrew     78:     return $rec if ! $encrypted;
1.19      andrew     79:     delete $rec->{'data'};
                     80:     $rec->{'name'} = $name;
1.16      andrew     81:     $rec->{'encrypted'} = $encrypted;
1.12      andrew     82:
1.16      andrew     83:     return $rec;
1.14      andrew     84: }
1.11      andrew     85:
1.16      andrew     86: sub PackRecord {
                     87:     my $self = shift;
                     88:     my $rec  = shift;
                     89:
1.23    ! andrew     90:     if ($rec->{'encrypted'}) {
1.22      andrew     91:         if (! defined $rec->{'name'}) {
                     92:             $rec->{'name'} = $EMPTY;
                     93:         }
1.19      andrew     94:         $rec->{'data'} = join $NULL, $rec->{'name'}, $rec->{'encrypted'};
                     95:         delete $rec->{'name'};
1.16      andrew     96:         delete $rec->{'encrypted'};
                     97:     }
1.1       andrew     98:
1.16      andrew     99:     return $self->SUPER::PackRecord($rec, @_);
1.14      andrew    100: }
1.1       andrew    101:
1.14      andrew    102: sub Encrypt {
                    103:     my $self = shift;
1.16      andrew    104:     my $rec  = shift;
                    105:     my $data = shift;
                    106:     my $pass = shift || $self->{'password'};
                    107:
                    108:     if ( ! $pass) {
                    109:         croak("'password' not set!\n");
                    110:     }
                    111:
                    112:     if ( ! $rec) {
                    113:         croak("Needed parameter 'record' not passed!\n");
                    114:     }
1.14      andrew    115:
1.16      andrew    116:     if ( ! $data) {
                    117:         croak("Needed parameter 'data' not passed!\n");
1.14      andrew    118:     }
                    119:
1.16      andrew    120:     if ( ! $self->Password($pass)) {
                    121:         croak("Incorrect Password!\n");
                    122:     }
1.14      andrew    123:
1.22      andrew    124:     $self->{'digest'}   ||= _calc_keys( $pass );
1.14      andrew    125:
1.16      andrew    126:     $data->{'account'}  ||= $EMPTY;
                    127:     $data->{'password'} ||= $EMPTY;
                    128:     $data->{'notes'}    ||= $EMPTY;
1.1       andrew    129:
1.22      andrew    130:     my $changed      = 0;
                    131:     my $need_newdate = 0;
                    132:     my $acct = {};
                    133:     if ($rec->{'encrypted'}) {
                    134:         $acct = $self->Decrypt($rec, $pass);
                    135:         foreach my $key (keys %{ $data }) {
                    136:             next if $key eq 'lastchange';
                    137:             if ($data->{$key} ne $acct->{$key}) {
                    138:                 $changed = 1;
                    139:                 last;
                    140:             }
                    141:         }
                    142:         if ( exists $data->{'lastchange'} && exists $acct->{'lastchange'} && (
                    143:             $data->{'lastchange'}->{day}   != $acct->{'lastchange'}->{day}   ||
                    144:             $data->{'lastchange'}->{month} != $acct->{'lastchange'}->{month} ||
                    145:             $data->{'lastchange'}->{year}  != $acct->{'lastchange'}->{year}
                    146:         )) {
                    147:             $changed = 1;
                    148:             $need_newdate = 0;
                    149:         } else {
                    150:             $need_newdate = 1;
                    151:         }
                    152:
                    153:     } else {
                    154:         $changed = 1;
                    155:     }
                    156:
                    157:     # no need to re-encrypt if it has not changed.
                    158:     return 1 if ! $changed;
                    159:
1.21      andrew    160:     my ($day, $month, $year);
                    161:
1.22      andrew    162:     if ($data->{'lastchange'} && ! $need_newdate ) {
                    163:         $day   = $data->{'lastchange'}->{'day'}   || 1;
                    164:         $month = $data->{'lastchange'}->{'month'} || 0;
                    165:         $year  = $data->{'lastchange'}->{'year'}  || 0;
                    166:
                    167:         # XXX Need to actually validate the above information somehow
                    168:         if ($year >= 1900) {
                    169:             $year -= 1900;
                    170:         }
                    171:     } else {
                    172:         $need_newdate = 1;
                    173:     }
                    174:
                    175:     if ($need_newdate) {
1.21      andrew    176:         ($day, $month, $year) = (localtime)[3,4,5];
                    177:     }
1.22      andrew    178:     $year -= 4;
                    179:     $month++;
                    180:
1.19      andrew    181:
                    182:     my $p = $day | ($month << 5) | ($year << 9);
                    183:     my $packeddate = pack 'n', $p;
                    184:
1.16      andrew    185:     my $plaintext = join $NULL,
1.19      andrew    186:         $data->{'account'}, $data->{'password'}, $data->{'notes'}, $packeddate;
1.1       andrew    187:
1.16      andrew    188:     my $encrypted = _crypt3des( $plaintext, $self->{'digest'}, $ENCRYPT );
1.11      andrew    189:
1.16      andrew    190:     return if ! $encrypted;
1.1       andrew    191:
1.19      andrew    192:     $rec->{'attributes'}{'Dirty'} = 1;
                    193:     $rec->{'attributes'}{'dirty'} = 1;
                    194:     $rec->{'name'}    ||= $data->{'name'};
1.16      andrew    195:     $rec->{'encrypted'} = $encrypted;
1.19      andrew    196:
1.14      andrew    197:     return 1;
                    198: }
1.1       andrew    199:
1.14      andrew    200: sub Decrypt {
                    201:     my $self = shift;
1.16      andrew    202:     my $rec  = shift;
                    203:     my $pass = shift || $self->{'password'};
                    204:
                    205:     if ( ! $pass) {
                    206:         croak("'password' not set!\n");
                    207:     }
                    208:
                    209:     if ( ! $rec) {
1.19      andrew    210:         croak("Needed parameter 'record' not passed!\n");
1.16      andrew    211:     }
1.14      andrew    212:
1.16      andrew    213:     if ( ! $self->Password($pass)) {
                    214:         croak("Invalid Password!\n");
1.14      andrew    215:     }
                    216:
1.16      andrew    217:     if ( ! $rec->{'encrypted'} ) {
                    218:         croak("No encrypted content!");
                    219:     }
1.14      andrew    220:
1.16      andrew    221:     $self->{'digest'} ||= _calc_keys( $pass );
1.14      andrew    222:
1.16      andrew    223:     my $decrypted =
                    224:         _crypt3des( $rec->{'encrypted'}, $self->{'digest'}, $DECRYPT );
1.19      andrew    225:     my ( $account, $password, $notes, $packeddate ) = split /$NULL/xm,
1.16      andrew    226:           $decrypted, 4;
1.14      andrew    227:
1.19      andrew    228:     my %Modified;
                    229:     if ($packeddate) {
                    230:         my $u = unpack 'n', $packeddate;
                    231:         my $year  = (($u & 0xFE00) >> 9) + 4; # since 1900
                    232:         my $month = (($u & 0x01E0) >> 5) - 1; # 0-11
                    233:         my $day   = (($u & 0x001F) >> 0);     # 1-31
                    234:
                    235:         %Modified = (
                    236:             year   => $year,
                    237:             month  => $month || 0,
                    238:             day    => $day   || 1,
                    239:         );
                    240:     }
                    241:
1.16      andrew    242:     return {
1.20      andrew    243:         name       => $rec->{'name'},
                    244:         account    => $account,
                    245:         password   => $password,
                    246:         notes      => $notes,
                    247:         lastchange => \%Modified,
1.16      andrew    248:     };
                    249: }
1.14      andrew    250:
1.16      andrew    251: sub Password {
                    252:     my $self = shift;
                    253:     my $pass = shift || $self->{'password'};
                    254:     my $new_pass = shift;
1.14      andrew    255:
1.16      andrew    256:     if (! exists $self->{'records'}) {
                    257:         # Give the PDB the first record that will hold the encrypted password
                    258:         $self->{'records'} = [ $self->new_Record ];
                    259:
                    260:         return $self->_password_update($pass);
                    261:     }
                    262:
                    263:     if ($new_pass) {
                    264:         my @accts = ();
                    265:         foreach my $i (0..$#{ $self->{'records'} }) {
                    266:             if ($i == 0) {
                    267:                 push @accts, undef;
                    268:                 next;
                    269:             }
                    270:             my $acct = $self->Decrypt($self->{'records'}->[$i], $pass);
                    271:             if ( ! $acct ) {
1.19      andrew    272:                 croak("Couldn't decrypt $self->{'records'}->[$i]->{'name'}");
1.16      andrew    273:             }
                    274:             push @accts, $acct;
                    275:         }
1.14      andrew    276:
1.16      andrew    277:         if ( ! $self->_password_update($new_pass)) {
                    278:             croak("Couldn't set new password!");
                    279:         }
                    280:         $pass = $new_pass;
1.1       andrew    281:
1.16      andrew    282:         foreach my $i (0..$#accts) {
                    283:             next if $i == 0;
1.22      andrew    284:             delete $self->{'records'}->[$i]->{'encrypted'};
1.16      andrew    285:             $self->Encrypt($self->{'records'}->[$i], $accts[$i], $pass);
                    286:         }
1.14      andrew    287:     }
1.1       andrew    288:
1.16      andrew    289:     return $self->_password_verify($pass);
1.1       andrew    290: }
                    291:
1.14      andrew    292: sub _calc_keys {
                    293:     my $pass = shift;
                    294:     if (! defined $pass) { croak('No password defined!'); };
                    295:
                    296:     my $digest = md5($pass);
                    297:
                    298:     my ( $key1, $key2 ) = unpack 'a8a8', $digest;
                    299:
                    300:     #--------------------------------------------------
                    301:     # print "key1: $key1: ", length $key1, "\n";
                    302:     # print "key2: $key2: ", length $key2, "\n";
                    303:     #--------------------------------------------------
                    304:
                    305:     $digest = unpack 'H*', $key1 . $key2 . $key1;
                    306:
                    307:     #--------------------------------------------------
                    308:     # print "Digest: ", $digest, "\n";
                    309:     # print length $digest, "\n";
                    310:     #--------------------------------------------------
                    311:
                    312:     return $digest;
1.3       andrew    313: }
                    314:
1.16      andrew    315: sub _password_verify {
1.14      andrew    316:     my $self = shift;
                    317:     my $pass = shift;
                    318:
                    319:     if (! $pass) { croak('No password specified!'); };
1.11      andrew    320:
1.16      andrew    321:     if (defined $self->{'password'} && $pass eq $self->{'password'}) {
                    322:         # already verified this password
                    323:         return 1;
                    324:     }
                    325:
1.14      andrew    326:     # AFAIK the thing we use to test the password is
                    327:     #     always in the first entry
                    328:     my $data = $self->{'records'}->[0]->{'data'};
1.11      andrew    329:
1.14      andrew    330:     #die "No encrypted password in file!" unless defined $data;
1.16      andrew    331:     if ( ! defined $data) { return; };
1.11      andrew    332:
1.14      andrew    333:     $data =~ s/$NULL$//xm;
1.11      andrew    334:
1.14      andrew    335:     my $salt = substr $data, 0, $kSalt_Size;
1.11      andrew    336:
1.14      andrew    337:     my $msg = $salt . $pass;
1.11      andrew    338:
1.14      andrew    339:     $msg .= "\0" x ( $MD5_CBLOCK - length $msg );
1.11      andrew    340:
1.14      andrew    341:     my $digest = md5($msg);
1.11      andrew    342:
1.14      andrew    343:     if ( $data eq $salt . $digest ) {
1.11      andrew    344:
1.14      andrew    345: # May as well generate the keys we need now, since we know the password is right
                    346:         $self->{'digest'} = _calc_keys($pass);
                    347:         if ( $self->{'digest'} ) {
                    348:             $self->{'password'} = $pass;
                    349:             return 1;
                    350:         }
                    351:     }
                    352:     return;
1.6       andrew    353: }
                    354:
1.16      andrew    355: sub _password_update {
1.14      andrew    356:
                    357:     # It is very important to Encrypt after calling this
                    358:     #     (Although it is generally only called by Encrypt)
                    359:     # because otherwise the data will be out of sync with the
                    360:     # password, and that would suck!
                    361:     my $self = shift;
                    362:     my $pass = shift;
                    363:
1.16      andrew    364:     if (! defined $pass) { croak('No password specified!'); };
1.14      andrew    365:
                    366:     my $salt;
                    367:     for ( 1 .. $kSalt_Size ) {
                    368:         $salt .= chr int rand 255;
                    369:     }
                    370:
                    371:     my $msg = $salt . $pass;
1.11      andrew    372:
1.14      andrew    373:     $msg .= "\0" x ( $MD5_CBLOCK - length $msg );
1.11      andrew    374:
1.14      andrew    375:     my $digest = md5($msg);
1.11      andrew    376:
1.14      andrew    377:     my $data = $salt . $digest;    # . "\0";
1.11      andrew    378:
1.14      andrew    379:     # AFAIK the thing we use to test the password is
                    380:     #     always in the first entry
                    381:     $self->{'records'}->[0]->{'data'} = $data;
1.11      andrew    382:
1.14      andrew    383:     $self->{'password'} = $pass;
                    384:     $self->{'digest'}   = _calc_keys( $self->{'password'} );
1.11      andrew    385:
1.14      andrew    386:     return 1;
1.1       andrew    387: }
                    388:
1.14      andrew    389: sub _crypt3des {
                    390:     my ( $plaintext, $passphrase, $flag ) = @_;
                    391:
                    392:     $passphrase   .= $SPACE x ( 16 * 3 );
                    393:     my $cyphertext = $EMPTY;
                    394:
                    395:     my $size = length $plaintext;
1.11      andrew    396:
1.14      andrew    397:     #print "STRING: '$plaintext' - Length: " . (length $plaintext) . "\n";
                    398:
                    399:     my @C;
                    400:     for ( 0 .. 2 ) {
                    401:         $C[$_] =
                    402:           new Crypt::DES( pack 'H*', ( substr $passphrase, 16 * $_, 16 ));
                    403:     }
                    404:
                    405:     for ( 0 .. ( ($size) / 8 ) ) {
                    406:         my $pt = substr $plaintext, $_ * 8, 8;
                    407:
                    408:         #print "PT: '$pt' - Length: " . length($pt) . "\n";
                    409:         if (! length $pt) { next; };
                    410:         if ( (length $pt) < 8 ) {
1.16      andrew    411:             if ($flag == $DECRYPT) { croak('record not 8 byte padded'); };
1.14      andrew    412:             my $len = 8 - (length $pt);
                    413:
                    414:             #print "LENGTH: $len\n";
                    415:             #print "Binary:    '" . unpack("b*", $pt) . "'\n";
                    416:             $pt .= ($NULL x $len);
                    417:
                    418:             #print "PT: '$pt' - Length: " . length($pt) . "\n";
                    419:             #print "Binary:    '" . unpack("b*", $pt) . "'\n";
                    420:         }
                    421:         if ( $flag == $ENCRYPT ) {
                    422:             $pt = $C[0]->encrypt($pt);
                    423:             $pt = $C[1]->decrypt($pt);
                    424:             $pt = $C[2]->encrypt($pt);
                    425:         }
                    426:         else {
                    427:             $pt = $C[0]->decrypt($pt);
                    428:             $pt = $C[1]->encrypt($pt);
                    429:             $pt = $C[2]->decrypt($pt);
                    430:         }
                    431:
                    432:         #print "PT: '$pt' - Length: " . length($pt) . "\n";
                    433:         $cyphertext .= $pt;
                    434:     }
                    435:
                    436:     $cyphertext =~ s/$NULL+$//xm;
1.11      andrew    437:
1.14      andrew    438:     #print "CT: '$cyphertext' - Length: " . length($cyphertext) . "\n";
1.11      andrew    439:
1.14      andrew    440:     return $cyphertext;
                    441: }
1.11      andrew    442:
1.14      andrew    443: 1;
                    444: __END__
1.11      andrew    445:
1.14      andrew    446: =head1 NAME
1.11      andrew    447:
1.14      andrew    448: Palm::Keyring - Handler for Palm Keyring databases.
1.1       andrew    449:
1.14      andrew    450: =head1 DESCRIPTION
1.7       andrew    451:
1.14      andrew    452: The Keyring PDB handler is a helper class for the Palm::PDB package. It
                    453: parses Keyring for Palm OS databases.  See
                    454: L<http://gnukeyring.sourceforge.net/>.
1.1       andrew    455:
1.14      andrew    456: It has the standard Palm::PDB methods with 2 additional public methods.
                    457: Decrypt and Encrypt.
1.1       andrew    458:
1.16      andrew    459: It currently supports the v4 Keyring databases.  The v5 databases from
                    460: the pre-release keyring-2.0 are not supported.
                    461:
                    462: This module doesn't store the decrypted content.  It only keeps it until it
                    463: returns it to you or encrypts it.
1.1       andrew    464:
1.14      andrew    465: =head1 SYNOPSIS
1.1       andrew    466:
1.16      andrew    467:     use Palm::PDB;
                    468:     use Palm::Keyring;
1.17      andrew    469:
                    470:     my $pass = 'password';
1.18      andrew    471:     my $file = 'Keys-Gtkr.pdb';
                    472:     my $pdb  = new Palm::PDB;
1.16      andrew    473:     $pdb->Load($file);
1.17      andrew    474:
                    475:     foreach (0..$#{ $pdb->{'records'} }) {
                    476:         next if $_ = 0; # skip the password record
                    477:         my $rec  = $pdb->{'records'}->[$_];
                    478:         my $acct = $pdb->Decrypt($rec, $pass);
1.19      andrew    479:         print $rec->{'name'}, ' - ', $acct->{'account'}, "\n";
1.16      andrew    480:     }
1.1       andrew    481:
1.14      andrew    482: =head1 SUBROUTINES/METHODS
1.1       andrew    483:
1.14      andrew    484: =head2 new
1.11      andrew    485:
1.16      andrew    486:     $pdb = new Palm::Keyring([$password]);
1.11      andrew    487:
1.14      andrew    488: Create a new PDB, initialized with the various Palm::Keyring fields
                    489: and an empty record list.
1.11      andrew    490:
1.14      andrew    491: Use this method if you're creating a Keyring PDB from scratch otherwise you
1.16      andrew    492: can just use Palm::PDB::new() before calling Load().
1.11      andrew    493:
1.16      andrew    494: =head2 Encrypt
1.11      andrew    495:
1.16      andrew    496:     $pdb->Encrypt($rec, $acct, [$password]);
1.11      andrew    497:
1.16      andrew    498: Encrypts an account into a record, either with the password previously
                    499: used, or with a password that is passed.
1.1       andrew    500:
1.16      andrew    501: $rec is a record from $pdb->{'records'} or a newly generated record.
                    502: $acct is a hashref in the format below.
1.1       andrew    503:
1.16      andrew    504:     my $acct = {
1.20      andrew    505:         name       => $rec->{'name'},
                    506:         account    => $account,
                    507:         password   => $password,
                    508:         notes      => $notes,
                    509:         lastchange => {
                    510:             year  => 107, # years since 1900
                    511:             month =>   0, # 0-11, 0 = January, 11 = December
1.21      andrew    512:             day   =>  30, # 1-31, same as localtime
1.20      andrew    513:         },
1.16      andrew    514:     };
1.7       andrew    515:
1.22      andrew    516: If you have changed anything other than the lastchange, or don't pass in a
                    517: lastchange record, Encrypt() will generate a new lastchange for you.
                    518:
                    519: If you pass in a lastchange field that is different than the one in the
                    520: record, it will honor what you passed in.
                    521:
                    522: It also only uses the $acct->{'name'} if there is not already a $rec->{'name'}.
                    523:
1.16      andrew    524: =head2 Decrypt
1.1       andrew    525:
1.16      andrew    526:     my $acct = $pdb->Decrypt($rec[, $password]);
1.1       andrew    527:
1.16      andrew    528: Decrypts the record and returns a hashref for the account as described
1.20      andrew    529: under Encrypt().
1.1       andrew    530:
1.16      andrew    531:     foreach (0..$#{ $pdb->{'records'}) {
                    532:         next if $_ == 0;
                    533:         my $rec = $pdb->{'records'}->[$_];
                    534:         my $acct = $pdb->Decrypt($rec[, $password]);
                    535:         # do something with $acct
                    536:     }
1.1       andrew    537:
1.16      andrew    538: =head2 Password
1.1       andrew    539:
1.16      andrew    540:     $pdb->Password([$password[, $new_password]]);
1.1       andrew    541:
1.16      andrew    542: Either sets the password to be used to crypt, or if you pass $new_password,
                    543: changes the password on the database.
1.1       andrew    544:
1.16      andrew    545: If you have created a new $pdb, and you didn't set a password when you
                    546: called new(), you only need to pass one password and it will set that as
                    547: the password.
1.1       andrew    548:
1.16      andrew    549: If nothing is passed, and there has been a password used before,
                    550: it just verifies that the password was correct.
1.1       andrew    551:
1.14      andrew    552: =head1 DEPENDENCIES
1.1       andrew    553:
1.14      andrew    554: Palm::StdAppInfo
1.1       andrew    555:
1.14      andrew    556: Digest::MD5
1.9       andrew    557:
1.14      andrew    558: Crypt::DES
1.4       andrew    559:
1.14      andrew    560: Readonly
1.10      andrew    561:
1.14      andrew    562: =head1 BUGS AND LIMITATIONS
1.1       andrew    563:
1.14      andrew    564: Once this module is uploaded, you can
                    565: Please report any bugs or feature requests to
                    566: C<bug-palm-keyring at rt.cpan.org>, or through the web interface at
                    567: L<http://rt.cpan.org>.  I will be notified, and then you'll automatically be
                    568: notified of progress on your bug as I make changes.
1.1       andrew    569:
                    570: =head1 AUTHOR
                    571:
1.12      andrew    572: Andrew Fresh E<lt>andrew@mad-techies.orgE<gt>
1.1       andrew    573:
1.14      andrew    574: =head1 LICENSE AND COPYRIGHT
                    575:
                    576: Copyright 2004, 2005, 2006, 2007 Andrew Fresh, All Rights Reserved.
                    577:
1.15      andrew    578: This program is free software; you can redistribute it and/or
                    579: modify it under the same terms as Perl itself.
1.14      andrew    580:
1.1       andrew    581: =head1 SEE ALSO
                    582:
                    583: Palm::PDB(3)
                    584:
                    585: Palm::StdAppInfo(3)
1.11      andrew    586:
                    587: The Keyring for Palm OS website:
                    588: L<http://gnukeyring.sourceforge.net/>

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>