"Fossies" - the Fresh Open Source Software Archive

Member "note-1.3.26/lib/NOTEDB/pwsafe3.pm" (3 Jun 2015, 13119 Bytes) of package /linux/privat/note-1.3.26.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Perl source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "pwsafe3.pm" see the Fossies "Dox" file reference documentation.

    1 # Perl module for note
    2 # pwsafe3 backend. see docu: perldoc NOTEDB::pwsafe3
    3 
    4 package NOTEDB::pwsafe3;
    5 
    6 $NOTEDB::pwsafe3::VERSION = "1.08";
    7 use strict;
    8 use Data::Dumper;
    9 use Time::Local;
   10 use Crypt::PWSafe3;
   11 
   12 use NOTEDB;
   13 
   14 use Fcntl qw(LOCK_EX LOCK_UN);
   15 
   16 use Exporter ();
   17 use vars qw(@ISA @EXPORT);
   18 @ISA = qw(NOTEDB Exporter);
   19 
   20 
   21 
   22 
   23 
   24 sub new {
   25     my($this, %param) = @_;
   26 
   27     my $class = ref($this) || $this;
   28     my $self = {};
   29     bless($self,$class);
   30 
   31     $self->{dbname}  = $param{dbname}   || File::Spec->catfile($ENV{HOME}, ".notedb");
   32 
   33     $self->{mtime}    = $self->get_stat();
   34     $self->{unread}   = 1;
   35     $self->{data}     = {};
   36     $self->{LOCKFILE} = $param{dbname} . "~LOCK";
   37     $self->{keepkey} = 0;
   38 
   39     return $self;
   40 }
   41 
   42 
   43 sub DESTROY {
   44   # clean the desk!
   45 }
   46 
   47 sub version {
   48     my $this = shift;
   49     return $NOTEDB::pwsafe3::VERSION;
   50 }
   51 
   52 sub get_stat {
   53   my ($this) = @_;
   54   if(-e $this->{dbname}) {
   55     return (stat($this->{dbname}))[9];
   56   }
   57   else {
   58     return time;
   59   }
   60 }
   61 
   62 sub filechanged {
   63   my ($this) = @_;
   64   my $current = $this->get_stat();
   65 
   66   if ($current > $this->{mtime}) {
   67     $this->{mtime} = $current;
   68     return $current;
   69   }
   70   else {
   71     return 0;
   72   }
   73 }
   74 
   75 sub set_del_all {
   76     my $this = shift;
   77     unlink $this->{dbname};
   78     open(TT,">$this->{dbname}") or die "Could not create $this->{dbname}: $!\n";
   79     close (TT);
   80 }
   81 
   82 
   83 sub get_single {
   84     my($this, $num) = @_;
   85     my($address, $note, $date, $n, $t, $buffer, );
   86 
   87     my %data = $this->get_all();
   88 
   89     return ($data{$num}->{note}, $data{$num}->{date});
   90 }
   91 
   92 
   93 sub get_all {
   94     my $this = shift;
   95     my($num, $note, $date, %res);
   96     if ($this->unchanged) {
   97     return %{$this->{cache}};
   98     }
   99 
  100     my %data = $this->_retrieve();
  101 
  102     foreach my $num (keys %data) {
  103     ($res{$num}->{date}, $res{$num}->{note}) = $this->_pwsafe3tonote($data{$num}->{note});
  104     }
  105 
  106     $this->cache(%res);
  107     return %res;
  108 }
  109 
  110 sub import_data {
  111   my ($this, $data) = @_;
  112 
  113   my $fh;
  114 
  115   if (-s $this->{dbname}) {
  116     $fh = new FileHandle "<$this->{dbname}" or die "could not open $this->{dbname}\n";
  117     flock $fh, LOCK_EX;
  118   }
  119 
  120   my $key   = $this->_getpass();
  121 
  122   eval {
  123     my $vault = new Crypt::PWSafe3(password => $key, file => $this->{dbname});
  124 
  125     foreach my $num (keys %{$data}) {
  126       my $checksum = $this->get_nextnum();
  127       my %record = $this->_notetopwsafe3($checksum, $data->{$num}->{note}, $data->{$num}->{date});
  128 
  129       my $rec = new Crypt::PWSafe3::Record();
  130       $rec->uuid($record{uuid});
  131       $vault->addrecord($rec);
  132       $vault->modifyrecord($record{uuid}, %record);
  133     }
  134 
  135     $vault->save();
  136   };
  137   if ($@) {
  138     print "Exception caught:\n$@\n";
  139     exit 1;
  140   }
  141 
  142   eval {
  143     flock $fh, LOCK_UN;
  144     $fh->close();
  145   };
  146 
  147   $this->{keepkey} = 0;
  148   $this->{key} = 0;
  149 }
  150 
  151 sub get_nextnum {
  152     my $this = shift;
  153     my($num, $te, $me, $buffer);
  154 
  155     my $ug    = new Data::UUID;
  156 
  157     $this->{nextuuid} =  unpack('H*', $ug->create());
  158     $num = $this->_uuid( $this->{nextuuid} );
  159 
  160     return $num;
  161 }
  162 
  163 sub get_search {
  164     my($this, $searchstring) = @_;
  165     my($buffer, $num, $note, $date, %res, $t, $n, $match);
  166 
  167     my $regex = $this->generate_search($searchstring);
  168     eval $regex;
  169     if ($@) {
  170     print "invalid expression: \"$searchstring\"!\n";
  171     return;
  172     }
  173     $match = 0;
  174 
  175     if ($this->unchanged) {
  176     foreach my $num (keys %{$this->{cache}}) {
  177         $_ = $this->{cache}{$num}->{note};
  178         eval $regex;
  179         if ($match) {
  180         $res{$num}->{note} = $this->{cache}{$num}->{note};
  181         $res{$num}->{date} = $this->{cache}{$num}->{date}
  182         }
  183         $match = 0;
  184     }
  185     return %res;
  186     }
  187 
  188     my %data =  $this->get_all();
  189 
  190     foreach my $num(sort keys %data) {
  191     $_ = $data{$num}->{note};
  192     eval $regex;
  193     if($match)
  194       {
  195           $res{$num}->{note} = $data{$num}->{note};
  196           $res{$num}->{date} = $data{$num}->{data};
  197       }
  198     $match = 0;
  199     }
  200 
  201     return %res;
  202 }
  203 
  204 
  205 
  206 
  207 sub set_edit {
  208     my($this, $num, $note, $date) = @_;
  209 
  210     my %data = $this->_retrieve();
  211 
  212     my %record = $this->_notetopwsafe3($num, $note, $date);
  213 
  214     if (exists $data{$num}) {
  215       $data{$num}->{note} = \%record;
  216       $this->_store(\%record);
  217     }
  218     else {
  219       %record = $this->_store(\%record, 1);
  220     }
  221 
  222     $this->changed;
  223 }
  224 
  225 
  226 sub set_new {
  227     my($this, $num, $note, $date) = @_;
  228     $this->set_edit($num, $note, $date);
  229 }
  230 
  231 
  232 sub set_del {
  233   my($this, $num) = @_;
  234 
  235   my $uuid  = $this->_getuuid($num);
  236   if(! $uuid) {
  237     print "Note $num does not exist!\n";
  238     return;
  239   }
  240 
  241   my $fh = new FileHandle "<$this->{dbname}" or die "could not open $this->{dbname}\n";
  242   flock $fh, LOCK_EX;
  243 
  244   my $key   = $this->_getpass();
  245   eval {
  246     my $vault = new Crypt::PWSafe3(password => $key, file => $this->{dbname});
  247     delete $vault->{record}->{$uuid};
  248     $vault->markmodified();
  249     $vault->save();
  250   };
  251   if ($@) {
  252     print "Exception caught:\n$@\n";
  253     exit 1;
  254   }
  255 
  256   eval {
  257     flock $fh, LOCK_UN;
  258     $fh->close();
  259   };
  260 
  261   # finally re-read the db, so that we always have the latest data
  262   $this->_retrieve($key);
  263   $this->changed;
  264   return;
  265 }
  266 
  267 sub set_recountnums {
  268     my($this) = @_;
  269     # unsupported
  270     return;
  271 }
  272 
  273 
  274 sub _store {
  275   my ($this, $record, $create) = @_;
  276 
  277   my $fh;
  278 
  279   if (-s $this->{dbname}) {
  280     $fh = new FileHandle "<$this->{dbname}" or die "could not open $this->{dbname}\n";
  281     flock $fh, LOCK_EX;
  282   }
  283 
  284   my $key;
  285   my $prompt = "pwsafe password: ";
  286 
  287   foreach my $try (1..5) {
  288     if($try > 1) {
  289       $prompt = "pwsafe password ($try retry): ";
  290     }
  291     $key   = $this->_getpass($prompt);
  292     eval {
  293       my $vault = new Crypt::PWSafe3(password => $key, file => $this->{dbname});
  294       if ($create) {
  295         my $rec = new Crypt::PWSafe3::Record();
  296         $rec->uuid($record->{uuid});
  297         $vault->addrecord($rec);
  298         $vault->modifyrecord($record->{uuid}, %{$record});
  299       }
  300       else {
  301         $vault->modifyrecord($record->{uuid}, %{$record});
  302       }
  303       $vault->save();
  304     };
  305     if ($@) {
  306       if($@ =~ /wrong pass/i) {
  307         $key = '';
  308         next;
  309       }
  310       else {
  311         print "Exception caught:\n$@\n";
  312         exit 1;
  313       }
  314     }
  315     else {
  316       last;
  317     }
  318   }
  319   eval {
  320     flock $fh, LOCK_UN;
  321     $fh->close();
  322   };
  323 
  324   if(!$key) {
  325     print STDERR "Giving up after 5 failed password attempts.\n";
  326     exit 1;
  327   }
  328 
  329   # finally re-read the db, so that we always have the latest data
  330   $this->_retrieve($key);
  331 }
  332 
  333 sub _retrieve {
  334   my ($this, $key) = @_;
  335   my $file = $this->{dbname};
  336   if (-s $file) {
  337     if ($this->filechanged() || $this->{unread}) {
  338       my %data;
  339       if (! $key) {
  340     $key   = $this->_getpass();
  341       }
  342       eval {
  343     my $vault = new Crypt::PWSafe3(password => $key, file => $this->{dbname});
  344 
  345     my @records = $vault->getrecords();
  346 
  347     foreach my $record (sort { $a->ctime <=> $b->ctime } @records) {
  348       my $num = $this->_uuid( $record->uuid );
  349       my %entry = (
  350                uuid   => $record->uuid,
  351                title  => $record->title,
  352                user   => $record->user,
  353                passwd => $record->passwd,
  354                notes  => $record->notes,
  355                group  => $record->group,
  356                lastmod=> $record->lastmod,
  357                ctime  => $record->ctime,
  358                );
  359       $data{$num}->{note} = \%entry;
  360     }
  361       };
  362       if ($@) {
  363     print "Exception caught:\n$@\n";
  364     exit 1;
  365       }
  366 
  367       $this->{unread} = 0;
  368       $this->{data}   = \%data;
  369       return %data;
  370     }
  371     else {
  372       return %{$this->{data}};
  373     }
  374   }
  375   else {
  376     return ();
  377   }
  378 }
  379 
  380 sub _pwsafe3tonote {
  381   #
  382   # convert pwsafe3 record to note record
  383   my ($this, $record) = @_;
  384   my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($record->{ctime});
  385   my $date = sprintf("%02d.%02d.%04d %02d:%02d:%02d", $mday, $mon+1, $year+1900, $hour, $min, $sec);
  386   chomp $date;
  387   my $note;
  388   if ($record->{group}) {
  389     my $group = $record->{group};
  390     # convert group separator
  391     $group =~ s#\.#/#g;
  392     $note = "/$group/\n";
  393   }
  394 
  395   # pwsafe3 uses windows newlines, so convert ours
  396   $record->{notes} =~ s/\r\n/\n/gs;
  397 
  398   #
  399   # we do NOT add user and password fields here extra
  400   # because if it is contained in the note, from were
  401   # it was extracted initially, where it remains anyway
  402   $note .= "$record->{title}\n$record->{notes}";
  403 
  404   return ($date, $note);
  405 }
  406 
  407 sub _notetopwsafe3 {
  408   #
  409   # convert note record to pwsafe3 record
  410   # only used on create or save
  411   #
  412   # this one is the critical part, because the two
  413   # record types are fundamentally incompatible.
  414   # we parse our record and try to guess the values
  415   # required for pwsafe3
  416   #
  417   # expected input for note:
  418   # /path/          -> group, optional
  419   # any text        -> title
  420   #     User: xxx   -> user
  421   # Password: xxx   -> passwd
  422   # anything else   -> notes
  423   #
  424   # expected input for date:
  425   # 23.02.2010 07:56:27
  426   my ($this, $num, $text, $date) = @_;
  427   my ($group, $title, $user, $passwd, $notes, $ts, $content);
  428   if ($text =~ /^\//) {
  429     ($group, $title, $content) = split /\n/, $text, 3;
  430   }
  431   else {
  432     ($title, $content) = split /\n/, $text, 2;
  433   }
  434 
  435   if(!defined $content) { $content = ""; }
  436   if(!defined $group) { $group = ""; }
  437 
  438   $user = $passwd = '';
  439   if ($content =~ /(user|username|login|account|benutzer):\s*(.+)/i) {
  440     $user = $2;
  441   }
  442   if ($content =~ /(password|pass|passwd|kennwort|pw):\s*(.+)/i) {
  443     $passwd = $2;
  444   }
  445 
  446   #               1       2       3       4      5      6      
  447   if ($date =~ /^(\d\d)\.(\d\d)\.(\d{4}) (\d\d):(\d\d):(\d\d)$/) {
  448     # timelocal($sec,$min,$hour,$mday,$mon,$year);            
  449     $ts = timelocal($6, $5, $4, $1, $2-1, $3-1900);
  450   }
  451 
  452   # make our topics pwsafe3 compatible groups
  453   $group =~ s#^/##;
  454   $group =~ s#/$##;
  455   $group =~ s#/#.#g;
  456 
  457   # pwsafe3 uses windows newlines, so convert ours
  458   $content =~ s/\n/\r\n/gs;
  459   my %record = (
  460         uuid   => $this->_getuuid($num),
  461         user   => $user,
  462         passwd => $passwd,
  463         group  => $group,
  464         title  => $title,
  465         ctime  => $ts,
  466         lastmod=> $ts,
  467         notes  => $content,
  468         );
  469   return %record;
  470 }
  471 
  472 sub _uuid {
  473   my ($this, $uuid) = @_;
  474   if (exists $this->{uuidnum}->{$uuid}) {
  475     return $this->{uuidnum}->{$uuid};
  476   }
  477 
  478   my $max = 0;
  479 
  480   if (exists $this->{numuuid}) {
  481     $max = (sort { $b <=> $a } keys %{$this->{numuuid}})[0];
  482   }
  483 
  484   my $num = $max + 1;
  485 
  486   $this->{uuidnum}->{$uuid} = $num;
  487   $this->{numuuid}->{$num}  = $uuid;
  488 
  489   return $num;
  490 }
  491 
  492 sub _getuuid {
  493   my ($this, $num) = @_;
  494   return $this->{numuuid}->{$num};
  495 }
  496 
  497 sub _getpass {
  498   #
  499   # We're doing this here ourselfes
  500   # because the note way of handling encryption
  501   # doesn't work with pwsafe3, we can't hold a cipher
  502   # structure in memory, because pwsafe3 handles this
  503   # itself.
  504   # Instead we ask for the password everytime we want
  505   # to fetch data from the actual file OR want to write
  506   # to it. To minimize reads, we use caching by default.
  507   my($this, $prompt) = @_;
  508 
  509   if ($this->{key}) {
  510     return $this->{key};
  511   }
  512   else {
  513     my $key;
  514     print STDERR $prompt ? $prompt : "pwsafe password: ";
  515     eval {
  516       local($|) = 1;
  517       local(*TTY);
  518       open(TTY,"/dev/tty") or die "No /dev/tty!";
  519       system ("stty -echo </dev/tty") and die "stty failed!";
  520       chomp($key = <TTY>);
  521       print STDERR "\r\n";
  522       system ("stty echo </dev/tty") and die "stty failed!";
  523       close(TTY);
  524     };
  525     if ($@) {
  526       $key = <>;
  527     }
  528     if ($this->{keepkey}) {
  529       $this->{key} = $key;
  530     }
  531     return $key;
  532   }
  533 }
  534 
  535 1; # keep this!
  536 
  537 __END__
  538 
  539 =head1 NAME
  540 
  541 NOTEDB::pwsafe3 - module lib for accessing a notedb from perl
  542 
  543 =head1 SYNOPSIS
  544 
  545     # include the module
  546     use NOTEDB;
  547 
  548     # create a new NOTEDB object
  549     $db = new NOTEDB("text", "/home/tom/.notedb", 4096, 24);
  550 
  551     # decide to use encryption
  552     # $key is the cipher to use for encryption
  553     # $method must be either Crypt::IDEA or Crypt::DES
  554     # you need Crypt::CBC, Crypt::IDEA and Crypt::DES to have installed.
  555     $db->use_crypt($key,$method);
  556 
  557     # do not use encryption
  558     # this is the default
  559     $db->no_crypt;
  560 
  561     # get a single note
  562     ($note, $date) = $db->get_single(1);
  563 
  564     # search for a certain note 
  565     %matching_notes = $db->get_search("somewhat");
  566     # format of returned hash:
  567     #$matching_notes{$numberofnote}->{'note' => 'something', 'date' => '23.12.2000 10:33:02'}
  568 
  569     # get all existing notes
  570     %all_notes = $db->get_all();
  571     # format of returnes hash like the one from get_search above
  572 
  573     # get the next noteid available
  574     $next_num = $db->get_nextnum();
  575 
  576     # modify a certain note
  577     $db->set_edit(1, "any text", "23.12.2000 10:33:02");
  578 
  579     # create a new note
  580     $db->set_new(5, "any new text", "23.12.2000 10:33:02");
  581 
  582     # delete a certain note
  583     $db->set_del(5);
  584 
  585         # turn on encryption. CryptMethod must be IDEA, DES or BLOWFISH
  586         $db->use_crypt("passphrase", "CryptMethod");
  587 
  588         # turn off encryption. This is the default.
  589         $db->no_crypt();
  590 
  591 
  592 =head1 DESCRIPTION
  593 
  594 You can use this module for accessing a note database. This backend uses
  595 a text file for storage and Config::General for accessing the file.
  596 
  597 Currently, NOTEDB module is only used by note itself. But feel free to use it
  598 within your own project! Perhaps someone want to implement a webinterface to
  599 note...
  600 
  601 =head1 USAGE
  602 
  603 please see the section SYNOPSIS, it says it all.
  604 
  605 =head1 AUTHOR
  606 
  607 Thomas Linden <tom AT linden DOT at>
  608 
  609 
  610 =cut
  611 
  612