/*
* ====================================================================
* Copyright (c) 2004-2012 TMate Software Ltd. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://svnkit.com/license.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
* ====================================================================
*/
package org.tmatesoft.svn.cli;
import com.jcraft.jsch.agentproxy.AgentProxyException;
import com.jcraft.jsch.agentproxy.Connector;
import com.jcraft.jsch.agentproxy.ConnectorFactory;
import com.jcraft.jsch.agentproxy.TrileadAgentProxy;
import com.trilead.ssh2.auth.AgentProxy;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.*;
import org.tmatesoft.svn.core.internal.util.SVNSSLUtil;
import org.tmatesoft.svn.core.internal.wc.ISVNAuthStoreHandler;
import org.tmatesoft.svn.core.internal.wc.ISVNGnomeKeyringPasswordProvider;
import org.tmatesoft.svn.core.internal.wc.ISVNSSLPasspharsePromptSupport;
import org.tmatesoft.svn.core.wc.SVNWCUtil;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.cert.X509Certificate;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
/**
* @version 1.0
* @author TMate Software Ltd.
*/
public class SVNConsoleAuthenticationProvider implements ISVNAuthenticationProvider, ISVNSSLPasspharsePromptSupport, ISVNAuthStoreHandler, ISVNGnomeKeyringPasswordProvider {
private static final String OUR_PASSPHRASE_PROMPT_TEXT = "-----------------------------------------------------------------------\n" +
"ATTENTION! Your passphrase for client certificate:\n" +
"\n" +
" {0}\n" +
"\n" +
"can only be stored to disk unencrypted! You are advised to configure\n" +
"your system so that Subversion can store passphrase encrypted, if\n" +
"possible. See the documentation for details.\n" +
"\n" +
"You can avoid future appearances of this warning by setting the value\n" +
"of the ''store-ssl-client-cert-pp-plaintext'' option to either ''yes'' or\n" +
"''no'' in ''{1}''.\n" +
"-----------------------------------------------------------------------\n";
private static final String OUR_PASSWORD_PROMPT_TEXT = "-----------------------------------------------------------------------\n" +
"ATTENTION! Your password for authentication realm:\n" +
"\n" +
" {0}\n" +
"\n" +
"can only be stored to disk unencrypted! You are advised to configure\n" +
"your system so that Subversion can store passwords encrypted, if\n" +
"possible. See the documentation for details.\n" +
"\n" +
"You can avoid future appearances of this warning by setting the value\n" +
"of the ''store-plaintext-passwords'' option to either ''yes'' or ''no'' in\n" +
"''{1}''.\n" +
"-----------------------------------------------------------------------\n";
private static final String OUR_HOST_KEY_PROMPT_TEXT = "The ''{0}'' server''s key fingerprint is:\n{1}\n";
private static final String OUR_PASSWORD_PROMPT_STRING = "Store password unencrypted (yes/no)? ";
private static final String OUR_PASSPHRASE_PROMPT_STRING = "Store passphrase unencrypted (yes/no)? ";
private static final int MAX_PROMPT_COUNT = 3;
private Map myRequestsCount = new HashMap();
private boolean myIsTrustServerCertificate;
public SVNConsoleAuthenticationProvider(boolean trustServerCertificate) {
myIsTrustServerCertificate = trustServerCertificate;
}
public int acceptServerAuthentication(SVNURL url, String realm, Object certificate, boolean resultMayBeStored) {
if (certificate instanceof byte[]) {
StringBuffer prompt = new StringBuffer(OUR_HOST_KEY_PROMPT_TEXT);
if (resultMayBeStored) {
prompt.append("If you trust this host, enter ''p'' to add the key to the SVN cache and carry on connecting.\n");
prompt.append("If you want to carry on connecting just once, without adding the key to the cache, type ''t''.");
prompt.append("If you do not trust this host, type ''R'' to abandon the connection.");
prompt.append("\n(R)eject, accept (t)emporarily or accept (p)ermanently? ");
} else {
prompt.append("If you want to carry on connecting just once, without adding the key to the cache, type ''t''.");
prompt.append("If you do not trust this host, type ''R'' to abandon the connection.");
prompt.append("\n(R)eject or accept (t)emporarily? ");
}
System.err.print(MessageFormat.format(prompt.toString(), new Object[] {url.getHost(), SVNSSLUtil.getFingerprint((byte[]) certificate, "MD5")}));
System.err.flush();
while(true) {
String line = readLine();
if (line == null) {
return ISVNAuthenticationProvider.REJECTED;
}
if (line.length() < 1) {
continue;
}
char ch = line.charAt(0);
if (ch == 'R' || ch == 'r') {
return ISVNAuthenticationProvider.REJECTED;
} else if (ch == 't' || ch == 'T') {
return ISVNAuthenticationProvider.ACCEPTED_TEMPORARY;
} else if (resultMayBeStored && (ch == 'p' || ch == 'P')) {
return ISVNAuthenticationProvider.ACCEPTED;
}
}
}
if (myIsTrustServerCertificate) {
return ISVNAuthenticationProvider.ACCEPTED_TEMPORARY;
}
if (!(certificate instanceof X509Certificate)) {
return ISVNAuthenticationProvider.ACCEPTED_TEMPORARY;
}
String hostName = url.getHost();
X509Certificate cert = (X509Certificate) certificate;
StringBuffer prompt = SVNSSLUtil.getServerCertificatePrompt(cert, realm, hostName);
if (resultMayBeStored) {
prompt.append("\n(R)eject, accept (t)emporarily or accept (p)ermanently? ");
} else {
prompt.append("\n(R)eject or accept (t)emporarily? ");
}
System.err.print(prompt.toString());
System.err.flush();
while(true) {
String line = readLine();
if (line == null) {
return ISVNAuthenticationProvider.REJECTED;
}
if (line.length() < 1) {
continue;
}
char ch = line.charAt(0);
if (ch == 'R' || ch == 'r') {
return ISVNAuthenticationProvider.REJECTED;
} else if (ch == 't' || ch == 'T') {
return ISVNAuthenticationProvider.ACCEPTED_TEMPORARY;
} else if (resultMayBeStored && (ch == 'p' || ch == 'P')) {
return ISVNAuthenticationProvider.ACCEPTED;
}
}
}
public SVNAuthentication requestClientAuthentication(String kind, SVNURL url, String realm, SVNErrorMessage errorMessage, SVNAuthentication previousAuth, boolean authMayBeStored) {
Integer requestsCount = (Integer) myRequestsCount.get(kind + "$" + url + "$" + realm);
if (requestsCount == null) {
myRequestsCount.put(kind + "$" + url + "$" + realm, new Integer(1));
} else if (requestsCount.intValue() == MAX_PROMPT_COUNT) {
// no more than three requests per realm
return null;
} else {
myRequestsCount.put(kind + "$" + url + "$" + realm, new Integer(requestsCount.intValue() + 1));
}
if (ISVNAuthenticationManager.PASSWORD.equals(kind)) {
String name = null;
String defaultUserName = null;
if (previousAuth != null) {
if (previousAuth.isPartial()) {
name = previousAuth.getUserName();
} else if (previousAuth.getUserName() != null) {
defaultUserName = previousAuth.getUserName();
}
}
printRealm(realm);
if (name == null) {
String promptString = defaultUserName == null ? "Username" : "Username [" + defaultUserName + "]";
name = prompt(promptString);
if ("".equals(name) && defaultUserName != null) {
name = defaultUserName;
}
}
if (name == null) {
return null;
}
String password = promptPassword("Password for '" + name + "'");
if (password == null) {
return null;
}
return new SVNPasswordAuthentication(name, password, authMayBeStored, url, false);
} else if (ISVNAuthenticationManager.SSH.equals(kind)) {
String name = null;
String defaultUserName = null;
String defaultPassword = null;
String defaultPassphrase = null;
File defaultPrivateKeyFile = null;
int defaultPort = -1;
if (url.getUserInfo() != null && !"".equals(url.getUserInfo())) {
defaultUserName = url.getUserInfo();
}
if (previousAuth != null && previousAuth instanceof SVNSSHAuthentication) {
SVNSSHAuthentication sshPreviousAuth = (SVNSSHAuthentication) previousAuth;
defaultUserName = defaultUserName == null ? sshPreviousAuth.getUserName() : defaultUserName;
defaultPassword = sshPreviousAuth.getPassword();
defaultPassphrase = sshPreviousAuth.getPassphrase();
defaultPrivateKeyFile = sshPreviousAuth.getPrivateKeyFile();
defaultPort = sshPreviousAuth.getPortNumber();
}
printRealm(realm);
name = prompt(defaultUserName == null ? "Username" : "Username [" + defaultUserName + "]");
if ("".equals(name) && defaultUserName != null) {
name = defaultUserName;
}
if (name == null) {
return null;
}
String passwordPrompt = null;
if (defaultPassword != null) {
passwordPrompt = "Password for '" + url.getHost() + "' (leave blank if you are going to use private key) [";
for (int i = 0; i < defaultPassword.length(); i++) {
passwordPrompt += "*";
}
passwordPrompt += "]";
} else {
passwordPrompt = "Password for '" + url.getHost() + "' (leave blank if you are going to use private key)";
}
String password = promptPassword(passwordPrompt);
if (password == null) {
return null;
} else if ("".equals(password)) {
if (defaultPassword != null) {
password = defaultPassword;
} else {
password = null;
}
}
String keyFilePath = null;
File keyFile = null;
String passphrase = null;
if (password == null) {
while(keyFilePath == null) {
String privateKeyFilePrompt = null;
if (defaultPrivateKeyFile != null) {
privateKeyFilePrompt = "Private key for '" + url.getHost() + "' (OpenSSH format) [" + defaultPrivateKeyFile.getAbsolutePath() + "]";
} else {
privateKeyFilePrompt = "Private key for '" + url.getHost() + "' (OpenSSH format) (leave blank to use a SSH agent)";
}
keyFilePath = prompt(privateKeyFilePrompt);
if ("".equals(keyFilePath)) {
if (defaultPrivateKeyFile != null) {
if (!defaultPrivateKeyFile.isFile() || !defaultPrivateKeyFile.canRead()) {
defaultPrivateKeyFile = null;
keyFilePath = null;
keyFile = null;
continue;
}
keyFile = defaultPrivateKeyFile;
keyFilePath = keyFile.getAbsolutePath();
}
continue;
}
if (keyFilePath == null) {
return null;
}
keyFile = new File(keyFilePath);
if (!keyFile.isFile() || !keyFile.canRead()) {
keyFilePath = null;
keyFile = null;
continue;
}
}
if(keyFile != null) {
String passphrasePrompt = null;
if (defaultPassphrase != null) {
passphrasePrompt = "Private key passphrase [";
for (int i = 0; i < defaultPassphrase.length(); i++) {
passphrasePrompt += "*";
}
passphrasePrompt += "]";
} else {
passphrasePrompt = "Private key passphrase [none]";
}
passphrase = promptPassword(passphrasePrompt);
if ("".equals(passphrase)) {
if (defaultPassphrase != null) {
passphrase = defaultPassphrase;
} else {
passphrase = null;
}
} else if (passphrase == null) {
return null;
}
}
}
int port = defaultPort > 0 ? defaultPort : 22;
String portValue = prompt("Port number for '" + url.getHost() + "' [" + port + "]");
if (portValue == null) {
return null;
}
if (!"".equals(portValue)) {
try {
port = Integer.parseInt(portValue);
} catch (NumberFormatException e) {}
}
if (password != null) {
return new SVNSSHAuthentication(name, password, port, authMayBeStored, url, false);
} else if (keyFile != null) {
return new SVNSSHAuthentication(name, keyFile, passphrase, port, authMayBeStored, url, false);
} else {
try {
Connector connector = ConnectorFactory.getDefault().createConnector();
AgentProxy agentConnection = new TrileadAgentProxy(connector);
return new SVNSSHAuthentication(name, agentConnection, port, url, false);
} catch (AgentProxyException e) {
return null;
}
}
} else if (ISVNAuthenticationManager.USERNAME.equals(kind)) {
String name = System.getProperty("user.name");
if (name != null && "".equals(name.trim())) {
name = null;
}
if (name != null) {
return new SVNUserNameAuthentication(name, authMayBeStored, url, false);
}
printRealm(realm);
name = prompt(!"file".equals(url.getProtocol()) ?
"Author name [" + System.getProperty("user.name") + "]" :
"Username [" + System.getProperty("user.name") + "]");
if (name == null) {
return null;
}
if ("".equals(name.trim())) {
name = System.getProperty("user.name");
}
return new SVNUserNameAuthentication(name, authMayBeStored, url, false);
} else if (ISVNAuthenticationManager.SSL.equals(kind)) {
if (SVNSSLAuthentication.isCertificatePath(realm)) {
String passphrase = promptPassword("Passphrase for '" + realm + "'");
if (passphrase == null) {
return null;
}
return new SVNPasswordAuthentication("", passphrase, authMayBeStored, url, false);
}
boolean isMSCAPI = false;
printRealm(realm);
String path = null;
while(path == null) {
path = prompt("Client certificate filename or 'MSCAPI'");
if ("".equals(path)) {
continue;
}
if (path == null) {
return null;
}
if (path.startsWith(SVNSSLAuthentication.MSCAPI)) {
isMSCAPI = true;
} else {
File file = new File(path);
if (!file.isFile() || !file.canRead()) {
path = null;
continue;
}
}
}
if (isMSCAPI) {
String alias = promptPassword("MSCAPI certificate alias");
if (alias == null) {
return null;
} else if ("".equals(alias)) {
alias = null;
}
return new SVNSSLAuthentication(SVNSSLAuthentication.MSCAPI, alias, authMayBeStored, url, false);
}
SVNSSLAuthentication sslAuth = new SVNSSLAuthentication(new File(path), null, authMayBeStored, url, false);
sslAuth.setCertificatePath(path);
return sslAuth;
}
return null;
}
public boolean canStorePlainTextPasswords(String realm, SVNAuthentication auth) throws SVNException {
return isPlainTextAllowed(realm, OUR_PASSWORD_PROMPT_TEXT, OUR_PASSWORD_PROMPT_STRING);
}
public boolean canStorePlainTextPassphrases(String realm, SVNAuthentication auth) throws SVNException {
return isPlainTextAllowed(realm, OUR_PASSPHRASE_PROMPT_TEXT, OUR_PASSPHRASE_PROMPT_STRING);
}
private boolean isPlainTextAllowed(String realm, String promptText, String promptString) {
File configPath = new File(SVNWCUtil.getDefaultConfigurationDirectory(), "servers");
String formattedMessage = MessageFormat.format(promptText, new Object[] { realm, configPath.getAbsolutePath() });
System.err.print(formattedMessage);
while (true) {
System.err.print(promptString);
System.err.flush();
String answer = readLine();
if ("yes".equalsIgnoreCase(answer)) {
return true;
} else if ("no".equalsIgnoreCase(answer)) {
return false;
}
promptString = "Please type 'yes' or 'no': ";
}
}
private static void printRealm(String realm) {
if (realm != null) {
System.err.println("Authentication realm: " + realm);
System.err.flush();
}
}
private static String prompt(String label) {
System.err.print(label + ": ");
System.err.flush();
return readLine();
}
private static String promptPassword(String label) {
System.err.print(label + ": ");
System.err.flush();
Class systemClass = System.class;
try {
// try to use System.console().readPassword - since JDK 1.6
Method consoleMethod = systemClass.getMethod("console", new Class[0]);
if (consoleMethod != null) {
Object consoleObject = consoleMethod.invoke(null, new Object[0]);
if (consoleObject != null) {
Class consoleClass = consoleObject.getClass();
Method readPasswordMethod = consoleClass.getMethod("readPassword", new Class[0]);
if (readPasswordMethod != null) {
Object password = readPasswordMethod.invoke(consoleObject, new Object[0]);
if (password == null) {
return null;
} else if (password instanceof char[]) {
return new String((char[]) password);
}
}
}
}
} catch (SecurityException e) {
} catch (NoSuchMethodException e) {
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {
}
return readLine();
}
private static String readLine() {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
try {
return reader.readLine();
} catch (IOException e) {
return null;
}
}
public boolean isSSLPassphrasePromtSupported() {
return true;
}
public String getKeyringPassword(String keyringName) throws SVNException {
return promptPassword("Password for '" + keyringName + "' GNOME keyring");
}
}