RSS

(root)/users/lolando/sgeps/trunk : /sgeps (revision 15)

To get this branch, use:
bzr branch /loggerhead/users/lolando/sgeps/trunk
Line Revision Contents
1 1
#! /usr/bin/perl -w
2
#
3 3
# A simple GnuPG-encrypted password store
4 1
#
5 12.2.7
# Allows to store key/{login,password,notes} pairs in a GPG-encrypted file
6 1
#
7
# Copyright © 2009, Roland Mas <lolando@debian.org>
8 12.2.7
# Copyright © 2010, Ben Voui <intrigeri@boum.org>
9 1
# 
10
# This program is free software; you can redistribute it and/or modify
11
# it under the terms of the GNU General Public License as published by
12
# the Free Software Foundation; either version 2 of the License, or
13
# (at your option) any later version.
14
#
15 4
# Usage: sgeps --create	       	   	    to create the store
16 12.2.9
#        sgeps --add <key>       	    to add a key/value to the store
17 12.2.7
#        sgeps --read <key>                 to read all data of a key from the store
18 12.2.9
#        sgeps --copy <key>                 to copy the login and password of a key to the X clipboard
19
#        sgeps --copy --login <key>         to copy the login of a key to the X clipboard
20
#        sgeps --copy --password <key>      to copy the password of a key to the X clipboard
21 10
#        sgeps --delete <key>	       	    to delete a key from the store
22 4
#        sgeps --list	       	   	    to list existing keys
23 12
#        sgeps --search <pattern>           to list existing keys matching the pattern
24 4
#        sgeps --add --overwrite <key>      to replace a key/value
25 12.2.11
#        sgeps --add --batch <key>   	    to add a key/value to the store with no interactive terminal
26 12.2.10
#        sgeps --edit <key>                 to edit the data for a key
27 1
# 
28 4
# Sample config file, to be stored in ~/.config/sgeps.conf (unless
29 1
# overridden with --config=.../foo.conf):
30
# ,----
31 7
# | store = ~/s1kr3t/sgeps.gpg
32 1
# | keyid = 0x12345678
33
# `----
34 12.2.9
#
35
# Non-Perl dependencies: GnuPG, xclip.
36 12.2.12
#
37
# TODO:
38
# - add status messages for --copy, to ease frontends development.
39 1
40
use strict ;
41
42 12.2.3
use Storable qw/ nfreeze thaw / ;
43 1
use File::Temp qw/ tempdir / ;
44
use Getopt::Long ;
45
use Config::Simple ;
46 12.2.5
use Term::ReadLine;
47 1
48 12.2.10
use vars qw/ %database %flags $config_file %config / ;
49 1
50 4
$config_file = "$ENV{HOME}/.config/sgeps.conf" ;
51 1
52
my $result = GetOptions ("read"      => \$flags{read},
53 12.2.9
			 "copy"      => \$flags{copy},
54
			 "login"     => \$flags{login},
55
			 "password"  => \$flags{password},
56
			 "both"      => \$flags{both},
57 1
			 "add"	     => \$flags{add},
58 10
			 "delete"    => \$flags{delete},
59 1
			 "overwrite" => \$flags{overwrite},
60 12.2.11
			 "batch"     => \$flags{batch},
61 12.2.10
			 "edit"	     => \$flags{edit},
62 1
			 "list"	     => \$flags{list},
63 12
			 "search"    => \$flags{search},
64 1
			 "create"    => \$flags{create},
65 12.1.1
			 "xclip"     => \$flags{xclip},
66 1
			 "config=s"  => \$config_file,
67
    ) ;
