File Coverage

blib/lib/Term/ReadPassword.pm
Criterion Covered Total %
statement 18 82 21.9
branch 0 44 0.0
condition 0 7 0.0
subroutine 6 7 85.7
pod 0 1 0.0
total 24 141 17.0


line stmt bran cond sub pod time code
1             package Term::ReadPassword;
2              
3 3     3   44909 use strict;
  3         6  
  3         124  
4 3     3   15 use warnings;
  3         3  
  3         100  
5              
6 3     3   1836 use Term::ReadLine;
  3         8460  
  3         115  
7 3     3   1817 use POSIX qw(:termios_h);
  3         18647  
  3         20  
8             my %CC_FIELDS = (
9             VEOF => VEOF,
10             VEOL => VEOL,
11             VERASE => VERASE,
12             VINTR => VINTR,
13             VKILL => VKILL,
14             VQUIT => VQUIT,
15             VSUSP => VSUSP,
16             VSTART => VSTART,
17             VSTOP => VSTOP,
18             VMIN => VMIN,
19             VTIME => VTIME,
20             );
21              
22 3         288 use vars qw(
23             $ALLOW_STDIN %SPECIAL $SUPPRESS_NEWLINE $INPUT_LIMIT
24             $USE_STARS $STAR_STRING $UNSTAR_STRING
25 3     3   4802 );
  3         6  
26              
27 3     3   14 use Exporter qw(import);
  3         4  
  3         2987  
