/*
* Copyright 2009 Perforce Software Inc., All Rights Reserved.
*/
package com.perforce.p4java.impl.mapbased.rpc;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import com.perforce.p4java.Log;
import com.perforce.p4java.PropertyDefs;
import com.perforce.p4java.env.PerforceEnvironment;
import com.perforce.p4java.exception.AccessException;
import com.perforce.p4java.exception.ConfigException;
import com.perforce.p4java.exception.ConnectionException;
import com.perforce.p4java.exception.MessageGenericCode;
import com.perforce.p4java.exception.MessageSeverityCode;
import com.perforce.p4java.exception.NullPointerError;
import com.perforce.p4java.exception.P4JavaException;
import com.perforce.p4java.exception.RequestException;
import com.perforce.p4java.exception.TrustException;
import com.perforce.p4java.impl.mapbased.rpc.connection.RpcConnection;
import com.perforce.p4java.impl.mapbased.rpc.func.client.ClientTrust;
import com.perforce.p4java.impl.mapbased.rpc.func.proto.PerformanceMonitor;
import com.perforce.p4java.impl.mapbased.rpc.func.proto.ProtocolCommand;
import com.perforce.p4java.impl.mapbased.rpc.helper.RpcUserAuthCounter;
import com.perforce.p4java.impl.mapbased.rpc.msg.RpcMessage;
import com.perforce.p4java.impl.mapbased.rpc.packet.helper.RpcPacketFieldRule;
import com.perforce.p4java.impl.mapbased.rpc.stream.RpcStreamConnection;
import com.perforce.p4java.impl.mapbased.server.Server;
import com.perforce.p4java.option.UsageOptions;
import com.perforce.p4java.option.server.TrustOptions;
import com.perforce.p4java.server.AuthTicketsHelper;
import com.perforce.p4java.server.CmdSpec;
import com.perforce.p4java.server.Fingerprint;
import com.perforce.p4java.server.FingerprintsHelper;
import com.perforce.p4java.server.IServerImplMetadata;
import com.perforce.p4java.server.IServerInfo;
import com.perforce.p4java.server.ServerStatus;
/**
* RPC-based Perforce server implementation superclass.
*/
public abstract class RpcServer extends Server {
/**
* The implementation type of this implementation.
*/
public static final IServerImplMetadata.ImplType IMPL_TYPE
= IServerImplMetadata.ImplType.NATIVE_RPC;
/**
* The default string sent to the Perforce server in the protocol
* command defining the client's program name. This can be set with the
* IServer interface.
*/
public static final String DEFAULT_PROG_NAME = "p4jrpc";
/**
* The default string sent to the Perforce server in the protocol
* command defining the client's program version. This can be set with the
* IServer interface.
*/
public static final String DEFAULT_PROG_VERSION = "Beta 1.0";
/**
* Default Perforce client API level; 76 represents 2014.1 capabilities.
* Don't change this unless you know what you're doing. Note that this
* is a default for most commands; some commands dynamically bump up
* the level for the command's duration.
*/
public static final int DEFAULT_CLIENT_API_LEVEL = 76; // 2014.1
// p4/msgs/p4tagl.cc
// p4/client/client.cc
// p4/server/rhservice.h
/**
* Default Perforce server API level; 99999 apparently means "whatever...".
* Don't change this unless you know what you're doing.
*/
public static final int DEFAULT_SERVER_API_LEVEL = 99999; // As picked off the wire for C++ API
// p4/client/clientmain.cc
// p4/server/rhservice.cc
/**
* Signifies whether or not we use tagged output. Don't change this unless
* you like weird incomprehensible errors and days of debugging.
*/
public static final boolean RPC_TAGS_USED = true;
/**
* Signifies whether or not the client is capable of handling streams.
*/
public static final boolean RPC_ENABLE_STREAMS = true;
/**
* The system properties key for the JVM's current directory.
*/
public static final String RPC_ENV_CWD_KEY = "user.dir";
/**
* The system properties key for the OS name.
*/
public static final String RPC_ENV_OS_NAME_KEY = "os.name";
/**
* RPC_ENV_OS_NAME_KEY property value prefix for Windows systems.
*/
public static final String RPC_ENV_WINDOWS_PREFIX = "windows";
/**
* What we use in the RPC environment packet to signal to the
* Perforce server that we're a Windows box.
*/
public static final String RPC_ENV_WINDOWS_SPEC = "NT"; // sic
/**
* What we use in the RPC environment packet to signal to the
* Perforce server that we're a NON-Windows box.
*/
public static final String RPC_ENV_UNIX_SPEC = "UNIX";
/**
* What we use in the RPC environment packet to signal to the Perforce
* server that we don't have a client set yet or don't know what it is.
*/
public static final String RPC_ENV_NOCLIENT_SPEC = "unknownclient";
/**
* What we use in the RPC environment packet to signal to the Perforce
* server that we don't have a hostname set yet or don't know what it is.
*/
public static final String RPC_ENV_NOHOST_SPEC = "nohost"; // as cribbed from the C++ API...
/**
* What we use in the RPC environment packet to signal to the Perforce
* server that we don't have a client set yet or don't know what it is.
*/
public static final String RPC_ENV_NOUSER_SPEC = "nouser"; // as cribbed from the C++ API...
/**
* What we use as a P4JTracer trace prefix for methods here.
*/
public static final String TRACE_PREFIX = "RpcServer";
/**
* Used to key temporary output streams in the command environment's
* state map for things like getFileContents(), etc., using the execStreamCmd
* method(s).
*/
public static final String RPC_TMP_OUTFILE_STREAM_KEY = "";
/**
* Use to key converter to use out of state map
*/
public static final String RPC_TMP_CONVERTER_KEY = "RPC_TMP_CONVERTER_KEY";
protected String localHostName = null; // intended to be just the unqualified name...
protected int clientApiLevel = DEFAULT_CLIENT_API_LEVEL;
protected int serverApiLevel = DEFAULT_SERVER_API_LEVEL;
protected String applicationName = null; // Application name
private static final String AUTH_FAIL_STRING_1 = "Single sign-on on client failed"; // SSO failure
private static final String AUTH_FAIL_STRING_2 = "Password invalid";
private static final String[] accessErrMsgs = {
CORE_AUTH_FAIL_STRING_1,
CORE_AUTH_FAIL_STRING_2,
CORE_AUTH_FAIL_STRING_3,
CORE_AUTH_FAIL_STRING_4,
AUTH_FAIL_STRING_1,
AUTH_FAIL_STRING_2
};
private static final String PASSWORD_NOT_SET_STRING = "no password set for this user";
protected long connectionStart = 0;
protected Map<String, Object> serverProtocolMap = new HashMap<String, Object>();
private PerformanceMonitor perfMonitor = new PerformanceMonitor();
protected ServerStats serverStats = null;
protected String serverId = null;
protected Map<String, String> secretKeys = new HashMap<String, String>();
protected ClientTrust clientTrust = null;
protected String ticketsFilePath = null;
protected String trustFilePath = null;
protected RpcUserAuthCounter authCounter = new RpcUserAuthCounter();
/**
* The RPC command args before the function name (i.e. "tag")
*/
protected Map<String, Object> cmdMapArgs = null;
/**
* If true, relax the command name validation checks done
* in the RPC layer. This is dangerous, and any use of this
* should only be done if you know what you're doing and you're
* able to deal with the consequences (which won't be spelled
* out here).
*/
protected boolean relaxCmdNameValidationChecks = false;
/**
* The default init sets up things like host names, etc., and fails if
* we can't establish some pretty basic things at connect time. Does
* <i>not</i> attempt to actually connect to the target Perforce
* server -- this is left for the connect() call, below.
*
* @see com.perforce.p4java.impl.mapbased.server.Server#init(java.lang.String, int, java.util.Properties)
*/
public ServerStatus init(String host, int port, Properties props)
throws ConfigException, ConnectionException {
return init(host, port, props, null);
}
public ServerStatus init(String host, int port, Properties props, UsageOptions opts)
throws ConfigException, ConnectionException {
return init(host, port, props, opts, false);
}
public ServerStatus init(String host, int port, Properties props, UsageOptions opts, boolean secure)
throws ConfigException, ConnectionException {
super.init(host, port, props, opts, secure);
try {
this.cmdMapArgs = new HashMap<String, Object>();
this.cmdMapArgs.put(ProtocolCommand.RPC_ARGNAME_PROTOCOL_ZTAGS, "");
this.relaxCmdNameValidationChecks = RpcPropertyDefs.getPropertyAsBoolean(props,
RpcPropertyDefs.RPC_RELAX_CMD_NAME_CHECKS_NICK, false);
this.applicationName = RpcPropertyDefs.getProperty(props,
RpcPropertyDefs.RPC_APPLICATION_NAME_NICK, null);
if (this.getUsageOptions().getHostName() != null) {
// This had better reflect reality...
this.localHostName = this.getUsageOptions().getHostName();
} else {
this.localHostName = java.net.InetAddress.getLocalHost().getHostName();
}
if (this.localHostName == null) {
throw new NullPointerError("Null client host name in RPC connection init");
}
if (!this.useAuthMemoryStore) {
// Search properties for ticket file path, fix for job035376
this.ticketsFilePath = this.props.getProperty(PropertyDefs.TICKET_PATH_KEY_SHORT_FORM,
this.props.getProperty(PropertyDefs.TICKET_PATH_KEY));
// Search environment variable
if (this.ticketsFilePath == null) {
this.ticketsFilePath = PerforceEnvironment.getP4Tickets();
}
// Search standard OS location
if (this.ticketsFilePath == null) {
this.ticketsFilePath = getDefaultP4TicketsFile();
}
// Search properties for trust file path
this.trustFilePath = this.props.getProperty(PropertyDefs.TRUST_PATH_KEY_SHORT_FORM,
this.props.getProperty(PropertyDefs.TRUST_PATH_KEY));
// Search environment variable
if (this.trustFilePath == null) {
this.trustFilePath = PerforceEnvironment.getP4Trust();
}
// Search standard OS location
if (this.trustFilePath == null) {
this.trustFilePath = getDefaultP4TrustFile();
}
}
this.serverStats = new ServerStats();
} catch (UnknownHostException uhe) {
throw new ConfigException("Unable to determine client host name: "
+ uhe.getLocalizedMessage());
}
// Initialize client trust
clientTrust = new ClientTrust(this);
return status;
}
/**
* Check the fingerprint of the Perforce server SSL connection
*
* @throws ConnectionException
*/
protected void checkFingerprint(RpcConnection rpcConnection) throws ConnectionException {
if (rpcConnection != null && rpcConnection.isSecure() && !rpcConnection.isTrusted()) {
if (rpcConnection.getFingerprint() == null) {
throw new ConnectionException("Null fingerprint for this Perforce SSL connection");
}
// New connection
if (!clientTrust.fingerprintExists(rpcConnection.getServerIpPort())) {
throw new TrustException(TrustException.Type.NEW_CONNECTION,
getServerHostPort(),
rpcConnection.getServerIpPort(),
rpcConnection.getFingerprint(),
clientTrust.getMessages().getMessage(
ClientTrust.CLIENT_TRUST_WARNING_NEW_CONNECTION,
new Object[] { getServerHostPort(), rpcConnection.getFingerprint() }) +
clientTrust.getMessages().getMessage(
ClientTrust.CLIENT_TRUST_EXCEPTION_NEW_CONNECTION)
);
}
// New key
if (!clientTrust.fingerprintMatches(rpcConnection.getServerIpPort(),
rpcConnection.getFingerprint())) {
throw new TrustException(TrustException.Type.NEW_KEY,
getServerHostPort(),
rpcConnection.getServerIpPort(),
rpcConnection.getFingerprint(),
clientTrust.getMessages().getMessage(
ClientTrust.CLIENT_TRUST_WARNING_NEW_KEY,
new Object[] { getServerHostPort(), rpcConnection.getFingerprint() }) +
clientTrust.getMessages().getMessage(
ClientTrust.CLIENT_TRUST_EXCEPTION_NEW_KEY)
);
}
// Trust this connection
rpcConnection.setTrusted(true);
}
}
/**
* Try to establish an actual RPC connection to the target Perforce server.
* Most of the actual setup work is done in the RpcConnection and
* RpcPacketDispatcher constructors, but associated gubbins such as
* auto login, etc., are done in the superclass.
*
* @see com.perforce.p4java.impl.mapbased.server.Server#connect()
*/
public void connect() throws ConnectionException, AccessException, RequestException, ConfigException {
this.connectionStart = System.currentTimeMillis();
super.connect();
}
/**
* Try to cleanly disconnect from the Perforce server at the other end
* of the current connection (with the emphasis on "cleanly"). This
* should theoretically include sending a release2 message, but we
* don't always get the chance to do that.
*
* @see com.perforce.p4java.impl.mapbased.server.Server#disconnect()
*/
public void disconnect() throws ConnectionException, AccessException {
super.disconnect();
if (this.connectionStart != 0) {
Log.stats("RPC connection connected for "
+ (System.currentTimeMillis() - this.connectionStart)
+ " msec elapsed time");
}
this.serverStats.logStats();
// Clear up all counts for this RPC server
this.authCounter.clearCount();
}
/**
* @see com.perforce.p4java.server.IServer#supportsSmartMove()
*/
public boolean supportsSmartMove() throws ConnectionException,
RequestException, AccessException {
// Return true iff server version >= 2009.1
// and move command is not disabled on server
if (this.serverVersion < 20091)
return false;
IServerInfo info = getServerInfo();
if (info == null)
return false;
return !info.isMoveDisabled();
}
/**
* @see com.perforce.p4java.server.IOptionsServer#getErrorOrInfoStr(java.util.Map)
*/
public String getErrorOrInfoStr(Map<String, Object> map) {
return getString(map, MessageSeverityCode.E_INFO);
}
public boolean isInfoMessage(Map<String, Object> map) {
if (map != null) {
return (RpcMessage.getSeverity((String) map.get("code0")) == MessageSeverityCode.E_INFO);
}
return false;
}
public int getSeverityCode(Map<String, Object> map) {
// Note: only gets first severity, i.e. code0:
if ((map != null) && map.containsKey("code0")) {
return RpcMessage.getSeverity((String) map.get("code0"));
}
return MessageSeverityCode.E_EMPTY;
}
protected int getGenericCode(Map<String, Object> map) {
// Note: only gets first code, i.e. code0:
if ((map != null) && map.containsKey("code0")) {
return RpcMessage.getGeneric((String) map.get("code0"));
}
return MessageGenericCode.EV_NONE;
}
private String getString(Map<String, Object> map, int minimumCode ) {
if (map != null) {
int index = 0;
String code = (String) map.get(RpcMessage.CODE + index);
// Return if no code0 key found
if (code == null) {
return null;
}
boolean foundCode = false;
StringBuilder codeString = new StringBuilder();
while (code != null) {
int severity = RpcMessage.getSeverity(code);
if (severity >= minimumCode) {
foundCode = true;
String fmtStr = (String) map.get(RpcMessage.FMT + index);
if (fmtStr != null) {
if (fmtStr.indexOf('%') != -1) {
fmtStr = RpcMessage.interpolateArgs(fmtStr, map);
}
// Insert latest message at beginning of error string
// since server structures them this way
codeString.insert(0, fmtStr);
codeString.insert(fmtStr.length(), '\n');
}
}
index++;
code = (String) map.get(RpcMessage.CODE + index);
}
// Only return a string if at least one severity code was found
if (foundCode) {
return codeString.toString();
}
}
return null;
}
/**
* RPC impl errors come across the wire as a map in the form usually like this:
* <pre>
* fmt0=Access for user '%user%' has not been enabled by 'p4 protect'.,
* func=client-Message, user=nouser, code0=822483067
* </pre>
* With tags being used for non-error payloads, we can just basically
* pick up the presence of the code0 entry; if it's there, use fmt0
* as the format and the other args as appropriate...<p>
*
* FIXME: work with multiple code/fmt sets... -- HR.
*
* @see com.perforce.p4java.server.IOptionsServer#getErrorStr(java.util.Map)
*/
public String getErrorStr(Map<String, Object> map) {
return getString(map, MessageSeverityCode.E_FAILED);
}
/**
* @see com.perforce.p4java.server.IOptionsServer#getInfoStr(java.util.Map)
*/
public String getInfoStr(Map<String, Object> map) {
if (map != null) {
String code0 = (String) map.get("code0");
int severity = RpcMessage.getSeverity(code0);
if (severity == MessageSeverityCode.E_INFO) {
String fmtStr = (String) map.get("fmt0");
if (fmtStr == null) {
return ""; // a la the p4 command line version
}
if (!fmtStr.contains("%")) {
return fmtStr;
}
return RpcMessage.interpolateArgs(fmtStr, map);
}
}
return null;
}
/**
* @see com.perforce.p4java.impl.mapbased.server.Server#isAuthFail(java.lang.String)
*/
@Override
public boolean isAuthFail(String errStr) {
if (errStr != null) {
for (String str : accessErrMsgs) {
if (errStr.contains(str)) {
return true;
}
}
}
return false;
}
/**
* @see com.perforce.p4java.impl.mapbased.server.Server#isLoginNotRequired(java.lang.String)
*/
@Override
public boolean isLoginNotRequired(String msgStr) {
if (msgStr != null) {
if (msgStr.contains(PASSWORD_NOT_SET_STRING)) {
return true;
}
}
return false;
}
public int getClientApiLevel() {
return this.clientApiLevel;
}
public void setClientApiLevel(int clientApiLevel) {
this.clientApiLevel = clientApiLevel;
}
public String getApplicationName() {
return this.applicationName;
}
public void setApplicationName(String applicationName) {
this.applicationName = applicationName;
}
public PerformanceMonitor getPerfMonitor() {
return this.perfMonitor;
}
public void setPerfMonitor(PerformanceMonitor perfMonitor) {
this.perfMonitor = perfMonitor;
}
protected String getOsTypeForEnv() {
String osName = System.getProperty(RPC_ENV_OS_NAME_KEY);
if ((osName != null) && osName.toLowerCase(Locale.ENGLISH).contains(RPC_ENV_WINDOWS_PREFIX)){
return RPC_ENV_WINDOWS_SPEC;
}
return RPC_ENV_UNIX_SPEC; // sic -- as seen in the C++ API...
}
protected String getLanguageForEnv() {
return this.getUsageOptions().getTextLanguage();
}
protected String getClientNameForEnv() {
if (getClientName() != null) {
return getClientName();
} else {
return this.getUsageOptions().getUnsetClientName();
}
}
protected String getHostForEnv() {
if (localHostName != null) {
return localHostName;
}
return RPC_ENV_NOHOST_SPEC;
}
protected String getUserForEnv() {
if (getUserName() != null) {
return getUserName();
}
return this.getUsageOptions().getUnsetUserName();
}
protected void processCmdCallbacks(int cmdCallBackKey, long timeTaken, List<Map<String, Object>> resultMaps) {
this.commandCallback.completedServerCommand(cmdCallBackKey, timeTaken);
if (resultMaps != null) {
for (Map<String, Object> map : resultMaps) {
String str = getErrorOrInfoStr(map);
if (str != null) str = str.trim();
int severity = getSeverityCode(map);
int generic = getGenericCode(map);
if (severity != MessageSeverityCode.E_EMPTY) {
this.commandCallback.receivedServerMessage(cmdCallBackKey, generic, severity,
str);
}
if (severity == MessageSeverityCode.E_INFO) {
this.commandCallback.receivedServerInfoLine(cmdCallBackKey, str);
} else if (severity >= MessageSeverityCode.E_FAILED) {
this.commandCallback.receivedServerErrorLine(cmdCallBackKey, str);
}
}
}
}
/**
* Save current ticket returned from {@link #getAuthTicket()}.
*
* @see #saveTicket(String)
* @throws P4JavaException
*/
public void saveCurrentTicket() throws P4JavaException {
saveTicket(getAuthTicket());
}
/**
* Save specified auth ticket value as associate with this server's address
* and configured user returned from {@link #getUserName()}. This will
* attempt to write an entry to the p4tickets file either specified as the
* P4TICKETS environment variable or at the OS specific default location. If
* the ticket value is null then the current entry in the will be cleared.
*
* @param ticketValue
* @throws ConfigException
*/
public void saveTicket(String ticketValue) throws ConfigException {
saveTicket(getUserName(), ticketValue);
}
/**
* Save specified auth ticket value as associate with this server's address
* and user name from the userName parameter. This will attempt to write
* an entry to the p4tickets file either specified as the P4TICKETS environment
* variable or at the OS specific default location. If the ticket value
* is null then the current entry will be cleared.
*
* @param userName
* @param ticketValue
* @throws ConfigException
*/
public void saveTicket(String userName, String ticketValue) throws ConfigException {
ConfigException exception = null;
// Must downcase the username to find or save a ticket when
// connected to a case insensitive server.
if (!isCaseSensitive() && userName != null) {
userName = userName.toLowerCase();
}
String serverId = getServerId();
// Try to save the ticket by server id first if set
if (serverId != null) {
try {
AuthTicketsHelper.saveTicket(userName, serverId, ticketValue,
this.ticketsFilePath);
} catch (IOException e) {
exception = new ConfigException(e);
}
}
// If id is null try to use configured server address
// If ticket value is null try to clear out any old values by the
// configured server address
if (ticketValue == null || serverId == null) {
// Try to save the ticket by server address
String server = null;
if (this.serverHost != null) {
server = this.serverHost;
if (this.serverPort != UNKNOWN_SERVER_PORT) {
server += ":" + Integer.toString(this.serverPort);
}
} else if (this.serverPort != UNKNOWN_SERVER_PORT) {
server = Integer.toString(this.serverPort);
}
if (server != null) {
try {
AuthTicketsHelper.saveTicket(userName, server, ticketValue,
this.ticketsFilePath);
} catch (IOException e) {
// Use first exception if thrown
if (exception == null) {
exception = new ConfigException(e);
}
}
}
}
//Throw the exception from either save attempt
if (exception != null) {
throw exception;
}
}
/**
* Get the p4tickets entry value for the current user returned from
* {@link #getUserName()} and server address based upon a search of either
* the file found at {@link PropertyDefs#TICKET_PATH_KEY_SHORT_FORM},
* {@link PropertyDefs#TICKET_PATH_KEY}, the P4TICKETS environment variable
* or the standard p4tickets file location for the current OS. Will return
* null if not found or if an error occurred attempt to lookup the value.
*
* @param serverId
*
* @return - ticket value to get used for {@link #setAuthTicket(String)} or
* null if not found.
*/
public String loadTicket(String serverId) {
String ticketValue = null;
String name = getUserName();
if (name != null) {
try {
ticketValue = AuthTicketsHelper.getTicketValue(name,
serverId, this.ticketsFilePath);
} catch (IOException e) {
ticketValue = null;
}
if (ticketValue == null) {
String server = null;
if (this.serverHost != null) {
server = this.serverHost;
if (this.serverPort != UNKNOWN_SERVER_PORT) {
server += ":" + Integer.toString(this.serverPort);
}
} else if (this.serverPort != UNKNOWN_SERVER_PORT) {
server = Integer.toString(this.serverPort);
}
try {
ticketValue = AuthTicketsHelper.getTicketValue(name,
server, this.ticketsFilePath);
} catch (IOException e) {
ticketValue = null;
}
}
}
return ticketValue;
}
/**
* Save specified fingerprint value as associate with this server's address.
* This will attempt to write an entry to the p4trust file either specified
* as the P4TRUST environment variable or at the OS specific default location.
* If the fingerprint value is null then the current entry will be cleared.
*
* @param rpcConnection
* @param fingerprintValue
* @throws ConfigException
*/
public void saveFingerprint(String serverIpPort, String fingerprintValue) throws ConfigException {
if (serverIpPort == null) {
return;
}
// Save the fingerprint by server IP and port
try {
FingerprintsHelper.saveFingerprint(
ClientTrust.USER_NAME_PLACE_HOLDER,
serverIpPort, fingerprintValue, this.trustFilePath);
} catch (IOException e) {
throw new ConfigException(e);
}
}
/**
* Get the p4trust entry value for the server IP and port based upon a search
* of either the file found at {@link PropertyDefs#TRUST_PATH_KEY_SHORT_FORM},
* {@link PropertyDefs#TRUST_PATH_KEY}, the P4TRUST environment variable
* or the standard p4trust file location for the current OS. Will return
* null if not found or if an error occurred attempt to lookup the value.
*
* @param serverIpPort
*
* @return - fingerprint or null if not found.
*/
public Fingerprint loadFingerprint(String serverIpPort) {
if (serverIpPort == null) {
return null;
}
Fingerprint fingerprint = null;
try {
fingerprint = FingerprintsHelper.getFingerprint(ClientTrust.USER_NAME_PLACE_HOLDER,
serverIpPort, this.trustFilePath);
} catch (IOException e) {
fingerprint = null;
}
return fingerprint;
}
/**
* Get the p4trust entries from the file found at {@link PropertyDefs#TRUST_PATH_KEY_SHORT_FORM},
* {@link PropertyDefs#TRUST_PATH_KEY}, the P4TRUST environment variable
* or the standard p4trust file location for the current OS. Will return
* null if nothing found or if an error occurred attempt to lookup the entries.
*
* @return - list of fingerprints or null if nothing found.
*/
public Fingerprint[] loadFingerprints() {
Fingerprint[] fingerprints = null;
try {
fingerprints = FingerprintsHelper.getFingerprints(this.trustFilePath);
} catch (IOException e) {
// Suppress error
}
return fingerprints;
}
/**
* @see com.perforce.p4java.impl.mapbased.server.Server#getTrust()
*/
public String getTrust() throws P4JavaException {
RpcConnection rpcConnection = null;
try {
rpcConnection = new RpcStreamConnection(serverHost, serverPort, props,
this.serverStats, this.charset, this.secure);
return rpcConnection.getFingerprint();
} finally {
if (rpcConnection != null) {
rpcConnection.disconnect(null);
}
}
}
/**
* @see com.perforce.p4java.impl.mapbased.server.Server#addTrust(com.perforce.p4java.option.server.TrustOptions)
*/
public String addTrust(TrustOptions opts) throws P4JavaException {
RpcConnection rpcConnection = null;
try {
rpcConnection = new RpcStreamConnection(serverHost, serverPort, props,
this.serverStats, this.charset, this.secure);
if (opts == null) {
opts = new TrustOptions();
}
String newConnectionWarning = clientTrust.getMessages().getMessage(
ClientTrust.CLIENT_TRUST_WARNING_NEW_CONNECTION,
new Object[] { getServerHostPort(), rpcConnection.getFingerprint() });
String newKeyWarning = clientTrust.getMessages().getMessage(
ClientTrust.CLIENT_TRUST_WARNING_NEW_KEY,
new Object[] { getServerHostPort(), rpcConnection.getFingerprint() });
boolean fpExist = clientTrust.fingerprintExists(rpcConnection.getServerIpPort());
boolean fpMatch = clientTrust.fingerprintMatches(rpcConnection.getServerIpPort(),
rpcConnection.getFingerprint());
// auto refuse
if (opts.isAutoRefuse()) {
// new connection
if (!fpExist) {
return newConnectionWarning;
}
// new key
if (!fpMatch) {
return newKeyWarning;
}
}
// new connection
if (!fpExist) {
// auto accept
if (opts.isAutoAccept()) {
// install fingerprint
clientTrust.installFingerprint(rpcConnection.getServerIpPort(),
rpcConnection.getFingerprint());
return newConnectionWarning +
clientTrust.getMessages().getMessage(ClientTrust.CLIENT_TRUST_ADDED,
new Object[] { getServerHostPort(), rpcConnection.getServerIpPort() });
}
// didn't accept
throw new TrustException(TrustException.Type.NEW_CONNECTION,
getServerHostPort(),
rpcConnection.getServerIpPort(),
rpcConnection.getFingerprint(),
newConnectionWarning +
clientTrust.getMessages().getMessage(ClientTrust.CLIENT_TRUST_ADD_EXCEPTION_NEW_CONNECTION)
);
}
// new key
if (!fpMatch) {
// force install
if (opts.isForce()) {
// auto accept
if (opts.isAutoAccept()) {
// install fingerprint
clientTrust.installFingerprint(rpcConnection.getServerIpPort(),
rpcConnection.getFingerprint());
return newKeyWarning +
clientTrust.getMessages().getMessage(ClientTrust.CLIENT_TRUST_ADDED,
new Object[] { getServerHostPort(), rpcConnection.getServerIpPort() });
}
// didn't accept
throw new TrustException(TrustException.Type.NEW_KEY,
getServerHostPort(),
rpcConnection.getServerIpPort(),
rpcConnection.getFingerprint(),
newKeyWarning +
clientTrust.getMessages().getMessage(ClientTrust.CLIENT_TRUST_ADD_EXCEPTION_NEW_KEY)
);
}
// not force install
throw new TrustException(TrustException.Type.NEW_KEY,
getServerHostPort(),
rpcConnection.getServerIpPort(),
rpcConnection.getFingerprint(),
newKeyWarning +
clientTrust.getMessages().getMessage(ClientTrust.CLIENT_TRUST_ADD_EXCEPTION_NEW_KEY)
);
}
// trust already established
return clientTrust.getMessages().getMessage(ClientTrust.CLIENT_TRUST_ALREADY_ESTABLISHED);
} finally {
if (rpcConnection != null) {
rpcConnection.disconnect(null);
}
}
}
/**
* @see com.perforce.p4java.impl.mapbased.server.Server#addTrust(java.lang.String)
*/
public String addTrust(String fingerprintValue) throws P4JavaException {
if (fingerprintValue == null) {
throw new NullPointerError("null fingerprintValue passed to addTrust");
}
RpcConnection rpcConnection = null;
try {
rpcConnection = new RpcStreamConnection(serverHost, serverPort, props,
this.serverStats, this.charset, this.secure);
String message = "";
// new connection
if (!clientTrust.fingerprintExists(rpcConnection.getServerIpPort())) {
message = clientTrust.getMessages().getMessage(
ClientTrust.CLIENT_TRUST_WARNING_NEW_CONNECTION,
new Object[] { getServerHostPort(), rpcConnection.getFingerprint() });
} else {
// new key
if (!clientTrust.fingerprintMatches(rpcConnection.getServerIpPort(),
fingerprintValue)) {
message = clientTrust.getMessages().getMessage(
ClientTrust.CLIENT_TRUST_WARNING_NEW_KEY,
new Object[] { getServerHostPort(), rpcConnection.getFingerprint() });
}
}
// install the fingerprint to the trust file
clientTrust.installFingerprint(rpcConnection.getServerIpPort(),
fingerprintValue);
return message + clientTrust.getMessages().getMessage(
ClientTrust.CLIENT_TRUST_ADDED,
new Object[] { getServerHostPort(), rpcConnection.getServerIpPort() });
} finally {
if (rpcConnection != null) {
rpcConnection.disconnect(null);
}
}
}
/**
* @see com.perforce.p4java.impl.mapbased.server.Server#removeTrust()
*/
public String removeTrust() throws P4JavaException {
RpcConnection rpcConnection = null;
try {
rpcConnection = new RpcStreamConnection(serverHost, serverPort, props,
this.serverStats, this.charset, this.secure);
String message = clientTrust.getMessages().getMessage(
ClientTrust.CLIENT_TRUST_REMOVED,
new Object[] { getServerHostPort(), rpcConnection.getServerIpPort() });
// remove the fingerprint from the trust file
clientTrust.removeFingerprint(rpcConnection.getServerIpPort());
// new connection
if (!clientTrust.fingerprintExists(rpcConnection.getServerIpPort())) {
return clientTrust.getMessages().getMessage(
ClientTrust.CLIENT_TRUST_WARNING_NEW_CONNECTION,
new Object[] { getServerHostPort(), rpcConnection.getFingerprint() }) + message;
}
// new key
if (!clientTrust.fingerprintMatches(rpcConnection.getServerIpPort(),
rpcConnection.getFingerprint())) {
return clientTrust.getMessages().getMessage(
ClientTrust.CLIENT_TRUST_WARNING_NEW_KEY,
new Object[] { getServerHostPort(), rpcConnection.getFingerprint() }) + message;
}
return message;
} finally {
if (rpcConnection != null) {
rpcConnection.disconnect(null);
}
}
}
/**
* @see com.perforce.p4java.impl.mapbased.server.Server#getTrusts()
*/
public List<Fingerprint> getTrusts() throws P4JavaException {
Fingerprint[] fingerprints = loadFingerprints();
if (fingerprints != null) {
return Arrays.asList(fingerprints);
}
return null;
}
/**
* Set the server's id field used for storing authentication tickets. The id
* specified here will be used when saving ticket values to a p4 tickets
* file. This field should only be set to the server id returned as part of
* a server message.
*
* @param serverId
*/
public void setServerId(String serverId) {
this.serverId = serverId;
}
/**
* Get the server's id field used for storing authentication tickets. This
* id should only be used as a server address portion for entries in a p4
* tickets file.
*
* @return - possibly null server id
*/
public String getServerId() {
return this.serverId;
}
/**
* Return true iff we should be performing server -> client
* file write I/O operations in place for this command.<p>
*
* See PropertyDefs.WRITE_IN_PLACE_KEY javadoc for the semantics
* of this.
*
* @param cmdName non-null command command name string
* @return true iff we should do a sync in place
*/
protected boolean writeInPlace(String cmdName) {
return cmdName.equalsIgnoreCase(CmdSpec.SYNC.toString())
&& new Boolean(System.getProperty(
PropertyDefs.WRITE_IN_PLACE_KEY,
props.getProperty(
PropertyDefs.WRITE_IN_PLACE_SHORT_FORM, "false")));
}
public String getSecretKey() {
return getSecretKey(this.userName);
}
public void setSecretKey(String secretKey) {
setSecretKey(this.userName, secretKey);
}
public String getSecretKey(String userName) {
if (userName != null) {
return secretKeys.get(userName);
}
return null;
}
public void setSecretKey(String userName, String secretKey) {
if (userName != null) {
if (secretKey == null) {
this.secretKeys.remove(userName);
} else {
this.secretKeys.put(userName, secretKey);
}
}
}
protected boolean isRelaxCmdNameValidationChecks() {
return relaxCmdNameValidationChecks;
}
protected void setRelaxCmdNameValidationChecks(
boolean relaxCmdNameValidationChecks) {
this.relaxCmdNameValidationChecks = relaxCmdNameValidationChecks;
}
/**
* Get the RPC packet field rule for skipping the charset conversion of
* a range of RPC packet fields; leave the values as bytes. <p>
*
* Note: currently only supporting the "export" command.
*/
protected RpcPacketFieldRule getRpcPacketFieldRule(Map<String, Object> inMap, CmdSpec cmdSpec) {
if (inMap != null && cmdSpec != null) {
if (cmdSpec == CmdSpec.EXPORT) {
if (inMap.containsKey(cmdSpec.toString())) {
// The map for this command spec
if (inMap.get(cmdSpec.toString()) instanceof Map<?,?>) {
@SuppressWarnings("unchecked")
Map<String, Object> cmdMap = (Map<String, Object>)inMap.remove(cmdSpec.toString());
if (cmdMap != null) {
return RpcPacketFieldRule.getInstance(cmdMap);
}
}
}
}
}
return null;
}
/**
* @see com.perforce.p4java.impl.mapbased.server.Server#setAuthTicket(java.lang.String, java.lang.String)
*/
public void setAuthTicket(String userName, String authTicket) {
if (userName == null) {
throw new IllegalArgumentException("Null userName passed to the setAuthTicket method.");
}
// Must downcase the username to find or save a ticket when
// connected to a case insensitive server.
if (!isCaseSensitive() && userName != null) {
userName = userName.toLowerCase();
}
String serverAddress = getServerId() != null ? getServerId() : getServerAddress();
if (serverAddress == null) {
throw new IllegalStateException("Null serverAddress in the setAuthTicket method.");
}
if (authTicket == null) {
this.authTickets.remove(composeAuthTicketEntryKey(userName, serverAddress));
} else {
this.authTickets.put(composeAuthTicketEntryKey(userName, serverAddress), authTicket);
}
}
/**
* @see com.perforce.p4java.impl.mapbased.server.Server#getAuthTicket(java.lang.String)
*/
public String getAuthTicket(String userName) {
// Must downcase the username to find or save a ticket when
// connected to a case insensitive server.
if (!isCaseSensitive() && userName != null) {
userName = userName.toLowerCase();
}
String serverAddress = getServerId() != null ? getServerId() : getServerAddress();
if (userName != null && serverAddress != null) {
return this.authTickets.get(composeAuthTicketEntryKey(userName, serverAddress));
}
return null;
}
/**
* @see com.perforce.p4java.impl.mapbased.server.Server#setTicketsFilePath(java.lang.String)
*/
public void setTicketsFilePath(String ticketsFilePath) {
if (ticketsFilePath == null) {
throw new IllegalArgumentException("Null ticketsFilePath passed to the setTicketsFilePath method.");
}
this.ticketsFilePath = ticketsFilePath;
}
/**
* @see com.perforce.p4java.impl.mapbased.server.Server#getTicketsFilePath()
*/
public String getTicketsFilePath() {
return this.ticketsFilePath;
}
/**
* @see com.perforce.p4java.impl.mapbased.server.Server#setTrustFilePath(java.lang.String)
*/
public void setTrustFilePath(String trustFilePath) {
if (ticketsFilePath == null) {
throw new IllegalArgumentException("Null trustFilePath passed to the setTrustFilePath method.");
}
this.trustFilePath = trustFilePath;
}
/**
* @see com.perforce.p4java.impl.mapbased.server.Server#getTrustFilePath()
*/
public String getTrustFilePath() {
return this.trustFilePath;
}
/**
* Compose the key for an auth ticket entry
*/
protected String composeAuthTicketEntryKey(String userName, String serverAddress) {
if (userName == null) {
throw new IllegalArgumentException("Null userName passed to the composeAuthTicketEntryKey method.");
}
if (serverAddress == null) {
throw new IllegalArgumentException("Null serverAddress in the composeAuthTicketEntryKey method.");
}
if (serverAddress.indexOf(':') == -1) {
serverAddress += "localhost:" + serverAddress;
}
return (serverAddress + "=" + userName);
}
/**
* Get the server's address field used for storing authentication tickets.
*
* @return - possibly null server address
*/
public String getServerAddress() {
String serverAddress = this.serverAddress;
// If id is null try to use configured server address
if (serverAddress == null) {
if (this.serverHost != null) {
serverAddress = this.serverHost;
if (this.serverPort != UNKNOWN_SERVER_PORT) {
serverAddress += ":" + Integer.toString(this.serverPort);
}
} else if (this.serverPort != UNKNOWN_SERVER_PORT) {
serverAddress = Integer.toString(this.serverPort);
}
}
return serverAddress;
}
/**
* Get the server's host and port used for the RPC connection.
*
* @return - possibly null server host and port
*/
public String getServerHostPort() {
String serverHostPort = null;
if (this.serverHost != null) {
serverHostPort = this.serverHost;
if (this.serverPort != UNKNOWN_SERVER_PORT) {
serverHostPort += ":" + Integer.toString(this.serverPort);
}
} else if (this.serverPort != UNKNOWN_SERVER_PORT) {
serverHostPort = Integer.toString(this.serverPort);
}
return serverHostPort;
}
/**
* Allow for per-command use of tags or not. Currently has limited use
* (only a few commands are anomalous as far as we can tell), but may
* find more uses generally with experience.<p>.
*
* This is normally used on a per-command (OneShot RPC server) basis.
* In order to use this on a per-session (NTS RPC server) implementation
* you must resend the RPC protocol, if the 'useTags' state has changed,
* prior to sending the command.
*/
protected boolean useTags(String cmdName, String[] cmdArgs, Map<String, Object> inMap, boolean isStreamCmd) {
CmdSpec cmdSpec = CmdSpec.getValidP4JCmdSpec(cmdName);
if (cmdSpec != null) {
if (cmdSpec == CmdSpec.LOGIN) {
return false;
}
if (isStreamCmd) {
switch (cmdSpec) {
case DESCRIBE:
case DIFF2:
case PRINT:
case PROTECT:
return false;
default:
break;
}
}
// Check the inMap for any tagged/non-tagged override
if (inMap != null) {
if (inMap.containsKey(IN_MAP_USE_TAGS_KEY)) {
return new Boolean((String)inMap.remove(IN_MAP_USE_TAGS_KEY));
}
}
}
return RPC_TAGS_USED;
}
public RpcUserAuthCounter getAuthCounter() {
return this.authCounter;
}
}