68
69 6
$config_file = glob $config_file ;
70
71 5
tie %config, "Config::Simple", $config_file or die Config::Simple->error() ;
72 1
73 7
$config{store} = glob $config{store} ;
74
75 1
SWITCH: {
76
    
77 12.2.9
    ($flags{read} || $flags{copy}) && do {
78 1
	my $kw = $ARGV[0] ;
79
	
80 9
	die "No key specified" unless (defined $kw and $kw ne '') ;
81 8
       
82 1
	&readfile ;
83
84 12.2.9
        die "No defined value for $kw" 	unless defined $database{$kw} ;
85
86
        my $v = $database{$kw} ;
87
88
        # update the entry to the new format
89
        if (! ref($v)) {
90
            $database{$kw} = {
91
                login    => $kw,
92
                password => $v,
93
                notes    => '',
94
            } ;
95
            &writefile ;
96
            $v = $database{$kw} ;
97
        }
98
99 12.2.14
        if ($flags{read} && ! $flags{xclip}) {
100 12.2.9
            print "login: "    . $v->{login} . "\n" ;
101
            print "password: " . $v->{password} . "\n" ;
102
            print "notes: "    . $v->{notes}    . "\n" ;
103
        }
104 12.2.14
        elsif ($flags{read} && $flags{xclip}) {
105
            &copyandwait($v->{password}) ;
106
            &emptyclipboard ;
107
        }
108 12.2.9
        elsif ($flags{copy}) {
109
            if ($flags{login}) {
110
                &copyandwait($v->{login}) ;
111
                &emptyclipboard ;
112
            }
113
            elsif ($flags{password}) {
114
                &copyandwait($v->{password}) ;
115
                &emptyclipboard ;
116 12.2.5
            }
117
            else {
118 12.2.9
                &copyandwait($v->{login}) ;
119
                &emptyclipboard ;
120
                &copyandwait($v->{password}) ;
121
                &emptyclipboard ;
122 12.2.5
            }
123 12.2.9
        }
124 1
	last SWITCH ;
125
    } ;
126
127 12
    ($flags{list} || $flags{search}) && do {
128
	my $pattern = $ARGV[0] ;
129
130
	if (defined $flags{search}) { 
131
	    die "No pattern specified" unless (defined $pattern and $pattern ne '') ;
132
	}
133
	
134 1
	&readfile ;
135 12
	
136
	print "Defined keys" ;
137
	print " (looking for pattern $pattern)" if (defined $flags{search}) ;
138
	print ":\n" ;
139
	
140
	foreach my $key (sort {$a cmp $b} keys %database) {
141
	    if (defined $flags{search}) {
142
		print "$key\n" if ($key =~ m/$pattern/) ;
143
	    } else {
144
		print "$key\n" ;
145
	    }
146 1
	}
147
	last SWITCH ;
148
    } ;
149
150 12.2.10
    ($flags{add} || $flags{edit}) && do {
151 1
	my $kw = $ARGV[0] ;
152
153 9
	die "No key specified" unless (defined $kw and $kw ne '') ;
154 8
       
155 12.2.2
	&readfile ;
156
157 12.2.10
	if (defined ($database{$kw}) && !$flags{overwrite} && !$flags{edit}) {
158
	    die "Value already defined for $kw, use --overwrite or --edit" ;
159 12.2.2
	}
160
161 12.2.10
        my %defaults = (
162
            login    => '',
163
            password => '',
164
            notes    => '',
165
        );
166
        if ($flags{add}) {
167
            $defaults{login} = $kw ;
168
        }
169
        elsif ($flags{edit}) {
170
            %defaults = %{$database{$kw}}
171
        }
172 12.2.5
173 12.2.10
        $database{$kw} = promptdata($kw, %defaults) ;
174 12.2.5
175 1
	&writefile ;
176
	last SWITCH ;
177
    } ;
178
179 10
    $flags{delete} && do {
180
	my $kw = $ARGV[0] ;
181
182
	die "No key specified" unless (defined $kw and $kw ne '') ;
183
       
184
	print "Are you sure? (y/N) ";
185
	my $val = <STDIN>;
186
	chomp $val;
187
	last SWITCH if $val ne 'y';
188
189
	&readfile ;
190
191
	print "Removing $kw from the store\n" ;
192
	delete $database{$kw};
193
	&writefile ;
194
	last SWITCH ;
195
    } ;
196
197 1
    $flags{create} && do {
198
	if (-e $config{store}) {
199 9
	    die "Store already exists" ;
200 1
	}
201
	%database = () ;
202
	&writefile ;
203
	last SWITCH ;
204
    } ;
205
}
206
207
exit 0 ;
208
209
sub readfile () {
210
    my $clear = qx! gpg -q -d $config{store} ! ;
211
    if ($? != 0) {
212 9
	die "Error while opening store" ;
213 1
    }
214
    %database = %{thaw $clear} ;
215
}
216
217
sub writefile () {
218 12.2.1
    my $clear = nfreeze \%database ;
219 1
    my $tmpdir = tempdir "$config{store}.XXXXXX" ;
220
    my $tmp = "$tmpdir/store" ;
221 12.2.4
    open W, "| gpg --encrypt --armor --recipient $config{keyid} --output $tmp 2> /dev/null"
222
        or die "Error opening a pipe to gpg" ;
223
    print W $clear or die "Error writing to the gpg pipe" ;
224
    close W or die "Error closing pipe to gpg" ;
225
    rename $tmp, $config{store} or die "Error moving temporary file to " . $config{store} ;
226 1
    rmdir $tmpdir ;
227
}
228 12.2.9
229
# copy passed string to the X clipboard, then wait for it to have been pasted,
230
# then empty the clipboard
231
sub copyandwait () {
232
    my $string = shift ;
233
    # bug in xclip: removing -verbose breaks -loops functionality
234
    open XCLIP, "| xclip -loops 1 -verbose >/dev/null 2>&1"
235
        or die "Error opening pipe to xclip" ;
236
    print XCLIP $string or die "Error copying data to X clipboard" ;
237
    close XCLIP or die "Error closing pipe to xclip" ;
238
239
    &emptyclipboard;
240
}
241
242
sub emptyclipboard () {
243
    open XCLIP, "| xclip" or die "Error opening pipe to xclip" ;
244
    print XCLIP "" or die "Error copying data to X clipboard" ;
245
    close XCLIP or die "Error closing pipe to xclip" ;
246
}
247 12.2.10
248 12.2.11
sub promptdata {
249 12.2.10
    my $key = shift ;
250 12.2.11
    my %default = @_ ;
251
252
    if ($flags{batch}) {
253
        my ($login, $password, $notes) = (<STDIN>, <STDIN>, <STDIN>);
254
        chomp ($login, $password, $notes);
255
        return {
256
            login    => $login,
257
            password => $password,
258
            notes    => $notes,
259
        } ;
260
    }
261
    else {
262
        my $term = Term::ReadLine->new('sgeps') ;
263
        return {
264
            login    => $term->readline("Login: ",    $default{login}),
265
            password => $term->readline("Password: ", $default{password}),
266
            notes    => $term->readline("Notes: ",    $default{notes}),
267
        } ;
268
    }
269 12.2.10
}

Loggerhead 1.17 is a web-based interface for Bazaar branches