001/*
002 * Copyright (c) 2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: welker $'
006 * '$Date: 2010-05-06 05:21:26 +0000 (Thu, 06 May 2010) $' 
007 * '$Revision: 24234 $'
008 * 
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029
030/** Textual password query class. Open source code taken from 
031 *  http://java.sun.com/developer/technicalArticles/Security/pwordmask/
032 *
033 *    '$RCSfile$'
034 *
035 *     '$Author: welker $'
036 *       '$Date: 2010-05-06 05:21:26 +0000 (Thu, 06 May 2010) $'
037 *   '$Revision: 24234 $'
038 *
039 *  For Details: http://www.kepler-project.org
040 *
041 */
042
043package org.kepler.ssh;
044
045import java.io.IOException;
046import java.io.InputStream;
047import java.io.PushbackInputStream;
048import java.util.Arrays;
049
050/**
051 * This class prompts the user for a password and attempts to mask input with
052 * "*"
053 */
054
055public class MaskedTextPasswordField {
056
057        /**
058         *@param input
059         *            stream to be used (e.g. System.in)
060         *@param prompt
061         *            The prompt to display to the user.
062         *@return The password as entered by the user.
063         */
064
065        public static final char[] getPassword(InputStream in, String prompt)
066                        throws IOException {
067                MaskingThread maskingthread = new MaskingThread(prompt);
068                Thread thread = new Thread(maskingthread);
069                thread.start();
070
071                char[] lineBuffer;
072                char[] buf;
073                int i;
074
075                buf = lineBuffer = new char[128];
076
077                int room = buf.length;
078                int offset = 0;
079                int c;
080
081                loop: while (true) {
082                        switch (c = in.read()) {
083                        case -1:
084                        case '\n':
085                                break loop;
086
087                        case '\r':
088                                int c2 = in.read();
089                                if ((c2 != '\n') && (c2 != -1)) {
090                                        if (!(in instanceof PushbackInputStream)) {
091                                                in = new PushbackInputStream(in);
092                                        }
093                                        ((PushbackInputStream) in).unread(c2);
094                                } else {
095                                        break loop;
096                                }
097
098                        default:
099                                if (--room < 0) {
100                                        buf = new char[offset + 128];
101                                        room = buf.length - offset - 1;
102                                        System.arraycopy(lineBuffer, 0, buf, 0, offset);
103                                        Arrays.fill(lineBuffer, ' ');
104                                        lineBuffer = buf;
105                                }
106                                buf[offset++] = (char) c;
107                                break;
108                        }
109                }
110                maskingthread.stopMasking();
111                if (offset == 0) {
112                        return null;
113                }
114                char[] ret = new char[offset];
115                System.arraycopy(buf, 0, ret, 0, offset);
116                Arrays.fill(buf, ' ');
117                return ret;
118        }
119
120        /**
121         * This class attempts to erase characters echoed to the console. Taken from
122         * http://java.sun.com/developer/technicalArticles/Security/pwordmask/
123         * Source is open.
124         */
125        private static class MaskingThread extends Thread {
126                private volatile boolean dowork;
127                private char echochar = ' ';
128
129                /**
130                 *@param prompt
131                 *            The prompt displayed to the user
132                 */
133                public MaskingThread(String prompt) {
134                        System.out.print(prompt);
135                }
136
137                /**
138                 * Begin masking until asked to stop.
139                 */
140                public void run() {
141
142                        int priority = Thread.currentThread().getPriority();
143                        Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
144
145                        try {
146                                dowork = true;
147                                while (dowork) {
148                                        System.out.print("\010" + echochar);
149                                        try {
150                                                // attempt masking at this rate
151                                                Thread.currentThread().sleep(1);
152                                        } catch (InterruptedException iex) {
153                                                Thread.currentThread().interrupt();
154                                                return;
155                                        }
156                                }
157                        } finally { // restore the original priority
158                                Thread.currentThread().setPriority(priority);
159                        }
160                }
161
162                /**
163                 * Instruct the thread to stop masking.
164                 */
165                public void stopMasking() {
166                        this.dowork = false;
167                }
168        }
169
170}