clivescan: removed metacafe support until fixed in clive.
[clive-utils.git] / clivepass
blob93173a9ec29fbae26b7fb5685e9d4b7c692ed84c
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 # TODO: Implement --show (usernames only)
26 # TODO: Document --show
28 use warnings;
29 use strict;
31 binmode(STDOUT, ":utf8");
33 use Crypt::PasswdMD5;
34 use Crypt::Twofish2;
35 use Config::Tiny;
36 use Crypt::Salt;
37 # Core modules:
38 use Digest::MD5 qw(md5_hex);
39 use MIME::Base64;
40 use Getopt::Long qw(:config bundling);
41 use File::Spec;
42 use File::Find;
43 use File::Path;
44 use Pod::Usage;
45 use Encode;
47 my $VERSION = "2.0beta3";
48 my $CONFIGDIR = $ENV{CLIVEPASS_CONFIGDIR}
49 || File::Spec->catfile($ENV{HOME}, ".config/clivepass");
50 my $PASSWDFILE = File::Spec->catfile($CONFIGDIR, "passwd");
52 my %opts;
53 GetOptions(\%opts,
54 'create|c', 'add|a=s', 'get|g=s', 'edit|e=s', 'delete|d=s',
55 'manual|m', 'help|h', 'version|v' => \&print_version,
56 ) or pod2usage(1);
58 pod2usage(-exitstatus => 0, -verbose => 1) if $opts{help};
59 pod2usage(-exitstatus => 0, -verbose => 2) if $opts{manual};
61 main();
63 sub main {
64 mkpath( [$CONFIGDIR], 1, 0700 );
66 if ( $opts{add} ) { add_login(); }
67 elsif ( $opts{create} ) { create_passwd(); }
68 elsif ( $opts{edit} ) { edit_login(); }
69 elsif ( $opts{delete} ) { delete_login(); }
70 elsif ( $opts{get} ) { get_login(); }
71 else {
72 pod2usage(-exitstatus => 0, -verbose => 1);
76 sub create_passwd {
77 if ( -e $PASSWDFILE ) {
78 print "WARN: $PASSWDFILE exists already.\n"
79 . "WARN: You are about to overwrite the existing file.\n"
80 . "WARN: Hit ctrl-c now if that's not your intention.\n";
82 print "Creating $PASSWDFILE.\n";
84 my ($phrase, $again);
85 $phrase = getpass("Enter passphrase: ") while ( ! $phrase );
86 $again = getpass("Enter passphrase again: ") 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 delete_login {
197 my ($key, $passwd) = get_key();
198 my ($id, $pwd) = lookup_login($passwd, $opts{delete});
200 print STDERR qq/error: no such login with the "$opts{delete}" /
201 . "username exists\n" and exit unless $pwd;
203 print "WARN: About to delete the login with the username "
204 . qq/"$id".\n/ ."> Confirm delete (y/N): ";
206 chomp(my $confirm = <STDIN>);
207 exit unless $confirm eq "y";
209 delete $passwd->{login}->{$id};
210 $passwd->write($PASSWDFILE);
213 sub lookup_login {
214 my ($passwd, $user) = @_;
216 foreach ( $passwd->{login} ) {
217 while ( my ($id, $pwd) = each(%{$_}) ) {
218 if ( $id eq $user ) {
219 return ($id,$pwd);
225 sub print_version {
226 my $perl_v = sprintf "%vd", $^V;
227 print
228 "clivepass version $VERSION. Copyright (C) 2008 Toni Gundogdu.
230 Perl: $perl_v ($^O)
231 Modules:
232 * Crypt::PasswdMD5/$Crypt::PasswdMD5::VERSION\t* Crypt::Twofish2/$Crypt::Twofish2::VERSION
233 * Crypt::Salt/$Crypt::Salt::VERSION\t\t* Config::Tiny/$Config::Tiny::VERSION
235 Core modules:
236 * Getopt::Long/$Getopt::Long::VERSION\t\t* Encode/$Encode::VERSION
237 * File::Spec/$File::Spec::VERSION\t\t* File::Find/$File::Find::VERSION
238 * Pod::Usage/$Pod::Usage::VERSION\t\t* Digest::MD5/$Digest::MD5::VERSION
239 * MIME::Base64/$MIME::Base64::VERSION
241 This program comes with ABSOLUTELY NO WARRANTY. You may redistribute copies of
242 clivepass under the terms of the GNU General Public License as published by the
243 Free Software Foundation, either version 3 of the License, or (at your option)
244 any later version. You should have received a copy of the General Public License
245 along with this program. If not, see http://www.gnu.org/licenses/.
246 "; exit;
249 __END__
251 =head1 NAME
253 clivepass - the login password utility for clive
255 =head1 SYNOPSIS
257 clivepass [option]...
259 =head1 DESCRIPTION
261 clivepass is an utility that can be used to create and change passwords
262 for websites used by L<clive(1)>. The passwords are encrypted and saved
263 along with the username information. Access is restricted by using a
264 global passphrase.
266 Historically, a similar utility, clive-passwd, was part of clive 1.x but
267 was written in Python instead of Perl.
269 =head1 OPTIONS
271 B<Basic Options>
273 =over 4
275 =item B<-h --help>
277 Show help and exit.
279 =item B<-v --version>
281 Show version and exit.
283 =item B<-c --create>
285 Create a new passwd file. See also L</FILES>.
287 =item B<-a --add=>I<username>
289 Add a new login with the I<username>.
291 =item B<-e --edit=>I<username>
293 Change login password for I<username>.
295 =item B<-g --get=>I<username>
297 Print the decrypted login password for the I<username>
298 to STDOUT.
300 =item B<-d --delete=>I<username>
302 Delete the login with the I<username>.
304 =back
306 =head1 EXAMPLES
308 =over 4
310 =item % clivepass -a myusername
312 =item % clivepass -g myusername
314 =back
316 =head1 FILES
318 By default, clivepass searches the ~/.config/clivepass directory for the
319 password file. The B<CLIVEPASS_CONFIGDIR> environment variable can be used
320 to override this behaviour.
322 =over 4
324 =item ~/.config/clivepass/passwd
326 Password file. Contains the salted passphrase hash and login usernames and
327 encrypted passwords.
329 =head1 SEE ALSO
331 L<clive(1)> L<clivescan(1)> L<clivefeed(1)>
333 =head1 OTHER
335 Project: http://googlecode.com/p/clive-utils/
337 A clive-utils development repository can be obtained from:
339 % git clone git://repo.or.cz/clive-utils.git
341 Patches welcome.
343 =head1 AUTHOR
345 Written by Toni Gundogdu <legatvs@gmail.com>
347 =cut