fix #1819: fork_worker: ensure sync'ed workers control terminal
[pve-common.git] / src / PVE / PTY.pm
index 51c7630..e433c4e 100644 (file)
@@ -165,6 +165,76 @@ sub tcsetsize($$$) {
        or die "failed to set window size: $!\n";
 }
 
+sub read_password($;$$) {
+    my ($query, $infd, $outfd) = @_;
+
+    my $password = '';
+
+    $infd //= \*STDIN;
+
+    if (!-t $infd) { # Not a terminal? Then just get a line...
+       local $/ = "\n";
+       $password = <$infd>;
+       die "EOF while reading password\n" if !defined $password;
+       chomp $password; # Chop off the newline
+       return $password;
+    }
+
+    $outfd //= \*STDOUT;
+
+    # Raw read loop:
+    my $old_termios;
+    $old_termios = tcgetattr($infd);
+    my $raw_termios = {%$old_termios};
+    cfmakeraw($raw_termios);
+    tcsetattr($infd, $raw_termios);
+    eval {
+       my $echo = undef;
+       my ($ch, $got);
+       syswrite($outfd, $query, length($query));
+       while (($got = sysread($infd, $ch, 1))) {
+           my ($ord) = unpack('C', $ch);
+           last if $ord == 4; # ^D / EOF
+           if ($ord == 0xA || $ord == 0xD) {
+               # newline, we're done
+               syswrite($outfd, "\r\n", 2);
+               last;
+           } elsif ($ord == 3) { # ^C
+               die "password input aborted\n";
+           } elsif ($ord == 0x7f) {
+               # backspace - if it's the first key disable
+               # asterisks
+               $echo //= 0;
+               if (length($password)) {
+                   chop $password;
+                   syswrite($outfd, "\b \b", 3);
+               }
+           } elsif ($ord == 0x09) {
+               # TAB disables the asterisk-echo
+               $echo = 0;
+           } else {
+               # other character, append to password, if it's
+               # the first character enable asterisks echo
+               $echo //= 1;
+               $password .= $ch;
+               syswrite($outfd, '*', 1) if $echo;
+           }
+       }
+       die "read error: $!\n" if !defined($got);
+    };
+    my $err = $@;
+    tcsetattr($infd, $old_termios);
+    die $err if $err;
+    return $password;
+}
+
+sub get_confirmed_password {
+    my $pw1 = read_password('Enter new password: ');
+    my $pw2 = read_password('Retype new password: ');
+    die "passwords do not match\n" if $pw1 ne $pw2;
+    return $pw1;
+}
+
 # Class functions
 
 sub new {