scan: Added support for lastfm, fixed gui invocation, fixed utf8 title issues.
[clive-utils.git] / clivepass
blob3044a203af623804edf7bb6246cbc552a8c159e0
1 #!/usr/bin/env perl
2 # -*- coding: ascii -*-
3 ###########################################################################
4 # clivepass, the login password utility for clive
5 # Copyright (C) 2008 Toni Gundogdu.
7 # This file is part of clive-utils.
9 # clivepass is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
14 # clivepass is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with clivepass. If not, see <http://www.gnu.org/licenses/>.
21 ###########################################################################
23 # Keep it simple.
25 use warnings;
26 use strict;
28 binmode(STDOUT, ":utf8");
30 use Crypt::PasswdMD5;
31 use Crypt::Twofish2;
32 use Config::Tiny;
33 use Crypt::Salt;
34 # Core modules:
35 use Digest::MD5 qw(md5_hex);
36 use MIME::Base64;
37 use Getopt::Long qw(:config bundling);
38 use File::Spec;
39 use File::Find;
40 use File::Path;
41 use Pod::Usage;
42 use Encode;
44 my $VERSION = "2.0.0";
45 my $CONFIGDIR = $ENV{CLIVEPASS_CONFIGDIR}
46 || File::Spec->catfile($ENV{HOME}, ".config/clivepass");
47 my $PASSWDFILE = File::Spec->catfile($CONFIGDIR, "passwd");
49 my %opts;
50 GetOptions(\%opts,
51 'create|c', 'add|a=s', 'get|g=s', 'edit|e=s', 'delete|d=s',
52 'show|s', 'manual|m', 'help|h', 'version|v' => \&print_version,
53 ) or pod2usage(1);
55 pod2usage(-exitstatus => 0, -verbose => 1) if $opts{help};
56 pod2usage(-exitstatus => 0, -verbose => 2) if $opts{manual};
58 main();
60 sub main {
61 mkpath( [$CONFIGDIR], 1, 0700 );
63 if ( $opts{add} ) { add_login(); }
64 elsif ( $opts{create} ) { create_passwd(); }
65 elsif ( $opts{edit} ) { edit_login(); }
66 elsif ( $opts{delete} ) { delete_login(); }
67 elsif ( $opts{get} ) { get_login(); }
68 elsif ( $opts{show} ) { show_logins(); }
69 else {
70 pod2usage(-exitstatus => 0, -verbose => 1);
74 sub create_passwd {
75 if ( -e $PASSWDFILE ) {
76 print "WARN: $PASSWDFILE exists already.\n"
77 . "WARN: You are about to overwrite the existing file.\n"
78 . "WARN: Hit ctrl-c now if that's not your intention.\n";
80 print "Creating $PASSWDFILE.\n";
82 my ($phrase, $again);
83 $phrase = getpass("Enter a new passphrase: ") while ( ! $phrase );
84 print "WARN: Consider using a longer passphrase.\n"
85 if ( length($phrase) < 8 );
86 $again = getpass("Re-enter the passphrase: ") while ( ! $again );
88 print STDERR "error: passphrases did not match\n" and exit
89 unless $phrase eq $again;
91 my $passwd = Config::Tiny->new;
92 $passwd->{_}->{phrase} = unix_md5_crypt($phrase, salt(8));
93 $passwd->write($PASSWDFILE);
95 return ( passwd => $passwd, phrase => $phrase );
98 sub verify_phrase {
99 my ($phrase_hash) = @_;
101 print STDERR "error: $PASSWDFILE: phrase hash not found\n" and exit
102 unless $phrase_hash;
104 my $phrase;
105 $phrase = getpass("Enter passphrase: ") while ( ! $phrase );
107 if ( unix_md5_crypt($phrase, $phrase_hash) ne $phrase_hash ) {
108 print STDERR "error: invalid passphrase\n";
109 exit;
111 return $phrase;
114 sub get_key {
115 my ($dupl_user) = @_;
117 print STDERR "error: $PASSWDFILE does not exist, use --create\n"
118 and exit if ( ! -e $PASSWDFILE );
120 my $passwd = Config::Tiny->read($PASSWDFILE);
122 if ( $dupl_user ) {
123 my ($id, $pwd) = lookup_login($passwd, $dupl_user);
124 print STDERR qq/error: login with the "$dupl_user" /
125 . "username exists already\n" and exit if $pwd;
128 my $phrase = verify_phrase($passwd->{_}->{phrase});
129 my $key = md5_hex($phrase);
131 return ($key, $passwd);
135 sub getpass {
136 if ( -t STDOUT ) {
137 system "stty -echo";
138 print shift;
140 chomp(my $passwd = <STDIN>);
142 if ( -t STDOUT ) {
143 print "\n";
144 system "stty echo";
146 return $passwd;
149 sub new_login {
150 my ($key, $passwd, $user) = @_;
152 my ($pwd, $again);
153 $pwd = getpass("Enter password for $user: ") while ( ! $pwd );
154 $again = getpass("Re-enter the password: ") while ( ! $again );
156 print STDERR "error: passwords did not match\n" and exit
157 unless $pwd eq $again;
159 my $c = Crypt::Twofish2->new($key, Crypt::Twofish2::MODE_CBC);
161 $passwd->{login}->{$user} =
162 encode_base64( $c->encrypt( pack('a16',$pwd) ) );
164 $passwd->write($PASSWDFILE);
167 sub add_login {
168 my ($key, $passwd) = get_key($opts{add});
169 new_login($key, $passwd, $opts{add});
172 sub edit_login {
173 my ($key, $passwd) = get_key();
174 my ($id, $pwd) = lookup_login($passwd, $opts{edit});
176 print STDERR qq/error: no such login with the "$opts{edit}" /
177 . "username exists\n" and exit unless $pwd;
179 print "WARN: Changing password for the login "
180 . qq/with the username "$id".\n/;
182 new_login($key, $passwd, $id);
185 sub get_login {
186 my ($key, $passwd) = get_key();
187 my ($id, $pwd) = lookup_login($passwd, $opts{get});
189 print STDERR qq/error: no such login with the "$opts{get}" /
190 . "username exists\n" and exit unless $pwd;
192 my $c = Crypt::Twofish2->new($key, Crypt::Twofish2::MODE_CBC);
193 print "login: " .$opts{get}."=". $c->decrypt(decode_base64($pwd)) ."\n";
196 sub show_logins {
197 my $passwd = Config::Tiny->read($PASSWDFILE);
198 foreach ( $passwd->{login} ) {
199 while ( my ($id, $pwd) = each(%{$_}) ) {
200 printf "%20s = %-32s\n", $id, $pwd;
205 sub delete_login {
206 my ($key, $passwd) = get_key();
207 my ($id, $pwd) = lookup_login($passwd, $opts{delete});
209 print STDERR qq/error: no such login with the "$opts{delete}" /
210 . "username exists\n" and exit unless $pwd;
212 print "WARN: About to delete the login with the username "
213 . qq/"$id".\n/ ."> Confirm delete (y/N): ";
215 chomp(my $confirm = <STDIN>);
216 exit unless $confirm eq "y";
218 delete $passwd->{login}->{$id};
219 $passwd->write($PASSWDFILE);
222 sub lookup_login {
223 my ($passwd, $user) = @_;
225 foreach ( $passwd->{login} ) {
226 while ( my ($id, $pwd) = each(%{$_}) ) {
227 if ( $id eq $user ) {
228 return ($id,$pwd);
234 sub print_version {
235 my $perl_v = sprintf "%vd", $^V;
236 print
237 "clivepass version $VERSION. Copyright (C) 2008 Toni Gundogdu.
239 Perl: $perl_v ($^O)
240 Modules:
241 * Crypt::PasswdMD5/$Crypt::PasswdMD5::VERSION\t* Crypt::Twofish2/$Crypt::Twofish2::VERSION
242 * Crypt::Salt/$Crypt::Salt::VERSION\t\t* Config::Tiny/$Config::Tiny::VERSION
244 Core modules:
245 * Getopt::Long/$Getopt::Long::VERSION\t\t* Encode/$Encode::VERSION
246 * File::Spec/$File::Spec::VERSION\t\t* File::Find/$File::Find::VERSION
247 * Pod::Usage/$Pod::Usage::VERSION\t\t* Digest::MD5/$Digest::MD5::VERSION
248 * MIME::Base64/$MIME::Base64::VERSION
250 This program comes with ABSOLUTELY NO WARRANTY. You may redistribute copies of
251 clivepass under the terms of the GNU General Public License as published by the
252 Free Software Foundation, either version 3 of the License, or (at your option)
253 any later version. You should have received a copy of the General Public License
254 along with this program. If not, see http://www.gnu.org/licenses/.
255 "; exit;
258 __END__
260 =head1 NAME
262 clivepass - the login password utility for clive
264 =head1 SYNOPSIS
266 clivepass [option]...
268 =head1 DESCRIPTION
270 clivepass is an utility that can be used to create and change passwords
271 for websites used by L<clive(1)>. The passwords are encrypted and saved
272 along with the username information. Access is restricted by using a
273 global passphrase.
275 Historically, a similar utility, clive-passwd, was part of clive 1.x but
276 was written in Python instead of Perl.
278 =head1 OPTIONS
280 B<Basic Options>
282 =over 4
284 =item B<-h --help>
286 Show help and exit.
288 =item B<-v --version>
290 Show version and exit.
292 =item B<-c --create>
294 Create a new passwd file. See also L</FILES>.
296 =item B<-a --add=>I<username>
298 Add a new login with the I<username>.
300 =item B<-e --edit=>I<username>
302 Change login password for I<username>.
304 =item B<-g --get=>I<username>
306 Print the decrypted login password for the I<username>
307 to STDOUT.
309 =item B<-s --show>
311 Show all saved login usernames with encrypted passwords.
313 =item B<-d --delete=>I<username>
315 Delete the login with the I<username>.
317 =back
319 =head1 EXAMPLES
321 =over 4
323 =item % clivepass -a myusername
325 =item % clivepass -g myusername
327 =back
329 =head1 FILES
331 By default, clivepass searches the ~/.config/clivepass directory for the
332 password file. The B<CLIVEPASS_CONFIGDIR> environment variable can be used
333 to override this behaviour.
335 =over 4
337 =item ~/.config/clivepass/passwd
339 Password file. Contains the salted passphrase hash and login usernames and
340 encrypted passwords.
342 =head1 SEE ALSO
344 L<clive(1)> L<clivescan(1)> L<clivefeed(1)>
346 =head1 OTHER
348 Project: http://googlecode.com/p/clive-utils/
350 A clive-utils development repository can be obtained from:
352 % git clone git://repo.or.cz/clive-utils.git
354 Patches welcome.
356 =head1 AUTHOR
358 Written by Toni Gundogdu <legatvs@gmail.com>
360 =cut