5 =NAME Perl6 Password Manager
6 =AUTHOR Ryan "rhr" Richter <ryan@tau.solarneutrino.net>
9 This program will generate, store, and retrieve passwords.
10 It uses B<xclip> to transfer passwords through the X11 clipboard,
11 so in normal use you will never even need to see your (unique,
12 randomly generated) passwords. Although it uses a terminal
13 L<ReadLine|perl5:Term::ReadLine> interface, it is designed
14 to be used mostly with the mouse, via cut-n-paste.
15 It can even generate random Unicode passwords!
19 T<<> >>.h I<# display usage information>
20 T<<> >>.n R<account-name> R R<username> I<# add a new account with a random password>
21 T<<> >>/R<account> I<# search database for matching account names>
22 T<<> >>R<account> I<# xclip password for R<account> and exit>
24 This last command ignores leading and trailing whitespace,
25 so that you can sloppily select the account name from the
26 output of the C</> command.
30 <account> xclip account password and exit
31 /<regex> search accounts
34 .p print account password
35 .x xclip account password
37 .r xclip random password
38 .R print random password
40 .A switch to all printable
43 .l change random password length
49 use perl5:Term::ReadLine;
51 regex alphanum { ^ <!before \t><alnum> $ }
52 regex printable { ^ <!before \t><print> $ }
53 my Regex $pwchar := &alphanum;
54 my Range $ascii = 0..127;
55 my Range $unicode = 0..0x10ffff;
56 my Range $charset := $ascii;
58 my Bool $changed = False;
61 sub help(--> Void) { warn $=USAGE; }
63 my Code &abort := -> Str $err { warn "$err\n"; return; }
65 sub search(Str $pat --> Void) {
66 for %pw.keys -> $k { say %pw{$k}<user>, "\t", $k if $k ~~ /<$pat>/ }
69 sub mk(Str $acct, Str $pass is copy, Str $user --> Void) {
71 $pass = randpass if $pass eq 'R';
72 %pw{$acct}<pass user> = $pass, $user;
75 sub del(Str $acct --> Void) {
76 abort "No account $acct" unless %pw{$acct}.:exists;
81 sub pr(Str $acct --> Void) {
82 abort "No account $acct" unless %pw{$acct}.:exists;
83 say %pw{$acct}<user pass>.join("\t");
86 sub wxclip(Str $acct --> Void) {
87 abort "No account $acct" unless %pw{$acct}.:exists;
88 xclip %pw{$acct}<pass>;
91 sub xclip(Str $s --> Void) {
92 my IO $xclip = Pipe.to: 'xclip' orelse abort 'No xclip - use .p';
97 sub sx(Str $s --> Void) {
98 my Str $pw = %pw{$s}<pass> //
99 first Str, (%pw{$_}<pass> if /$s/ for %pw.keys)
100 orelse abort "Couldn't find account $s";
108 sub randpass(--> Str) {
110 # < TimToady> and 9 developers out of 10 will shoot you if you use that construct. :)
111 # < TimToady> at least, if you use it uncommented...
112 my Str @password := gather while @password < $len {
113 if ($c = $charset.pick.chr) ~~ $pwchar { take $c }
115 return [~] @password;
119 unlink 'pwd.gpg.old' orelse abort "Couldn't unlink: $!";
120 rename 'pwd.gpg', 'pwd.gpg.old' orelse abort "Couldn't rename: $!";
121 my IO $pwd = Pipe.to: 'gpg --symmetric --force-mdc --cipher-algo AES256 --output pwd.gpg'
122 orelse abort "Couldn't encrypt: $!";
123 for %pw.keys -> $k { $pwd.say: $k, "\t", %pw{$k}<pass user>.join("\t") }
127 abort "Couldn't write pwd: $!";
133 [ '/' $<pat> = [ \N* ] { search $<pat> }
134 | \s* <!before '.'> $<acct> = [ \T+? ] \s* $$ { sx $<acct> }
135 | '.' [ n [ \t $<acct> = [ \T+ ] \t $<pass> = [ \T+ ] \t $<user> = [ \T+ ] $$
136 { mk $<acct>, $<pass>, $<user> }
137 | { warn ".n [tab] account [tab] password [tab] username\n" } <commit> <fail>
139 | d [ \s+ $<acct> = [ \T+? ] \s* $$ { del $<acct> }
140 | { warn ".d account\n" } <commit> <fail>
142 | p [ \s+ $<acct> = [ \T+? ] \s* $$ { pr $<acct> }
143 | { warn ".p account\n" } <commit> <fail>
145 | x [ \s+ $<acct> = [ \T+? ] \s* $$ { wxclip $<acct> }
146 | { warn ".x account\n" } <commit> <fail>
148 | l [ \s+ $<len> = [ \d+ ] \s* $$ { $len = $<len> }
149 | { warn ".l length\nlength is $len\n" } <commit> <fail>
152 | r { xclip randpass }
154 | a { $pwchar := &alphanum }
155 | A { $pwchar := &printable }
156 | u { $charset := $unicode }
157 | u { $charset := $ascii }
159 | { warn "Bad command\n"; help; } <commit> <fail>
165 ^^ $<acct> = [ \T+ ] \t $<pass> = [ \T+ ] \t $<user> = [ \T+ ] $$
168 %*ENV<PATH> = '/bin:/usr/bin:/usr/bin/X11';
170 chdir "$+HOME/pw" orelse die "Couldn't cd: $!";
171 my IO $pwd = Pipe.from: 'gpg --output - --decrypt pwd.gpg' orelse die "Couldn't decrypt: $!";
173 /<pwent>/ or die 'Malformed line ', $pwd.linenum, ": $_\n";
174 %pw{$<pwent><acct>}<pass user> = $<pwent><pass user>;
178 my $term = new Term::ReadLine: 'pw';
179 my $attribs = $term.Attribs;
180 $attribs<completion_entry_function> = $attribs<list_completion_function>;
181 $attribs<completion_word> = %pw.keys;
183 while defined $_ = $term.readline('> ') {
185 NEXT { $attribs<completion_word> = %pw.keys; }