28              
29             our @EXPORT = qw(
30             read_password
31             );
32             our $VERSION = '0.11_02';
33              
34             # The special characters in the input stream
35             %SPECIAL = (
36             "\x03" => 'INT', # Control-C, Interrupt
37             "\x15" => 'NAK', # Control-U, NAK (clear buffer)
38             "\x08" => 'DEL', # Backspace
39             "\x7f" => 'DEL', # Delete
40             "\x0d" => 'ENT', # CR, Enter
41             "\x0a" => 'ENT', # LF, Enter
42             );
43              
44             # The maximum amount of data for the input buffer to hold
45             $INPUT_LIMIT = 1000;
46              
47             sub read_password {
48 0     0 0   my ( $prompt, $idle_limit, $interruptable ) = @_;
49 0 0         $prompt = '' unless defined $prompt;
50 0 0         $idle_limit = 0 unless defined $idle_limit;
51 0 0         $interruptable = 0 unless defined $interruptable;
52              
53             # Let's open the TTY (rather than STDIN) if we can
54 0           local ( *TTY, *TTYOUT );
55 0           my ( $in, $out ) = Term::ReadLine->findConsole;
56 0 0         die "No console available" unless $in;
57 0 0         if ( open TTY, "+<$in" ) {
    0          
58              
59             # Cool
60             }
61             elsif ($ALLOW_STDIN) {
62 0 0         open TTY, "<&STDIN"
63             or die "Can't re-open STDIN: $!";
64             }
65             else {
66 0           die "Can't open '$in' read/write: $!";
67             }
68              
69             # And let's send the output to the TTY as well
70 0 0         if ( open TTYOUT, ">>$out" ) {
    0          
71              
72             # Cool
73             }
74             elsif ($ALLOW_STDIN) {
75              
76             # Well, let's allow STDOUT as well
77 0 0         open TTYOUT, ">>&STDOUT"
78             or die "Can't re-open STDOUT: $!";
79             }
80             else {
81 0           die "Can't open '$out' for output: $!";
82             }
83              
84             # Don't buffer it!
85 0           select( ( select(TTYOUT), $| = 1 )[0] );
86 0           print TTYOUT $prompt;
87              
88             # Okay, now remember where everything was, so we can put it back when
89             # we're done
90 0           my $fd_tty = fileno(TTY);
91 0           my $term = POSIX::Termios->new();
92 0           $term->getattr($fd_tty);
93 0           my $original_flags = $term->getlflag();
94 0           my %original_cc;
95 0           for my $field_name ( keys %CC_FIELDS ) {
96 0           $original_cc{$field_name} = $term->getcc( $CC_FIELDS{$field_name} );
97             }
98              
99             # What makes this setup different from the ordinary?
100             # No keyboard-generated signals, no echoing, no canonical input
101             # processing (like backspace handling)
102 0           my $flags = $original_flags & ~( ISIG | ECHO | ICANON );
103 0           $term->setlflag($flags);
104 0 0         if ($idle_limit) {
105              
106             # $idle_limit is in seconds, so multiply by ten
107 0           $term->setcc( VTIME, 10 * $idle_limit );
108              
109             # Continue running the program after that time, even if there
110             # weren't any characters typed
111 0           $term->setcc( VMIN, 0 );
112             }
113             else {
114             # No time limit, but...
115 0           $term->setcc( VTIME, 0 );
116              
117             # Continue as soon as one character has been struck
118 0           $term->setcc( VMIN, 1 );
119             }
120              
121             # Optionally echo stars in place of password characters. The
122             # $unstar_string uses backspace characters.
123 0 0 0       my $star_string = $USE_STARS ? ( $STAR_STRING || '*' ) : '';
124 0 0 0       my $unstar_string = $USE_STARS ? ( $UNSTAR_STRING || "\b*\b \b" ) : '';
125              
126             # If there's anything already buffered, we should throw it out. This
127             # is to discourage users from typing their password before they see
128             # the prompt, since their keystrokes may be echoing on the screen.
129             #
130             # So this statement supposedly makes sure the prompt goes out, the
131             # unread input buffer is discarded, and _then_ the changes take
132             # effect. Thus, everything they typed ahead is (probably) echoed.
133 0           $term->setattr( $fd_tty, TCSAFLUSH );
134              
135 0           my $input = '';
136 0           my $return_value;
137             KEYSTROKE:
138 0           while (1) {
139 0           my $new_keys = '';
140 0           my $count = sysread( TTY, $new_keys, 99 );
141              
142             # We're here, so either the idle_limit expired, or the user typed
143             # something.
144 0 0         if ($count) {
145 0           for my $new_key ( split //, $new_keys ) {
146 0 0         if ( my $meaning = $SPECIAL{$new_key} ) {
147 0 0 0       if ( $meaning eq 'ENT' ) {
    0          
    0          
    0          
148              
149             # Enter/return key
150             # Return what we have so far
151 0           $return_value = $input;
152 0           last KEYSTROKE;
153             }
154             elsif ( $meaning eq 'DEL' ) {
155              
156             # Delete/backspace key
157             # Take back one char, if possible
158 0 0         if ( length $input ) {
159 0           $input = substr $input, 0, length($input) - 1;
160 0           print TTYOUT $unstar_string;
161             }
162             }
163             elsif ( $meaning eq 'NAK' ) {
164              
165             # Control-U (NAK)
166             # Clear what we have read so far
167 0           for ( 1 .. length $input ) {
168 0           print TTYOUT $unstar_string;
169             }
170 0           $input = '';
171             }
172             elsif ( $interruptable and $meaning eq 'INT' ) {
173              
174             # Breaking out of the program
175             # Return early
176 0           last KEYSTROKE;
177             }
178             else {
179             # Just an ordinary keystroke
180 0           $input .= $new_key;
181 0           print TTYOUT $star_string;
182             }
183             }
184             else {
185             # Not special
186 0           $input .= $new_key;
187 0           print TTYOUT $star_string;
188             }
189             }
190              
191             # Just in case someone sends a lot of data
192 0 0         $input = substr( $input, 0, $INPUT_LIMIT )
193             if length($input) > $INPUT_LIMIT;
194             }
195             else {
196             # No count, so something went wrong. Assume timeout.
197             # Return early
198 0           last KEYSTROKE;
199             }
200             }
201              
202             # Done with waiting for input. Let's not leave the cursor sitting
203             # there, after the prompt.
204 0 0         print TTYOUT "\n" unless $SUPPRESS_NEWLINE;
205              
206             # Let's put everything back where we found it.
207 0           $term->setlflag($original_flags);
208 0           while ( my ( $field, $value ) = each %original_cc ) {
209 0           $term->setcc( $CC_FIELDS{$field}, $value );
210             }
211 0           $term->setattr( $fd_tty, TCSAFLUSH );
212 0           close(TTY);
213 0           close(TTYOUT);
214 0           $return_value;
215             }
216              
217             1;
218             __END__