diff --git a/pcml/RJobList.pcml b/pcml/RJobList.pcml index 0ac558c15..3583193ba 100644 --- a/pcml/RJobList.pcml +++ b/pcml/RJobList.pcml @@ -29,7 +29,7 @@ - + @@ -93,7 +93,7 @@ - + @@ -107,7 +107,7 @@ - + diff --git a/pcml/RJobLog.pcml b/pcml/RJobLog.pcml index 553e7aba6..d339cfb7c 100644 --- a/pcml/RJobLog.pcml +++ b/pcml/RJobLog.pcml @@ -30,7 +30,7 @@ - + @@ -66,7 +66,7 @@ - + @@ -80,7 +80,7 @@ - + diff --git a/pcml/RMessageQueue.pcml b/pcml/RMessageQueue.pcml index eb3ea7524..4eb69512a 100644 --- a/pcml/RMessageQueue.pcml +++ b/pcml/RMessageQueue.pcml @@ -22,7 +22,7 @@ - + @@ -68,7 +68,7 @@ - + @@ -82,7 +82,7 @@ - + diff --git a/pcml/RPrinter.pcml b/pcml/RPrinter.pcml index ea4b0759b..4a9995de4 100644 --- a/pcml/RPrinter.pcml +++ b/pcml/RPrinter.pcml @@ -15,7 +15,7 @@ - + diff --git a/pcml/RPrinterList.pcml b/pcml/RPrinterList.pcml index d7c317f44..b5b933f7c 100644 --- a/pcml/RPrinterList.pcml +++ b/pcml/RPrinterList.pcml @@ -15,7 +15,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ - + diff --git a/pcml/RUserList.pcml b/pcml/RUserList.pcml index 73746e9c8..6a2189797 100644 --- a/pcml/RUserList.pcml +++ b/pcml/RUserList.pcml @@ -15,7 +15,7 @@ - + @@ -41,7 +41,7 @@ - + @@ -55,7 +55,7 @@ - + diff --git a/src/main/java/com/ibm/as400/access/AS400.java b/src/main/java/com/ibm/as400/access/AS400.java index 7b0a5f4c2..dca163ba7 100644 --- a/src/main/java/com/ibm/as400/access/AS400.java +++ b/src/main/java/com/ibm/as400/access/AS400.java @@ -1109,6 +1109,24 @@ public AS400(AS400 system) } } + /** + * Returns a new instance of an AS400 object. + *

+ * If running on IBM i, the target is the local system. This has the same effect as using localhost for + * the system name, *CURRENT for the user ID, and *CURRENT for the password. + *

+ * If running on another operating system, a sign-on prompt may be displayed. The user is then able to specify the + * system name, user ID, and password. + * @param useSSL Whether or not the new AS400 object should use secure connections when communicating with the + * host servers. + * @return AS400 object. + **/ + public static AS400 newInstance(boolean useSSL) + { + return (useSSL) ? new SecureAS400() + : new AS400(); + } + /** * Returns a new instance of an AS400 object. It uses the specified system name. *

@@ -1935,46 +1953,6 @@ public void connectService(int service, int overridePort) throws AS400SecurityEx } } - /** - * Connects to a port on the server, via DHCP. Security is validated and a connection is established. - * - * @param port The port number to connect to. - * @return A Socket object representing the connection. - * @exception AS400SecurityException If a security or authority error occurs. - * @exception IOException If an error occurs while communicating with the system. - **/ - public Socket connectToPort(int port) throws AS400SecurityException, IOException - { - if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Connecting port:", port); - - chooseImpl(); - signon(false); - Socket s = impl_.connectToPort(port); - if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Port connected:", s.getPort()); - return s; - } - - /** - * Connects to a port on the server, via DHCP. Security is validated and a connection is established. - * - * @param port The port number to connect to. - * @param forceNonLocalhost whether to use localhost when connect to the port if running on as400 - * @return A Socket object representing the connection. - * @exception AS400SecurityException If a security or authority error occurs. - * @exception IOException If an error occurs while communicating with the system. - **/ - // Add this interface for L1C for the issue of DHCP server has listened on 942 for STRTCPSVR on localhost. - public Socket connectToPort(int port,boolean forceNonLocalhost) throws AS400SecurityException, IOException - { - if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Connecting port:", port); - - chooseImpl(); - signon(false); - Socket s = impl_.connectToPort(port,forceNonLocalhost); - if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Port connected:", s.getPort()); - return s; - } - // Common code for all the constuctors and readObject. private void construct() { @@ -1989,7 +1967,7 @@ private void construct() if (isSecure()) { - if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Constructing SecureAS400 object."); + if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Constructing secure AS400 object."); useSSLConnection_ = new SSLOptions(); @@ -2096,7 +2074,44 @@ private void fireConnectEvent(ConnectionEvent event, boolean connect) * @exception AS400SecurityException If a security or authority error occurs. * @exception IOException If an error occurs while communicating with the system. **/ - public ProfileTokenCredential generateProfileToken(String userIdentity, int tokenType, int timeoutInterval) throws AS400SecurityException, IOException + public ProfileTokenCredential generateProfileToken(String userIdentity, int tokenType, int timeoutInterval) throws AS400SecurityException, IOException { + return generateProfileToken(userIdentity, tokenType, timeoutInterval, null, null); + } + + /** + * Generates a profile token on behalf of the provided user identity. This user identity must be associated with a + * user profile via EIM. + *

+ * Invoking this method does not change the user ID and password assigned to the system or otherwise modify the user + * or authorities under which the application is running. The profile associated with this system object must have + * enough authority to generate an authentication token for another user. + *

+ * This function is only supported on i5/OS V5R3M0 or greater. + * + * @param userIdentity The LDAP distinguished name. + * @param tokenType The type of profile token to create. Possible types are defined as fields on the + * ProfileTokenCredential class: + *

    + *
  • {@link com.ibm.as400.security.auth.ProfileTokenCredential#TYPE_SINGLE_USE + * TYPE_SINGLE_USE} + *
  • {@link com.ibm.as400.security.auth.ProfileTokenCredential#TYPE_MULTIPLE_USE_NON_RENEWABLE + * TYPE_MULTIPLE_USE_NON_RENEWABLE} + *
  • {@link com.ibm.as400.security.auth.ProfileTokenCredential#TYPE_MULTIPLE_USE_RENEWABLE + * TYPE_MULTIPLE_USE_RENEWABLE} + *
+ * @param timeoutInterval The number of seconds to expiration when the token is created (1-3600). + * @param verificationID The verification ID that will be associated with profile token. The verification ID + * is the label that identifies the specific application, service, or action associated + * with the profile token request. A null value will result in the host server using a + * default value. + * @param remoteIPAddress The remote IP address (the IP address of the requester) that will be associated with + * profile token. A null value will result in the host server using a default value. + * + * @return A ProfileTokenCredential representing the provided user identity. + * @exception AS400SecurityException If a security or authority error occurs. + * @exception IOException If an error occurs while communicating with the system. + **/ + public ProfileTokenCredential generateProfileToken(String userIdentity, int tokenType, int timeoutInterval, String verificationID, String remoteIPAddress) throws AS400SecurityException, IOException { connectService(AS400.SIGNON); @@ -2109,6 +2124,8 @@ public ProfileTokenCredential generateProfileToken(String userIdentity, int toke profileToken.setSystem(this); profileToken.setTokenType(tokenType); profileToken.setTimeoutInterval(timeoutInterval); + profileToken.setVerificationID(verificationID); + profileToken.setRemoteIPAddress(remoteIPAddress); } catch (PropertyVetoException e) { @@ -2640,7 +2657,41 @@ public ProfileTokenCredential getProfileToken() throws AS400SecurityException, I * @exception IOException If an error occurs while communicating with the system. * @exception InterruptedException If this thread is interrupted. **/ - public ProfileTokenCredential getProfileToken(int tokenType, int timeoutInterval) throws AS400SecurityException, IOException, InterruptedException + public ProfileTokenCredential getProfileToken(int tokenType, int timeoutInterval) throws AS400SecurityException, IOException, InterruptedException { + return getProfileToken(tokenType, timeoutInterval, null, null); + } + + /** + * Authenticates the assigned user profile and password and returns a corresponding ProfileTokenCredential if + * successful. + *

+ * This function is not supported if the assigned password is *CURRENT and cannot be used to generate a renewable + * token. This function is only supported if the system is at i5/OS V4R5M0 or greater. + *

+ * Note: If an additional authentication factor has been set for the AS400 object, it will be used when + * generating the profile token. + * + * @param tokenType The type of profile token to create. Possible types are defined as fields on the + * ProfileTokenCredential class: + *

    + *
  • {@link com.ibm.as400.security.auth.ProfileTokenCredential#TYPE_SINGLE_USE + * TYPE_SINGLE_USE} + *
  • {@link com.ibm.as400.security.auth.ProfileTokenCredential#TYPE_MULTIPLE_USE_NON_RENEWABLE + * TYPE_MULTIPLE_USE_NON_RENEWABLE} + *
+ * @param timeoutInterval The number of seconds to expiration when the token is created (1-3600). + * @param verificationID The verification ID that will be associated with profile token. The verification ID + * is the label that identifies the specific application, service, or action associated + * with the profile token request. A null value will result in the host server using a + * default value. + * @param remoteIPAddress The remote IP address (the IP address of the requester) that will be associated with + * profile token. A null value will result in the host server using a default value. + * @return A ProfileTokenCredential representing the signed-on user. + * @exception AS400SecurityException If a security or authority error occurs. + * @exception IOException If an error occurs while communicating with the system. + * @exception InterruptedException If this thread is interrupted. + **/ + public ProfileTokenCredential getProfileToken(int tokenType, int timeoutInterval, String verificationID, String remoteIPAddress) throws AS400SecurityException, IOException, InterruptedException { connectService(AS400.SIGNON); @@ -2660,6 +2711,9 @@ public ProfileTokenCredential getProfileToken(int tokenType, int timeoutInterval profileToken.setSystem(this); profileToken.setTokenType(tokenType); profileToken.setTimeoutInterval(timeoutInterval); + profileToken.setVerificationID(verificationID); + profileToken.setRemoteIPAddress(remoteIPAddress); + profileToken.setAdditionalAuthenticationFactor(additionalAuthenticationFactor_); } catch (PropertyVetoException e) { @@ -2684,7 +2738,7 @@ public ProfileTokenCredential getProfileToken(int tokenType, int timeoutInterval return profileToken; } - + /** * Authenticates the given user profile and password and returns a corresponding ProfileTokenCredential if * successful. @@ -2818,6 +2872,50 @@ public ProfileTokenCredential getProfileToken(String userId, String password, in * @exception InterruptedException If this thread is interrupted. **/ public ProfileTokenCredential getProfileToken(String userId, char[] password, int tokenType, int timeoutInterval) throws AS400SecurityException, IOException, InterruptedException + { + return getProfileToken(userId, password, null, tokenType, timeoutInterval, null, null); + } + + /** + * Authenticates the given user profile and password and returns a corresponding ProfileTokenCredential if + * successful. + *

+ * Invoking this method does not change the user ID and password assigned to the system or otherwise modify the user + * or authorities under which the application is running. + *

+ * This function is only supported if the system is at i5/OS V4R5M0 or greater. + *

+ * Note: Providing an incorrect password increments the number of failed sign-on attempts for the user + * profile, and can result in the profile being disabled. Refer to documentation on the + * ProfileTokenCredential class for additional restrictions. + * + * @param userId The user profile name. + * @param password The user profile password. + * @param additionalAuthFactor The additional authentication factor or null if not specifying one. + * @param tokenType The type of profile token to create. Possible types are defined as fields on the + * ProfileTokenCredential class: + *

    + *
  • {@link com.ibm.as400.security.auth.ProfileTokenCredential#TYPE_SINGLE_USE + * TYPE_SINGLE_USE} + *
  • {@link com.ibm.as400.security.auth.ProfileTokenCredential#TYPE_MULTIPLE_USE_NON_RENEWABLE + * TYPE_MULTIPLE_USE_NON_RENEWABLE} + *
  • {@link com.ibm.as400.security.auth.ProfileTokenCredential#TYPE_MULTIPLE_USE_RENEWABLE + * TYPE_MULTIPLE_USE_RENEWABLE} + *
+ * @param timeoutInterval The number of seconds to expiration when the token is created (1-3600). + * @param verificationID The verification ID that will be associated with profile token. The verification ID + * is the label that identifies the specific application, service, or action associated + * with the profile token request. A null value will result in the host server using a + * default value. + * @param remoteIPAddress The remote IP address (the IP address of the requester) that will be associated with + * profile token. A null value will result in the host server using a default value. + * @return A ProfileTokenCredential representing the authenticated profile and password. + * @exception AS400SecurityException If a security or authority error occurs. + * @exception IOException If an error occurs while communicating with the system. + * @exception InterruptedException If this thread is interrupted. + **/ + public ProfileTokenCredential getProfileToken(String userId, char[] password, char[] additionalAuthFactor, int tokenType, int timeoutInterval, + String verificationID, String remoteIPAddress) throws AS400SecurityException, IOException, InterruptedException { connectService(AS400.SIGNON); @@ -2842,6 +2940,9 @@ public ProfileTokenCredential getProfileToken(String userId, char[] password, in profileToken.setSystem(this); profileToken.setTokenType(tokenType); profileToken.setTimeoutInterval(timeoutInterval); + profileToken.setVerificationID(verificationID); + profileToken.setRemoteIPAddress(remoteIPAddress); + profileToken.setAdditionalAuthenticationFactor(additionalAuthFactor); } catch (PropertyVetoException e) { @@ -3182,6 +3283,22 @@ public int getVersion() throws AS400SecurityException, IOException return version; } + + /** + * Returns the version, release, and modification level a given system. + * + * @param systemName The IP address or hostname of the target system. + * @param useSSL Whether or not secure connections should be used when communicating with the host servers. + * + * @return The high 16-bit is the version, the next 8 bits is the release, and the low 8 bits is the modification + * level. Thus version 5, release 1, modification level 0, returns 0x00050100. + * @exception AS400SecurityException If a security or authority error occurs. + * @exception IOException If an error occurs while communicating with the system. + **/ + public static int getVRM(String systemName, boolean useSSL) throws AS400SecurityException, IOException { + if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Getting vrm for system: " + systemName + ", use SSL:", useSSL); + return AS400ImplRemote.getVRM(systemName, useSSL); + } /** * Returns the version, release, and modification level for the system. @@ -5591,11 +5708,16 @@ private void ensureSecureInstance() /** * Returns true if host server communications is performed over a secure channel. + *

+ * Note:This method is the only reliable way to determine whether + * host server communications is performed over a secure channel. + * An AS400 object that is not an instance of SecureAS400 class can use + * secure communications in some instances. * * @return true if communications is done over secure channel; otherwise false. **/ public boolean isSecure() { - return (this instanceof SecureAS400); + return ((useSSLConnection_ != null) || (this instanceof SecureAS400)); } // ======== START ================= diff --git a/src/main/java/com/ibm/as400/access/AS400GenAuthTknDS.java b/src/main/java/com/ibm/as400/access/AS400GenAuthTknDS.java index bfa782cf3..c5d2b525d 100644 --- a/src/main/java/com/ibm/as400/access/AS400GenAuthTknDS.java +++ b/src/main/java/com/ibm/as400/access/AS400GenAuthTknDS.java @@ -20,12 +20,14 @@ class AS400GenAuthTknDS extends ClientAccessDataStream { AS400GenAuthTknDS(byte[] userIDbytes, byte[] authenticationBytes, int authScheme, int profileTokenType, int profileTokenTimeout, int serverLevel, - byte[] addAuthFactor) + byte[] addAuthFactor, byte[] verificationID, byte[] clientIPAddr) { super(new byte[45 + authenticationBytes.length + ((userIDbytes == null || authScheme == 1|| authScheme ==2) ? 0:16) + (serverLevel < 5 ? 0 : 7) + ((serverLevel >= 18 && null != addAuthFactor && 0 < addAuthFactor.length) ? addAuthFactor.length + 10: 0) + + ((serverLevel >= 18 && null != verificationID) ? verificationID.length + 10: 0) + + ((serverLevel >= 18 && null != clientIPAddr) ? clientIPAddr.length + 10: 0) ]); setLength(data_.length); @@ -124,6 +126,34 @@ else if (authenticationBytes.length == 20) offset += 10 + addAuthFactor.length; } + + if (null != verificationID && 0 < verificationID.length) + { + // LL + set32bit(verificationID.length + 4 + 2 + 4, offset); + // CP + set16bit(0x1130, offset + 4); + // CCSID + set32bit(1208, offset + 6); + // data + System.arraycopy(verificationID, 0, data_, offset + 10, verificationID.length); + + offset += 10 + verificationID.length; + } + + if (null != clientIPAddr && 0 < clientIPAddr.length) + { + // LL + set32bit(clientIPAddr.length + 4 + 2 + 4, offset); + // CP + set16bit(0x1131, offset + 4); + // CCSID + set32bit(1208, offset + 6); + // data + System.arraycopy(clientIPAddr, 0, data_, offset + 10, clientIPAddr.length); + + offset += 10 + clientIPAddr.length; + } } } diff --git a/src/main/java/com/ibm/as400/access/AS400Impl.java b/src/main/java/com/ibm/as400/access/AS400Impl.java index 9f2dc92b1..865057af9 100644 --- a/src/main/java/com/ibm/as400/access/AS400Impl.java +++ b/src/main/java/com/ibm/as400/access/AS400Impl.java @@ -36,11 +36,6 @@ interface AS400Impl void connect(int service, int overridePort, boolean skipSignonServer) throws AS400SecurityException, IOException; /*@V1C*/ // Connect to service. void connect(int service) throws AS400SecurityException, IOException; - // Establish a DHCP connection to the specified port. - Socket connectToPort(int port) throws AS400SecurityException, IOException; - //@N5A Establish a DHCP connection to the specified port. Add this interface for L1C for DHCP already listens on 942 of localhost for STRTCPSVR - Socket connectToPort(int port,boolean forceNonLocalhost) throws AS400SecurityException, IOException; - //int createUserHandle() throws AS400SecurityException, IOException;//@SAA @V4D // Disconnect from service. void disconnect(int service); // Exchange seeds with remote implementation. diff --git a/src/main/java/com/ibm/as400/access/AS400ImplProxy.java b/src/main/java/com/ibm/as400/access/AS400ImplProxy.java index 51f02bca7..ad7f9aa3e 100644 --- a/src/main/java/com/ibm/as400/access/AS400ImplProxy.java +++ b/src/main/java/com/ibm/as400/access/AS400ImplProxy.java @@ -102,36 +102,6 @@ public void connect(int service, int overridePort, boolean skipSignonServer) thr } } - // Connect to port. - @Override - public Socket connectToPort(int port) throws AS400SecurityException, IOException - { - try - { - return (Socket)connection_.callMethod(pxId_, "connectToPort", new Class[] { Integer.TYPE }, new Object[] { Integer.valueOf(port) }).getReturnValue(); - } - catch (InvocationTargetException e) - { - throw ProxyClientConnection.rethrow2(e); - } - } - - @Override - public Socket connectToPort(int port,boolean forceNonLocalhost) throws AS400SecurityException, IOException - { - try - { - return (Socket)connection_.callMethod(pxId_, - "connectToPort", - new Class[] { Integer.TYPE, Boolean.TYPE }, - new Object[] { Integer.valueOf(port), Boolean.valueOf(forceNonLocalhost) }).getReturnValue(); - } - catch (InvocationTargetException e) - { - throw ProxyClientConnection.rethrow2(e); - } - } - // Disconnect from service. @Override public void disconnect(int service) diff --git a/src/main/java/com/ibm/as400/access/AS400ImplRemote.java b/src/main/java/com/ibm/as400/access/AS400ImplRemote.java index 59a9d95a2..c97e75bd6 100644 --- a/src/main/java/com/ibm/as400/access/AS400ImplRemote.java +++ b/src/main/java/com/ibm/as400/access/AS400ImplRemote.java @@ -193,6 +193,7 @@ public class AS400ImplRemote implements AS400Impl AS400Server.addReplyStream(new SignonExchangeAttributeRep(), AS400.SIGNON); AS400Server.addReplyStream(new IFSUserHandleSeedRep(), AS400.FILE); AS400Server.addReplyStream(new IFSCreateUserHandleRep(), AS400.FILE); + AS400Server.addReplyStream(new IFSUserHandle2Rep(), AS400.FILE); AS400Server.addReplyStream(new SignonExchangeAttributeRep(), AS400.HOSTCNN); AS400Server.addReplyStream(new HCSUserInfoReplyDS(), AS400.HOSTCNN); AS400Server.addReplyStream(new HCSGetNewConnReplyDS(), AS400.HOSTCNN); @@ -369,7 +370,6 @@ public SignonInfo changePassword(String systemName, boolean systemNameLocal, } // Get a socket connection. - // TODO AMRA - need to first authenticate to hostcnn boolean needToDisconnect = (signonServer_ == null); signonConnect(); @@ -629,18 +629,6 @@ else if (service == AS400.HOSTCNN) getConnection(service, overridePort, false, skipSignonServer); } - @Override - public Socket connectToPort(int port) throws AS400SecurityException, IOException { - return getConnection(0, port, false); - } - - // @N5A Establish a DHCP connection to the specified port. Add this interface - // for L1C for DHCP already listens on 942 of localhost for STRTCPSVR - @Override - public Socket connectToPort(int port, boolean forceNonLocalhost) throws AS400SecurityException, IOException { - return getConnection(0, port, forceNonLocalhost); - } - // @SAA Create user handle for the connection public int createUserHandle() throws AS400SecurityException, IOException { @@ -752,6 +740,16 @@ else if (ds instanceof IFSReturnCodeRep) } setUserHandle(((IFSCreateUserHandleRep) ds).getHandle()); } + else if (ds instanceof IFSUserHandle2Rep) + { + rc = ((IFSUserHandle2Rep) ds).getReturnCode(); + if (rc != IFSReturnCodeRep.SUCCESS) + { + Trace.log(Trace.ERROR, "IFSUserHandle2Rep return code", rc); + throw new ExtendedIOException(rc); + } + setUserHandle(((IFSUserHandle2Rep) ds).getHandle()); + } else if (ds instanceof IFSReturnCodeRep) { rc = ((IFSReturnCodeRep) ds).getReturnCode(); @@ -869,6 +867,7 @@ public void disconnectServer(AS400Server server) // between the public class and the implRemote class. The transmitted // authentication information can be encoded/decoded using the exchanged // seeds. + @Override public byte[] exchangeSeed(byte[] proxySeed) { // Hold the seed they send us. @@ -940,9 +939,13 @@ public void generateProfileToken(ProfileTokenCredential profileToken, String use throw AS400ImplRemote.returnSecurityException(reply.getRC(), null, userId_); } + // [0]=factor, [1]=verification ID, [2]=remote ip address + Object[] additonalAuthInfo = getAdditionalAuthInfo(profileToken, null); + SignonGenAuthTokenRequestDS req2 = new SignonGenAuthTokenRequestDS( BinaryConverter.charArrayToByteArray(userIdentity.toCharArray()), profileToken.getTokenType(), - profileToken.getTimeoutInterval(), serverLevel_); + profileToken.getTimeoutInterval(), serverLevel_, + (byte[])additonalAuthInfo[1], (byte[])additonalAuthInfo[2]); SignonGenAuthTokenReplyDS rep = (SignonGenAuthTokenReplyDS) signonServer_.sendAndReceive(req2); int rc = rep.getRC(); @@ -957,6 +960,7 @@ public void generateProfileToken(ProfileTokenCredential profileToken, String use } try { profileToken.setToken(rep.getProfileTokenBytes()); + profileToken.setTokenCreator(ProfileTokenCredential.CREATOR_SIGNON_SERVER); } catch (PropertyVetoException e) { Trace.log(Trace.ERROR, e); @@ -986,136 +990,141 @@ public void generateProfileToken(ProfileTokenCredential profileToken, String use switch (authScheme) { - case AS400.AUTHENTICATION_SCHEME_GSS_TOKEN: - try - { - authenticationBytes = (gssCredential_ == null) - ? TokenManager.getGSSToken(systemName_, gssName) - : TokenManager2.getGSSToken(systemName_, gssCredential_); - } - catch (Exception e) - { - Trace.log(Trace.ERROR, "Error retrieving GSSToken:", e); - throw new AS400SecurityException(AS400SecurityException.KERBEROS_TICKET_NOT_VALID_RETRIEVE, e); - } - break; - case AS400.AUTHENTICATION_SCHEME_PROFILE_TOKEN: - case AS400.AUTHENTICATION_SCHEME_IDENTITY_TOKEN: - authenticationBytes = vault.decode(proxySeed_, remoteSeed_); - break; - default: // Password. - byte[] passwordByte = vault.decode(proxySeed_, remoteSeed_); - char[] password = BinaryConverter.byteArrayToCharArray(passwordByte); - CredentialVault.clearArray(passwordByte); - proxySeed_ = null; - remoteSeed_ = null; - - // Generate the correct password based on the password encryption level of the system. - if (passwordLevel_ < 2) - { - // Prepend Q to numeric password. A "numeric password" is a password that starts with a numeric digit. - if (password.length > 0 && Character.isDigit(password[0])) + case AS400.AUTHENTICATION_SCHEME_GSS_TOKEN: + try { - if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Prepending Q to numeric password."); - - char[] passwordWithQ = new char[password.length + 1]; - passwordWithQ[0] = 'Q'; - System.arraycopy(password, 0, passwordWithQ, 1, password.length); - CredentialVault.clearArray(password); - password = passwordWithQ; + authenticationBytes = (gssCredential_ == null) + ? TokenManager.getGSSToken(systemName_, gssName) + : TokenManager2.getGSSToken(systemName_, gssCredential_); } - - if (password.length > 10) + catch (Exception e) { - Trace.log(Trace.ERROR, "Length of parameter 'password' is not valid:", password.length); - throw new AS400SecurityException(AS400SecurityException.PASSWORD_LENGTH_NOT_VALID); + Trace.log(Trace.ERROR, "Error retrieving GSSToken:", e); + throw new AS400SecurityException(AS400SecurityException.KERBEROS_TICKET_NOT_VALID_RETRIEVE, e); } - authenticationBytes = encryptPassword(userIdEbcdic, - SignonConverter.upperCharsToByteArray(password), clientSeed_, serverSeed_); - CredentialVault.clearArray(password); - } - else if (passwordLevel_ < 4) - { - // Do SHA-1 encryption. - byte[] userIdBytes = BinaryConverter.charArrayToByteArray(SignonConverter.byteArrayToCharArray(userIdEbcdic)); - - // Screen out passwords that start with a star. - if (password.length == 0) { - Trace.log(Trace.ERROR, "Parameter 'password' is empty."); - throw new AS400SecurityException(AS400SecurityException.SIGNON_CHAR_NOT_VALID); - } - - if (password[0] == '*') { - Trace.log(Trace.ERROR, "Parameter 'password' begins with a '*' character."); - throw new AS400SecurityException( AS400SecurityException.SIGNON_CHAR_NOT_VALID); - } - - char[] trimmedPassword = trimUnicodeSpace(password); - byte[] passwordBytes = BinaryConverter.charArrayToByteArray(trimmedPassword); - CredentialVault.clearArray(trimmedPassword); - CredentialVault.clearArray(password); - byte[] sequence = { 0, 0, 0, 0, 0, 0, 0, 1 }; - - if (PASSWORD_TRACE) { - Trace.log(Trace.DIAGNOSTIC, "Pre SHA-1 userIdBytes:", userIdBytes); - Trace.log(Trace.DIAGNOSTIC, "Pre SHA-1 passwordBytes:", passwordBytes); - Trace.log(Trace.DIAGNOSTIC, "Pre SHA-1 sequence:", sequence); + break; + case AS400.AUTHENTICATION_SCHEME_PROFILE_TOKEN: + case AS400.AUTHENTICATION_SCHEME_IDENTITY_TOKEN: + authenticationBytes = vault.decode(proxySeed_, remoteSeed_); + break; + default: // Password. + byte[] passwordByte = vault.decode(proxySeed_, remoteSeed_); + char[] password = BinaryConverter.byteArrayToCharArray(passwordByte); + CredentialVault.clearArray(passwordByte); + proxySeed_ = null; + remoteSeed_ = null; + + // Generate the correct password based on the password encryption level of the system. + if (passwordLevel_ < 2) + { + // Prepend Q to numeric password. A "numeric password" is a password that starts with a numeric digit. + if (password.length > 0 && Character.isDigit(password[0])) + { + if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Prepending Q to numeric password."); + + char[] passwordWithQ = new char[password.length + 1]; + passwordWithQ[0] = 'Q'; + System.arraycopy(password, 0, passwordWithQ, 1, password.length); + CredentialVault.clearArray(password); + password = passwordWithQ; + } + + if (password.length > 10) + { + Trace.log(Trace.ERROR, "Length of parameter 'password' is not valid:", password.length); + throw new AS400SecurityException(AS400SecurityException.PASSWORD_LENGTH_NOT_VALID); + } + authenticationBytes = encryptPassword(userIdEbcdic, + SignonConverter.upperCharsToByteArray(password), clientSeed_, serverSeed_); + CredentialVault.clearArray(password); } - - byte[] token = generateShaToken(userIdBytes, passwordBytes); - CredentialVault.clearArray(passwordBytes); - - authenticationBytes = generateShaSubstitute(token, serverSeed_, clientSeed_, userIdBytes, sequence); - } - else - { - if (password.length == 0) { - Trace.log(Trace.ERROR, "Parameter 'password' is empty."); - throw new AS400SecurityException(AS400SecurityException.SIGNON_CHAR_NOT_VALID); + else if (passwordLevel_ < 4) + { + // Do SHA-1 encryption. + byte[] userIdBytes = BinaryConverter.charArrayToByteArray(SignonConverter.byteArrayToCharArray(userIdEbcdic)); + + // Screen out passwords that start with a star. + if (password.length == 0) { + Trace.log(Trace.ERROR, "Parameter 'password' is empty."); + throw new AS400SecurityException(AS400SecurityException.SIGNON_CHAR_NOT_VALID); + } + + if (password[0] == '*') { + Trace.log(Trace.ERROR, "Parameter 'password' begins with a '*' character."); + throw new AS400SecurityException( AS400SecurityException.SIGNON_CHAR_NOT_VALID); + } + + char[] trimmedPassword = trimUnicodeSpace(password); + byte[] passwordBytes = BinaryConverter.charArrayToByteArray(trimmedPassword); + CredentialVault.clearArray(trimmedPassword); + CredentialVault.clearArray(password); + byte[] sequence = { 0, 0, 0, 0, 0, 0, 0, 1 }; + + if (PASSWORD_TRACE) { + Trace.log(Trace.DIAGNOSTIC, "Pre SHA-1 userIdBytes:", userIdBytes); + Trace.log(Trace.DIAGNOSTIC, "Pre SHA-1 passwordBytes:", passwordBytes); + Trace.log(Trace.DIAGNOSTIC, "Pre SHA-1 sequence:", sequence); + } + + byte[] token = generateShaToken(userIdBytes, passwordBytes); + CredentialVault.clearArray(passwordBytes); + + authenticationBytes = generateShaSubstitute(token, serverSeed_, clientSeed_, userIdBytes, sequence); } - - // Screen out passwords that start with a star. - if (password[0] == '*') { - Trace.log(Trace.ERROR, "Parameter 'password' begins with a '*' character."); - throw new AS400SecurityException(AS400SecurityException.SIGNON_CHAR_NOT_VALID); + else + { + if (password.length == 0) { + Trace.log(Trace.ERROR, "Parameter 'password' is empty."); + throw new AS400SecurityException(AS400SecurityException.SIGNON_CHAR_NOT_VALID); + } + + // Screen out passwords that start with a star. + if (password[0] == '*') { + Trace.log(Trace.ERROR, "Parameter 'password' begins with a '*' character."); + throw new AS400SecurityException(AS400SecurityException.SIGNON_CHAR_NOT_VALID); + } + + /* + * If a sequence number is used, the client increments its password sequence "PWSEQs" by + * one and saves it. PWSEQs is an 8-byte value. The implementation in the host servers always + * uses a sequence number of 1. + */ + byte[] sequence = { 0, 0, 0, 0, 0, 0, 0, 1 }; + //Generate salt for password level 4 + /* + * The following steps describe the algorithm used to generate the pwdlvl 4 version of the password: + * 1. Convert the 10-character blank padded user ID to upper case. + * 2. Convert the 10-character blank padded upper case user ID to Unicode (CCSID 13488). + * 3. Convert the password value to Unicode (CCSID 13488). + * 4. Generate the salt value: + * a. Fill a 28-byte variable with Unicode blanks (0x0020). + * b. Copy the Unicode user ID value into the first 20 bytes of the 28-byte blank filled variable. + * c. Copy the last 8 bytes (last 4 characters) of the Unicode password value into the last 8 bytes of the 28-byte variable. If the password is less than 4 characters, then copy the entire Unicode password value. + * d. Do a SHA-256 hash on the 28-byte variable to produce the 32-byte salt value. + * 5. Generate the pwdlvl 4 version of the password using PBKDF2 with HMAC SHA-512 with the following values: + * Hash algorithm = HMAC SHA-512 (produces a 64-byte key) + * Data = Unicode password value + * Data Length = Length of Unicode password value + * Iterations = 10022 + * Initialization vector length = 32 + * Initialization vector (salt) = value generated in Step #4. + */ + byte[] token = generatePwdTokenForPasswordLevel4(userId, password); + CredentialVault.clearArray(password); + authenticationBytes = generateSha512Substitute(userId, token, serverSeed_, clientSeed_, sequence); } - - /* - * If a sequence number is used, the client increments its password sequence "PWSEQs" by - * one and saves it. PWSEQs is an 8-byte value. The implementation in the host servers always - * uses a sequence number of 1. - */ - byte[] sequence = { 0, 0, 0, 0, 0, 0, 0, 1 }; - //Generate salt for password level 4 - /* - * The following steps describe the algorithm used to generate the pwdlvl 4 version of the password: - * 1. Convert the 10-character blank padded user ID to upper case. - * 2. Convert the 10-character blank padded upper case user ID to Unicode (CCSID 13488). - * 3. Convert the password value to Unicode (CCSID 13488). - * 4. Generate the salt value: - * a. Fill a 28-byte variable with Unicode blanks (0x0020). - * b. Copy the Unicode user ID value into the first 20 bytes of the 28-byte blank filled variable. - * c. Copy the last 8 bytes (last 4 characters) of the Unicode password value into the last 8 bytes of the 28-byte variable. If the password is less than 4 characters, then copy the entire Unicode password value. - * d. Do a SHA-256 hash on the 28-byte variable to produce the 32-byte salt value. - * 5. Generate the pwdlvl 4 version of the password using PBKDF2 with HMAC SHA-512 with the following values: - * Hash algorithm = HMAC SHA-512 (produces a 64-byte key) - * Data = Unicode password value - * Data Length = Length of Unicode password value - * Iterations = 10022 - * Initialization vector length = 32 - * Initialization vector (salt) = value generated in Step #4. - */ - byte[] token = generatePwdTokenForPasswordLevel4(userId_, password); - CredentialVault.clearArray(password); - authenticationBytes = generateSha512Substitute(userId_, token, serverSeed_, clientSeed_, sequence); - } } - byte[] aaf = aafIndicator_ ? additionalAuthFactor_ : null; + if (Trace.isTraceOn()) Trace.log(Trace.DIAGNOSTIC, "AS400ImplRemote generating profile token for user: " + userId); + + + // [0]=factor, [1]=verification ID, [2]=remote ip address + Object[] additonalAuthInfo = getAdditionalAuthInfo(profileToken, null); AS400GenAuthTknDS req = new AS400GenAuthTknDS(userIdEbcdic, authenticationBytes, authScheme, profileToken.getTokenType(), - profileToken.getTimeoutInterval(), serverLevel_, aaf); + profileToken.getTimeoutInterval(), serverLevel_, + (byte[])additonalAuthInfo[0], (byte[])additonalAuthInfo[1], (byte[])additonalAuthInfo[2]); CredentialVault.clearArray(authenticationBytes); AS400GenAuthTknReplyDS rep = (AS400GenAuthTknReplyDS) signonServer_.sendAndReceive(req); @@ -1133,6 +1142,7 @@ else if (passwordLevel_ < 4) try { profileToken.setToken(rep.getProfileTokenBytes()); + profileToken.setTokenCreator(ProfileTokenCredential.CREATOR_SIGNON_SERVER); } catch (PropertyVetoException e) { Trace.log(Trace.ERROR, e); throw new InternalErrorException(InternalErrorException.UNEXPECTED_EXCEPTION, e); @@ -1147,10 +1157,21 @@ else if (passwordLevel_ < 4) } } - public static boolean getAdditionalAuthenticationIndicator(String systemName, boolean useSSL) throws AS400SecurityException, IOException - { - AS400ImplRemote implRemote = new AS400ImplRemote(); + + private static class SystemInformation { + ServerVersion version; + int serverLevel; + int passwordLevel; + boolean aafIndicator; + } + + private static SystemInformation getSystemInformation(String systemName, boolean useSSL) throws AS400SecurityException, IOException + { + if (systemName == null) + throw new NullPointerException("systemName"); + AS400ImplRemote implRemote = new AS400ImplRemote(); + try { implRemote.systemName_ = systemName; @@ -1160,7 +1181,14 @@ public static boolean getAdditionalAuthenticationIndicator(String systemName, bo if (implRemote.hostcnnServer_ == null) implRemote.signonConnect(); - return implRemote.aafIndicator_; + + SystemInformation si = new SystemInformation(); + si.version = implRemote.version_; + si.serverLevel = implRemote.serverLevel_; + si.passwordLevel = implRemote.passwordLevel_; + si.aafIndicator = implRemote.aafIndicator_ ; + + return si; } finally { if (implRemote.hostcnnServer_ != null) @@ -1169,6 +1197,14 @@ public static boolean getAdditionalAuthenticationIndicator(String systemName, bo implRemote.signonDisconnect(); } } + + public static boolean getAdditionalAuthenticationIndicator(String systemName, boolean useSSL) throws AS400SecurityException, IOException { + return (getSystemInformation(systemName, useSSL)).aafIndicator; + } + + public static int getVRM(String systemName, boolean useSSL) throws AS400SecurityException, IOException { + return (getSystemInformation(systemName, useSSL)).version.getVersionReleaseModification(); + } // Get either the user's CCSID, the signon server CCSID, or our best guess. public int getCcsid() @@ -1312,90 +1348,6 @@ private String readFTPLine(BufferedReader reader) throws IOException return fullMessage.toString(); } - // Note: The 'dhcp' argument is a dummy argument, whose sole purpose is to - // differentiate this method from getConnection(int port). The value of 'dhcp' is ignored. - // @N5A Add this interface for L1C for DHCP already listens on 942 of localhost for STRTCPSVR - synchronized Socket getConnection(int dhcp, int port, boolean forceNonLocalhost) throws AS400SecurityException, IOException - { - if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Establishing connection to system at port:", port); - - Socket socket = new Socket((systemNameLocal_ && !forceNonLocalhost) ? "localhost" : systemName_, port); - int connectionID = socket.hashCode(); - - try - { - PortMapper.setSocketProperties(socket, socketProperties_); - InputStream inStream = socket.getInputStream(); - OutputStream outStream = socket.getOutputStream(); - - // The first request we send is "exchange random seeds"... - int serverId = AS400Server.getServerId(AS400.COMMAND); - AS400XChgRandSeedDS xChgReq = new AS400XChgRandSeedDS(serverId); - if (Trace.traceOn_) xChgReq.setConnectionID(connectionID); - xChgReq.write(outStream); - - AS400XChgRandSeedReplyDS xChgReply = new AS400XChgRandSeedReplyDS(); - if (Trace.traceOn_) xChgReply.setConnectionID(connectionID); - xChgReply.read(inStream); - - if (xChgReply.getRC() != 0) - { - byte[] rcBytes = new byte[4]; - BinaryConverter.intToByteArray(xChgReply.getRC(), rcBytes, 0); - Trace.log(Trace.ERROR, "Exchange of random seeds failed with return code:", rcBytes); - throw AS400ImplRemote.returnSecurityException(xChgReply.getRC(), null, null); - } - - if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Exchange of random seeds successful."); - - // Next we send the "start server job" request... - byte[] clientSeed = xChgReq.getClientSeed(); - byte[] serverSeed = xChgReply.getServerSeed(); - byte[] userIDbytes = SignonConverter.stringToByteArray(userId_); - byte[] encryptedPassword = getPassword(clientSeed, serverSeed); - - if (PASSWORD_TRACE) { - Trace.log(Trace.DIAGNOSTIC, "Sending Start Server Request..."); - Trace.log(Trace.DIAGNOSTIC, " User ID:", userId_); - Trace.log(Trace.DIAGNOSTIC, " User ID bytes:", userIDbytes); - Trace.log(Trace.DIAGNOSTIC, " Client seed:", clientSeed); - Trace.log(Trace.DIAGNOSTIC, " Server seed:", serverSeed); - Trace.log(Trace.DIAGNOSTIC, " Encrypted password:", encryptedPassword); - } - - AS400StrSvrDS req = new AS400StrSvrDS(serverId, userIDbytes, encryptedPassword, credVault_.getType()); - if (Trace.traceOn_) - req.setConnectionID(connectionID); - req.write(outStream); - - AS400StrSvrReplyDS reply = new AS400StrSvrReplyDS(); - if (Trace.traceOn_) - reply.setConnectionID(connectionID); - reply.read(inStream); - - if (reply.getRC() != 0) { - byte[] rcBytes = new byte[4]; - BinaryConverter.intToByteArray(reply.getRC(), rcBytes, 0); - Trace.log(Trace.ERROR, "Start server failed with return code:", rcBytes); - throw AS400ImplRemote.returnSecurityException(reply.getRC(), null, userId_); - } - - if (Trace.traceOn_) - Trace.log(Trace.DIAGNOSTIC, "Server started successfully."); - return socket; - } - catch (IOException | AS400SecurityException e) - { - Trace.log(Trace.ERROR, "Establishing DHCP connection failed:", e); - try { - socket.close(); - } catch (IOException ee) { - Trace.log(Trace.ERROR, "Error closing socket:", ee); - } - throw e; - } - } - // Gets the jobs with which we are connected. @Override public String[] getJobs(int service) @@ -1501,13 +1453,12 @@ synchronized AS400Server getConnection(int service, int overridePort, boolean fo String jobString = ""; InputStream inStream = null; OutputStream outStream = null; - boolean usingAuthenticatedHostcnnConnection = false; boolean haveHostcnnConnection = (hostcnnServer_ != null); - try + // DDM (AS400.RECORDACCESS) does not fall under the HOSTCNN umbrella, it is a separate server. + if (!haveHostcnnConnection || service == AS400.RECORDACCESS) { - // DDM (AS400.RECORDACCESS) does not fall under the HOSTCNN umbrella, it is a separate server. - if (!haveHostcnnConnection || service == AS400.RECORDACCESS) + try { if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "The service as-hostcnn is not available to use or service is DDM"); @@ -1578,10 +1529,12 @@ synchronized AS400Server getConnection(int service, int overridePort, boolean fo iaspBytes = text18.toBytes(ddmRDB_); } - byte[] aaf = aafIndicator_ ? additionalAuthFactor_ : null; + // [0]=factor, [1]=verification ID, [2]=remote ip address + Object[] additonalAuthInfo = getAdditionalAuthInfo(null, aafIndicator_); ClassDecoupler.connectDDMPhase2(outStream, inStream, userIDbytes, ddmSubstitutePassword, iaspBytes, - authScheme, ddmRDB_, systemName_, connectionID, aaf); + authScheme, ddmRDB_, systemName_, connectionID, + (byte[])additonalAuthInfo[0], (byte[])additonalAuthInfo[1], (byte[])additonalAuthInfo[2]); } else { @@ -1634,9 +1587,11 @@ synchronized AS400Server getConnection(int service, int overridePort, boolean fo Trace.log(Trace.DIAGNOSTIC, " Password level: ", passwordLevel_); } - // Do not pass authentication factor if server does not support it. Otherwise, you will get an error. + // [0]=factor, [1]=verification ID, [2]=remote ip address + Object[] additonalAuthInfo = getAdditionalAuthInfo(null, xChgReply.getAAFIndicator()); + AS400StrSvrDS req = new AS400StrSvrDS(serverId, userIDbytes, encryptedPassword, credVault_.getType(), - (xChgReply.getAAFIndicator()) ? additionalAuthFactor_ : null); + (byte[])additonalAuthInfo[0], (byte[])additonalAuthInfo[1], (byte[])additonalAuthInfo[2]); if (Trace.traceOn_) req.setConnectionID(connectionID); req.write(outStream); @@ -1656,129 +1611,32 @@ synchronized AS400Server getConnection(int service, int overridePort, boolean fo jobString = obtainJobIdForConnection(reply.getJobNameBytes()); } } - else - { - if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Attempting to create connection to " + AS400.getServerName(service) + " via as-hostcnn"); - - // ------- - // Prepare new connection request with server type - // ------- - - int requestedServerID = AS400Server.getServerId(service); - - HCSPrepareNewConnDS HCSPrepDS = new HCSPrepareNewConnDS(requestedServerID); - if (Trace.traceOn_) HCSPrepDS.setConnectionID(hostcnnServer_.getConnectionID()); - - usingAuthenticatedHostcnnConnection = true; - HCSPrepareNewConnReplyDS HCSPrepReply = (HCSPrepareNewConnReplyDS) hostcnnServer_.sendAndReceive(HCSPrepDS); - usingAuthenticatedHostcnnConnection = false; - - if (HCSPrepReply.getRC() != 0) + catch (IOException | AS400SecurityException | RuntimeException e) + { + try { - byte[] rcBytes = new byte[4]; - BinaryConverter.intToByteArray(HCSPrepReply.getRC(), rcBytes, 0); - Trace.log(Trace.ERROR, "Route prepare connection failed with return code:", rcBytes); - throw AS400ImplRemote.returnSecurityException(HCSPrepReply.getRC(), null, userId_); + // If we have host server connection, close it as well. + if (socketContainer != null) + socketContainer.close(); + } + catch (Throwable ee) { + Trace.log(Trace.ERROR, "Error closing socket:", ee); } - byte[] connectionReqID = HCSPrepReply.getConnReqID(); - - // ------- - // Connect to HCS using new socket - // ------- - - if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Connect to as-hostcnn using new socket"); - - socketContainer = PortMapper.getServerSocket((systemNameLocal_) - ? "localhost" - : systemName_, AS400.HOSTCNN, overridePort, useSSLConnection_, socketProperties_, mustUseNetSockets_); - - connectionID = socketContainer.hashCode(); - inStream = socketContainer.getInputStream(); - outStream = socketContainer.getOutputStream(); - - // ------- - // Give new connection request with new connection request ID - // ------- - - if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Give new connection request with new connection request ID"); - - HCSGetNewConnDS HCSGetDS = new HCSGetNewConnDS(connectionReqID); - if (Trace.traceOn_) HCSGetDS.setConnectionID(connectionID); - HCSGetDS.write(outStream); - - HCSGetNewConnReplyDS HCSGetReply = new HCSGetNewConnReplyDS(); - if (Trace.traceOn_) HCSGetReply.setConnectionID(connectionID); - HCSGetReply.read(inStream); - - if (HCSGetReply.getRC() != 0) - { - byte[] rcBytes = new byte[4]; - BinaryConverter.intToByteArray(HCSGetReply.getRC(), rcBytes, 0); - Trace.log(Trace.ERROR, "Get new connection failed with return code:", rcBytes); - throw AS400ImplRemote.returnSecurityException(HCSGetReply.getRC(), null, userId_); - } - - // ------- - // Route new connection request with connection request ID - // ------- - - if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Route new connection request with connection request ID"); - HCSRouteNewConnDS HCSRouteDS = new HCSRouteNewConnDS(connectionReqID); - if (Trace.traceOn_) HCSRouteDS.setConnectionID(connectionID); - - usingAuthenticatedHostcnnConnection = true; - HCSRouteNewConnReplyDS HCSRouteReply = (HCSRouteNewConnReplyDS) hostcnnServer_.sendAndReceive(HCSRouteDS); - usingAuthenticatedHostcnnConnection = false; - - if (HCSRouteReply.getRC() != 0) - { - byte[] rcBytes = new byte[4]; - BinaryConverter.intToByteArray(HCSRouteReply.getRC(), rcBytes, 0); - Trace.log(Trace.ERROR, "Route new connection failed with return code:", rcBytes); - throw AS400ImplRemote.returnSecurityException(HCSRouteReply.getRC(), null, userId_); - } - - jobString = obtainJobIdForConnection(HCSRouteReply.getJobNameBytes()); + throw e; } - } - catch (IOException | AS400SecurityException | RuntimeException e) - { - try - { - // If error happened when communicating with hostcnn, close the socket. - if (usingAuthenticatedHostcnnConnection) - { - hostcnnServer_.forceDisconnect(); - hostcnnServer_ = null; - } - - // If we have host server connection, close it as well. - if (socketContainer != null) - socketContainer.close(); - } - catch (Throwable ee) { - Trace.log(Trace.ERROR, "Error closing socket:", ee); - } - - // If we used hostcnn server, then we cannot establish host server connection over an hostcnn connection. - if (haveHostcnnConnection && !(e instanceof AS400SecurityException)) - { - if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Exception during communication with as-hostcnn server"); - throw new ServerStartupException( ServerStartupException.CONNECTION_NOT_ESTABLISHED, e); - } - - throw e; - } - - if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Server started successfully. Job: " + jobString); + + // At this point the Socket connection is established. Now we need to set up + // the AS400Server object before passing it back to the caller. - // At this point the Socket connection is established. Now we need to set up - // the AS400Server object before passing it back to the caller. + // Construct a new server... + server = (threadUsed_) ? new AS400ThreadedServer(this, service, socketContainer, jobString) + : new AS400NoThreadServer(this, service, socketContainer, jobString); + } + else + server = getConnectionViaHOSTCNN(service, overridePort, forceNewConnection, skipSignonServer); - // Construct a new server... - server = (threadUsed_) ? new AS400ThreadedServer(this, service, socketContainer, jobString) - : new AS400NoThreadServer(this, service, socketContainer, jobString); + if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Server started successfully. Job: " + server.jobString_); // Add the system to our list so we can return it on a subsequent connect()... serverList.addElement(server); @@ -1787,6 +1645,135 @@ synchronized AS400Server getConnection(int service, int overridePort, boolean fo return server; } + + private AS400Server getConnectionViaHOSTCNN(int service, int overridePort, boolean forceNewConnection, boolean skipSignonServer) throws AS400SecurityException, IOException + { + if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Attempting to create connection to " + AS400.getServerName(service) + " via as-hostcnn"); + + SocketContainer socketContainer = null; + int connectionID; + String jobString = ""; + InputStream inStream = null; + OutputStream outStream = null; + boolean usingAuthenticatedHostcnnConnection = false; + + try + { + // ------- + // Prepare new connection request with server type + // ------- + + int requestedServerID = AS400Server.getServerId(service); + + HCSPrepareNewConnDS HCSPrepDS = new HCSPrepareNewConnDS(requestedServerID); + if (Trace.traceOn_) HCSPrepDS.setConnectionID(hostcnnServer_.getConnectionID()); + + usingAuthenticatedHostcnnConnection = true; + HCSPrepareNewConnReplyDS HCSPrepReply = (HCSPrepareNewConnReplyDS) hostcnnServer_.sendAndReceive(HCSPrepDS); + usingAuthenticatedHostcnnConnection = false; + + if (HCSPrepReply.getRC() != 0) + { + byte[] rcBytes = new byte[4]; + BinaryConverter.intToByteArray(HCSPrepReply.getRC(), rcBytes, 0); + Trace.log(Trace.ERROR, "Route prepare connection failed with return code:", rcBytes); + throw AS400ImplRemote.returnSecurityException(HCSPrepReply.getRC(), null, userId_); + } + + byte[] connectionReqID = HCSPrepReply.getConnReqID(); + + // ------- + // Connect to HCS using new socket + // ------- + + if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Connect to as-hostcnn using new socket"); + + // Note that you cannot override hostcnn port since users cannot connect to it directly! + socketContainer = PortMapper.getServerSocket((systemNameLocal_) ? "localhost" : systemName_, + AS400.HOSTCNN, -1, useSSLConnection_, socketProperties_, mustUseNetSockets_); + + connectionID = socketContainer.hashCode(); + inStream = socketContainer.getInputStream(); + outStream = socketContainer.getOutputStream(); + + // ------- + // Give new connection request with new connection request ID + // ------- + + if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Give new connection request with new connection request ID"); + + HCSGetNewConnDS HCSGetDS = new HCSGetNewConnDS(connectionReqID); + if (Trace.traceOn_) HCSGetDS.setConnectionID(connectionID); + HCSGetDS.write(outStream); + + HCSGetNewConnReplyDS HCSGetReply = new HCSGetNewConnReplyDS(); + if (Trace.traceOn_) HCSGetReply.setConnectionID(connectionID); + HCSGetReply.read(inStream); + + if (HCSGetReply.getRC() != 0) + { + byte[] rcBytes = new byte[4]; + BinaryConverter.intToByteArray(HCSGetReply.getRC(), rcBytes, 0); + Trace.log(Trace.ERROR, "Get new connection failed with return code:", rcBytes); + throw AS400ImplRemote.returnSecurityException(HCSGetReply.getRC(), null, userId_); + } + + // ------- + // Route new connection request with connection request ID + // ------- + + if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Route new connection request with connection request ID"); + HCSRouteNewConnDS HCSRouteDS = new HCSRouteNewConnDS(connectionReqID); + if (Trace.traceOn_) HCSRouteDS.setConnectionID(connectionID); + + usingAuthenticatedHostcnnConnection = true; + HCSRouteNewConnReplyDS HCSRouteReply = (HCSRouteNewConnReplyDS) hostcnnServer_.sendAndReceive(HCSRouteDS); + usingAuthenticatedHostcnnConnection = false; + + if (HCSRouteReply.getRC() != 0) + { + byte[] rcBytes = new byte[4]; + BinaryConverter.intToByteArray(HCSRouteReply.getRC(), rcBytes, 0); + Trace.log(Trace.ERROR, "Route new connection failed with return code:", rcBytes); + throw AS400ImplRemote.returnSecurityException(HCSRouteReply.getRC(), null, userId_); + } + + jobString = obtainJobIdForConnection(HCSRouteReply.getJobNameBytes()); + + // Construct a new server... + return (threadUsed_) ? new AS400ThreadedServer(this, service, socketContainer, jobString) + : new AS400NoThreadServer(this, service, socketContainer, jobString); + } + catch (IOException | AS400SecurityException | RuntimeException e) + { + try + { + // If error happened when communicating with hostcnn, close the socket. + if (usingAuthenticatedHostcnnConnection) + { + hostcnnServer_.forceDisconnect(); + hostcnnServer_ = null; + } + + // If we have host server connection, close it as well. + if (socketContainer != null) + socketContainer.close(); + } + catch (Throwable ee) { + Trace.log(Trace.ERROR, "Error closing socket:", ee); + } + + // If we used hostcnn server, then we cannot establish host server connection over an hostcnn connection. + if (!(e instanceof AS400SecurityException)) + { + if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Exception during communication with as-hostcnn server"); + throw new ServerStartupException( ServerStartupException.CONNECTION_NOT_ESTABLISHED, e); + } + + throw e; + } + } + // The NLV to send to the system. String getNLV() @@ -3300,7 +3287,7 @@ public SignonInfo setState(AS400Impl impl, CredentialVault credVault) // TODO AMRA - need to verify what can be copied. - credVault_ = credVault; + credVault_ = credVault.clone(); systemName_ = parentImpl.systemName_; userId_ = parentImpl.userId_; systemNameLocal_ = parentImpl.systemNameLocal_; @@ -3356,18 +3343,18 @@ else if (byteType == AS400.AUTHENTICATION_SCHEME_GSS_TOKEN) byte[] newBytes = CredentialVault.decode(proxySeed_, remoteSeed_, bytes); switch (byteType) { - case AS400.AUTHENTICATION_SCHEME_PASSWORD: - tempVault = new PasswordVault(newBytes); - break; - case AS400.AUTHENTICATION_SCHEME_PROFILE_TOKEN: - tempVault = new ProfileTokenVault(newBytes); - break; - case AS400.AUTHENTICATION_SCHEME_IDENTITY_TOKEN: - tempVault = new IdentityTokenVault(newBytes); - break; - default: - Trace.log(Trace.ERROR, "Unsupported byte type: " + byteType); - throw new InternalErrorException(InternalErrorException.UNKNOWN, byteType); + case AS400.AUTHENTICATION_SCHEME_PASSWORD: + tempVault = new PasswordVault(newBytes); + break; + case AS400.AUTHENTICATION_SCHEME_PROFILE_TOKEN: + tempVault = new ProfileTokenVault(newBytes); + break; + case AS400.AUTHENTICATION_SCHEME_IDENTITY_TOKEN: + tempVault = new IdentityTokenVault(newBytes); + break; + default: + Trace.log(Trace.ERROR, "Unsupported byte type: " + byteType); + throw new InternalErrorException(InternalErrorException.UNKNOWN, byteType); } CredentialVault.clearArray(newBytes); @@ -3398,10 +3385,8 @@ public SignonInfo signon(String systemName, boolean systemNameLocal, String use // Exchange sign-on flows with sign-on server. @Override - public SignonInfo signon(String systemName, boolean systemNameLocal, - String userId, CredentialVault vault, String gssName, - char[] additionalAuthFactor) - throws AS400SecurityException, IOException + public SignonInfo signon(String systemName, boolean systemNameLocal, String userId, CredentialVault vault, String gssName, + char[] additionalAuthFactor) throws AS400SecurityException, IOException { // If userid, or system has changed, we need to disconnect any connection to HOSTCNN and SIGNON. if (hostcnnServer_ != null && @@ -3519,7 +3504,6 @@ public SignonInfo signon(String systemName, boolean systemNameLocal, if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Retrieve Signon Information Request successful."); - // TODO AMRA can/should we share signoninfo object? profileToken? signonInfo_ = new SignonInfo(); signonInfo_.currentSignonDate = signonRep.getCurrentSignonDate(); signonInfo_.lastSignonDate = signonRep.getLastSignonDate(); @@ -3731,8 +3715,8 @@ public SignonInfo skipSignon(String systemName, boolean systemNameLocal, return signonInfo_; } - // The hostcnn connection takes over for signon server when it comes to authentication and - // getting user attributes. Of course, it also controls the establishing of host server job + // The hostcnn connection takes over for signon server when it comes to authentication. + // Of course, it also controls the establishing of host server job // connections under the auspices of the initial use of the additional authentication factor. // And it never goes away unless there is a request to disconnect or the connection has been severed. synchronized private void hostcnnConnect(boolean authenticate) throws AS400SecurityException, IOException @@ -3772,9 +3756,8 @@ synchronized private void hostcnnConnect(boolean authenticate) throws AS400Secur if (!reconnecting || (reconnecting && !isConnectionAlive(AS400.HOSTCNN))) { // If going to releases that do not support MFA, portmapper will throw an exception, need to handle. - socketContainer = PortMapper.getServerSocket( (systemNameLocal_) - ? "localhost" - : systemName_, AS400.HOSTCNN, -1, useSSLConnection_, socketProperties_, mustUseNetSockets_); + socketContainer = PortMapper.getServerSocket( (systemNameLocal_) ? "localhost" : systemName_, + AS400.HOSTCNN, -1, useSSLConnection_, socketProperties_, mustUseNetSockets_); hostcnnServer.setSocket(socketContainer); connectionID = hostcnnServer.getConnectionID(); @@ -3807,7 +3790,11 @@ synchronized private void hostcnnConnect(boolean authenticate) throws AS400Secur Trace.log(Trace.ERROR, "Server exchange client/server attributes failed, return code:", rcBytes); throw AS400ImplRemote.returnSecurityException(attrRep.getRC(), null, userId_); } - + + // ------- + // Bookkeeping... + // ------- + version_ = new ServerVersion(attrRep.getServerVersion()); serverLevel_ = attrRep.getServerLevel(); passwordLevel_ = attrRep.getPasswordLevel(); @@ -3841,8 +3828,11 @@ synchronized private void hostcnnConnect(boolean authenticate) throws AS400Secur if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Send start server job request for service as-hostcnn"); - byte[] userIDbytes = SignonConverter.stringToByteArray(userId_); - byte[] encryptedPassword = getPassword(hostcnn_clientSeed_, hostcnn_serverSeed_); + byte[] userIDbytes = (credVault_.getType() == AS400.AUTHENTICATION_SCHEME_PASSWORD) + ? SignonConverter.stringToByteArray(userId_) : null; + byte[] encryptedPassword = (credVault_.getType() == AS400.AUTHENTICATION_SCHEME_GSS_TOKEN) + ? credVault_.getClearCredential() : getPassword(hostcnn_clientSeed_, hostcnn_serverSeed_); + if (PASSWORD_TRACE) { Trace.log(Trace.DIAGNOSTIC, "Sending Start Server Request..."); @@ -3854,7 +3844,11 @@ synchronized private void hostcnnConnect(boolean authenticate) throws AS400Secur Trace.log(Trace.DIAGNOSTIC, " Password level: ", passwordLevel_); } - AS400StrSvrDS req = new AS400StrSvrDS(serverId, userIDbytes, encryptedPassword, credVault_.getType(), additionalAuthFactor_); + // [0]=factor, [1]=verification ID, [2]=remote ip address + Object[] additonalAuthInfo = getAdditionalAuthInfo(null, aafIndicator_); + + AS400StrSvrDS req = new AS400StrSvrDS(serverId, userIDbytes, encryptedPassword, credVault_.getType(), + (byte[])additonalAuthInfo[0], (byte[])additonalAuthInfo[1], (byte[])additonalAuthInfo[2]); if (Trace.traceOn_) req.setConnectionID(connectionID); req.write(outStream); @@ -3872,6 +3866,14 @@ synchronized private void hostcnnConnect(boolean authenticate) throws AS400Secur } hostcnnServer.setJobString(obtainJobIdForConnection(reply.getJobNameBytes())); + + // Set authenticated user if not already set. + if (userId_.length() == 0) + { + byte[] b = reply.getUserIdBytes(); + if (b != null) + userId_ = SignonConverter.byteArrayToString(b); + } } // ------- @@ -5093,4 +5095,58 @@ public void setAdditionalAuthenticationFactor(char[] additionalAuthFactor) additionalAuthFactor_ = (null != additionalAuthFactor && 0 < additionalAuthFactor.length ) ? (new String(additionalAuthFactor)).getBytes(StandardCharsets.UTF_8) : null; } + + private Object[] getAdditionalAuthInfo(ProfileTokenCredential profileToken, Boolean aafIndicator) + { + Object[] authdata = new Object[] { null, null, null }; + + int vrm = (version_ != null) ? version_.getVersionReleaseModification() : getVRM(); + if (vrm > 0x00070500 || (aafIndicator != null && aafIndicator)) + { + authdata[0] = additionalAuthFactor_; + + // If profile token is null, means we are not generating a profile token, so profile token should be in credential. + boolean creatingToken = (profileToken != null); + if (profileToken == null && (credVault_ instanceof ProfileTokenVault)) + profileToken = ((ProfileTokenVault)credVault_).getProfileTokenCredential(); + + if (profileToken != null) + { + String verificationID_s = profileToken.getVerificationID(); + authdata[1] = (verificationID_s != null && !verificationID_s.isEmpty()) ? verificationID_s.getBytes(StandardCharsets.UTF_8) : null; + + String clientIPAddress_s = profileToken.getRemoteIPAddress(); + authdata[2] = (clientIPAddress_s != null && !clientIPAddress_s.isEmpty()) ? clientIPAddress_s.getBytes(StandardCharsets.UTF_8) : null; + + // Verification ID will always be set to something. However, depending on where token was created, the client IP address + // may or may not be set. If creating the token and it is not set, use the IP address of the sign-on server. Thus, all tokens + // that signon server creates should have client IP address. + if (authdata[2] == null) + { + // Note that not setting client IP address will result in sign-on host server setting the client IP address. + if (creatingToken && signonServer_ != null) + { + // We are creating token, try to set client IP address using sign-on server. + clientIPAddress_s = signonServer_.getLocalAddress(); + authdata[2] = (clientIPAddress_s != null && !clientIPAddress_s.isEmpty()) ? clientIPAddress_s.getBytes(StandardCharsets.UTF_8) : null; + try { + profileToken.setRemoteIPAddress(clientIPAddress_s); + } catch (PropertyVetoException e) { + throw new InternalErrorException(InternalErrorException.UNEXPECTED_EXCEPTION, e); + } + } + else if (profileToken.getTokenCreator() != ProfileTokenCredential.CREATOR_SIGNON_SERVER) + authdata[2] = "*NOUSE".getBytes(StandardCharsets.UTF_8); + } + } + } + + if (Trace.traceOn_) + { + Trace.log(Trace.DIAGNOSTIC, "Verification ID: " + (authdata[1] != null ? new String((byte[])authdata[1]) : null)); + Trace.log(Trace.DIAGNOSTIC, "Client IP address: " + (authdata[2] != null ? new String((byte[])authdata[2]) : null)); + } + + return authdata; + } } \ No newline at end of file diff --git a/src/main/java/com/ibm/as400/access/AS400JDBCDataSource.java b/src/main/java/com/ibm/as400/access/AS400JDBCDataSource.java index ff0c5a09c..2e501f207 100644 --- a/src/main/java/com/ibm/as400/access/AS400JDBCDataSource.java +++ b/src/main/java/com/ibm/as400/access/AS400JDBCDataSource.java @@ -299,9 +299,8 @@ public AS400JDBCDataSource(AS400 as400) this(); as400_ = as400; - if( as400 instanceof SecureAS400 || as400.isSecure() ) + if (as400.isSecure()) setSecure(true); - } @@ -321,15 +320,9 @@ public AS400JDBCDataSource(AS400 as400) // set up property change support changes_ = new PropertyChangeSupport(this); - // set up the as400 object - if (((String) reference.get(SECURE).getContent()).equalsIgnoreCase(TRUE_)) { - isSecure_ = true; - as400_ = new SecureAS400(); - - } else { - isSecure_ = false; - as400_ = new AS400(); - } + // set up the as400 object + isSecure_ = ((String) reference.get(SECURE).getContent()).equalsIgnoreCase(TRUE_); + as400_ = AS400.newInstance(isSecure_); // must initialize the JDProperties so the property change checks dont get a NullPointerException properties_ = new JDProperties(null, null,null,null); @@ -570,19 +563,10 @@ public Connection getConnection() throws SQLException //if the user asks for the object //to be secure, clone a SecureAS400 object; otherwise, clone an AS400 object char[] aaf = properties_.getAdditionalAuthenticationFactor(); - if (isSecure_ || isSecure()) { - SecureAS400 newAs400 = new SecureAS400(as400_); - if (aaf != null) { - newAs400.setAdditionalAuthenticationFactor(aaf); - } - return getConnection(newAs400); //@B4A - } else { //@B4A - AS400 newAs400 = new AS400(as400_); - if (aaf != null) { - newAs400.setAdditionalAuthenticationFactor(aaf); - } - return getConnection(newAs400); - } + AS400 newAs400 = AS400.newInstance((isSecure_ || isSecure()), as400_); + newAs400.setAdditionalAuthenticationFactor(aaf); + + return getConnection(newAs400); } @@ -704,12 +688,9 @@ public Connection getConnection(String user, char[] password, char[] additionalA //if the user asks for the object //to be secure, clone a SecureAS400 object; otherwise, clone an AS400 object - try { - if (isSecure_ || isSecure()) { // @C2A - as400Object = new SecureAS400(getServerName(), user, password, additionalAuthenticationFactor); // @C2A - } else { // @C2A //@C2A - as400Object = new AS400(getServerName(), user, password, additionalAuthenticationFactor); // @C2A - } // @C2A + try + { + as400Object = AS400.newInstance((isSecure_ || isSecure()), getServerName(), user, password, additionalAuthenticationFactor); } catch (AS400SecurityException e) { JDError.throwSQLException(this, JDError.EXC_CONNECTION_REJECTED, e); throw new SQLException("PREVENT COMPILER ERROR"); /* Dead code */ @@ -1617,10 +1598,7 @@ private void initializeTransient() changes_ = new PropertyChangeSupport(this); - if (isSecure_) //@B4A - as400_ = new SecureAS400(); //@B4A - else //@B4A - as400_ = new AS400(); + as400_ = AS400.newInstance(isSecure_); // Reinitialize the serverName, user, password, etc. if (serialServerName_ != null) diff --git a/src/main/java/com/ibm/as400/access/AS400JDBCDriver.java b/src/main/java/com/ibm/as400/access/AS400JDBCDriver.java index 3175980e0..454673cda 100644 --- a/src/main/java/com/ibm/as400/access/AS400JDBCDriver.java +++ b/src/main/java/com/ibm/as400/access/AS400JDBCDriver.java @@ -546,10 +546,7 @@ public java.sql.Connection connect (AS400 system) if (system == null) throw new NullPointerException("system"); - if (system instanceof SecureAS400) - return initializeConnection(new SecureAS400(system)); - else - return initializeConnection(new AS400(system)); + return initializeConnection(AS400.newInstance(system.isSecure(), system)); // Initialize the connection. //@B7D Connection connection = null; @@ -756,12 +753,7 @@ else if (JDProperties.isToolboxTraceSet (null, info) == JDProperties.TRACE_TOOLB if(!clone) //Do not clone the AS400 object, use the one passed in return initializeConnection(schema, info, system); else //clone the AS400 object - { - if(system instanceof SecureAS400) - return initializeConnection(schema, info, new SecureAS400(system)); - else - return initializeConnection(schema, info, new AS400(system)); - } + return initializeConnection(schema, info, AS400.newInstance(system.isSecure(), system)); } //@B5A @@ -946,10 +938,8 @@ else if (JDProperties.isToolboxTraceSet (null, info) == JDProperties.TRACE_TOOLB //@PDD not used JDProperties jdProperties = new JDProperties (null, info); - if (system instanceof SecureAS400) - return initializeConnection(schema, info, new SecureAS400(system)); - else - return initializeConnection(schema, info, new AS400(system)); + return initializeConnection(schema, info, AS400.newInstance(system.isSecure(), system)); + // Initialize the connection if the URL is valid. //@B7D Connection connection = null; //@B7D connection = initializeConnection (schema, info, o); @@ -1191,37 +1181,22 @@ static AS400 initializeAS400(JDDataSourceURL dataSourceUrl, // Create the AS400 object, so we can create a Connection via loadImpl2. AS400 as400 = null; - try { - if (secure) + try { - if (serverName.length() == 0) - as400 = new SecureAS400 (); - else if (userName == null ) - as400 = new SecureAS400 (serverName); - else if (clearPassword == null ) - as400 = new SecureAS400 (serverName, userName); - else - as400 = new SecureAS400 (serverName, userName, clearPassword, additionalAuthenticationFactor); - + if (serverName.length() == 0) + as400 = AS400.newInstance(secure); + else if ((userName == null) || (userName.length() == 0)) + as400 = AS400.newInstance(secure, serverName); + else if (clearPassword == null) + as400 = AS400.newInstance(secure, serverName, userName); + else + as400 = AS400.newInstance(secure, serverName, userName, clearPassword, additionalAuthenticationFactor); Object sslSocketFactoryObject = jdProperties.getOriginalInfo().get(PROPERTY_SSL_SOCKET_FACTORY); if ((sslSocketFactoryObject != null) && (sslSocketFactoryObject instanceof SSLSocketFactory)) { as400.setSSLSocketFactory((SSLSocketFactory) sslSocketFactoryObject); } } - else - { - if (serverName.length() == 0) - as400 = new AS400 (); - else if ((userName == null) || (userName.length() == 0)) - as400 = new AS400 (serverName); - else if (clearPassword == null ) - as400 = new AS400 (serverName, userName); - else - // Note: If additionalAuthenticationFactor is specified then a connection to the - // signon server will immediately be established. - as400 = new AS400 (serverName, userName, clearPassword, additionalAuthenticationFactor); - } - } catch (AS400SecurityException e) + catch (AS400SecurityException e) { JDError.throwSQLException (as400, JDError.EXC_CONNECTION_REJECTED, e); } diff --git a/src/main/java/com/ibm/as400/access/AS400JDBCManagedDataSource.java b/src/main/java/com/ibm/as400/access/AS400JDBCManagedDataSource.java index cc5ac9106..bcfcc37bb 100644 --- a/src/main/java/com/ibm/as400/access/AS400JDBCManagedDataSource.java +++ b/src/main/java/com/ibm/as400/access/AS400JDBCManagedDataSource.java @@ -283,9 +283,7 @@ public AS400JDBCManagedDataSource(String serverName, String user, String passwor this(); setSecure(true); - - as400_ = new SecureAS400(as400_); - + as400_ = AS400.newInstance(true, as400_); setServerName(serverName); setUser(user); @@ -307,17 +305,9 @@ public AS400JDBCManagedDataSource(String serverName, String user, String passwor Properties properties = new Properties(); // Set up the as400 object. - if (((String)reference.get(SECURE).getContent()).equalsIgnoreCase(TRUE_)) - { - isSecure_ = true; - as400_ = new SecureAS400(); + isSecure_ = ((String)reference.get(SECURE).getContent()).equalsIgnoreCase(TRUE_); + as400_ = AS400.newInstance(isSecure_); - } - else - { - isSecure_ = false; - as400_ = new AS400(); - } // Note that we allow the SECURE property to also get added to JDProperties in the loop below. boolean isConnectionPoolDataSource = (this instanceof AS400JDBCManagedConnectionPoolDataSource); @@ -455,12 +445,8 @@ final AS400JDBCConnection createPhysicalConnection() throws SQLException // If the user asks for the object // to be secure, clone a SecureAS400 object; otherwise, clone an AS400 object. - if (isSecure_ || isSecure()) { - as400Object = new SecureAS400(as400_); - } - else { - as400Object = new AS400(as400_); - } + as400Object = AS400.newInstance((isSecure_ || isSecure()), as400_); + if (sockProps_.isAnyOptionSet()) { // only need to set if not default as400Object.setSocketProperties(sockProps_); } @@ -500,12 +486,8 @@ final AS400JDBCConnection createPhysicalConnection(String user, char[] password) // If the user asks for the object // to be secure, clone a SecureAS400 object; otherwise, clone an AS400 object. - if (isSecure_ || isSecure()) { - as400Object = new SecureAS400(as400_); - } - else { - as400Object = new AS400(as400_); - } + as400Object = AS400.newInstance((isSecure_ || isSecure()), as400_); + try { as400Object.setUserId(user); as400Object.setPassword(password); @@ -2023,10 +2005,8 @@ private final void initializeTransient() poolManagerInitialized_ = false; defaultConnectionPoolKey_ = null; connectionKeyNeedsUpdate_ = true; - if (isSecure_) - as400_ = new SecureAS400(); - else - as400_ = new AS400(); + + as400_ = AS400.newInstance(isSecure_); if (sockProps_.isAnyOptionSet()) { // only need to set if not default as400_.setSocketProperties(sockProps_); diff --git a/src/main/java/com/ibm/as400/access/AS400Server.java b/src/main/java/com/ibm/as400/access/AS400Server.java index e465d1305..7cad64e98 100644 --- a/src/main/java/com/ibm/as400/access/AS400Server.java +++ b/src/main/java/com/ibm/as400/access/AS400Server.java @@ -51,6 +51,10 @@ final int getService() { return service_; } + final String getLocalAddress() { + return socket_.getLocalAddress(); + } + abstract boolean isConnected(); public abstract DataStream getExchangeAttrReply(); public abstract void setExchangeAttrReply(DataStream xChgAttrReply); diff --git a/src/main/java/com/ibm/as400/access/AS400StrSvrDS.java b/src/main/java/com/ibm/as400/access/AS400StrSvrDS.java index 7bb34c5be..a9a974d10 100644 --- a/src/main/java/com/ibm/as400/access/AS400StrSvrDS.java +++ b/src/main/java/com/ibm/as400/access/AS400StrSvrDS.java @@ -81,12 +81,15 @@ else if (authenticationBytes.length == 20) } - AS400StrSvrDS(int serverId, byte[] userIDbytes, byte[] authenticationBytes, int byteType, byte[] addAuthFactor) + AS400StrSvrDS(int serverId, byte[] userIDbytes, byte[] authenticationBytes, int authScheme, byte[] addAuthFactor, byte[] verificationID, byte[] clientIPAddr) { super(new byte[((userIDbytes == null) ? 28 + authenticationBytes.length : 44 + authenticationBytes.length) + - ((addAuthFactor != null && addAuthFactor.length > 0) ? (addAuthFactor.length + 10) : 0)]); + ((authScheme == AS400.AUTHENTICATION_SCHEME_PASSWORD && addAuthFactor != null && addAuthFactor.length > 0) ? (addAuthFactor.length + 10) : 0) + + ((authScheme == AS400.AUTHENTICATION_SCHEME_PROFILE_TOKEN && verificationID != null) ? (verificationID.length + 10) : 0) + + ((authScheme == AS400.AUTHENTICATION_SCHEME_PROFILE_TOKEN && clientIPAddr != null) ? (clientIPAddr.length + 10) : 0) + ]); setLength(data_.length); // Header ID replaced with Attributes. @@ -100,15 +103,15 @@ else if (authenticationBytes.length == 20) int offset = 20; - if (byteType == AS400.AUTHENTICATION_SCHEME_IDENTITY_TOKEN) + if (authScheme == AS400.AUTHENTICATION_SCHEME_IDENTITY_TOKEN) data_[20] = (byte)0x06; else data_[20] = (byte)0x02; - if (byteType == AS400.AUTHENTICATION_SCHEME_GSS_TOKEN) + if (authScheme == AS400.AUTHENTICATION_SCHEME_GSS_TOKEN) data_[20] = (byte)0x05; - if (byteType == AS400.AUTHENTICATION_SCHEME_PASSWORD) + if (authScheme == AS400.AUTHENTICATION_SCHEME_PASSWORD) { if (authenticationBytes.length == 8) data_[20] = (byte) 0x01; @@ -124,7 +127,7 @@ else if (authenticationBytes.length == 20) // LL set32bit(6 + authenticationBytes.length, 22); // CP - if (byteType == AS400.AUTHENTICATION_SCHEME_PASSWORD) + if (authScheme == AS400.AUTHENTICATION_SCHEME_PASSWORD) set16bit(0x1105, 26); else set16bit(0x1115, 26); @@ -146,17 +149,55 @@ else if (authenticationBytes.length == 20) offset += 16; } - if (addAuthFactor != null && addAuthFactor.length > 0) + if (authScheme == AS400.AUTHENTICATION_SCHEME_PASSWORD) { - // Set additional authentication factor - // LL - set32bit(4 + 2 + 4 + addAuthFactor.length, offset); - // CP - set16bit(0x112F, offset + 4); - // CCSID - set32bit(1208, offset + 6); - // Data (Additional authentication factor in UTF-8) - System.arraycopy(addAuthFactor, 0, data_, offset + 10, addAuthFactor.length); + if (addAuthFactor != null && addAuthFactor.length > 0) + { + // Set additional authentication factor + // LL + set32bit(4 + 2 + 4 + addAuthFactor.length, offset); + // CP + set16bit(0x112F, offset + 4); + // CCSID + set32bit(1208, offset + 6); + // Data (Additional authentication factor in UTF-8) + System.arraycopy(addAuthFactor, 0, data_, offset + 10, addAuthFactor.length); + + offset += 10 + addAuthFactor.length; + } + } + + if (authScheme == AS400.AUTHENTICATION_SCHEME_PROFILE_TOKEN) + { + if (verificationID != null) + { + // Set verification ID + // LL + set32bit(4 + 2 + 4 + verificationID.length, offset); + // CP + set16bit(0x1130, offset + 4); + // CCSID + set32bit(1208, offset + 6); + // Data (verification ID in UTF-8) + System.arraycopy(verificationID, 0, data_, offset + 10, verificationID.length); + + offset += 10 + verificationID.length; + } + + if (clientIPAddr != null) + { + // Set client IP address + // LL + set32bit(4 + 2 + 4 + clientIPAddr.length, offset); + // CP + set16bit(0x1131, offset + 4); + // CCSID + set32bit(1208, offset + 6); + // Data (client IP address in UTF-8) + System.arraycopy(clientIPAddr, 0, data_, offset + 10, clientIPAddr.length); + + offset += 10 + clientIPAddr.length; + } } } diff --git a/src/main/java/com/ibm/as400/access/ClassDecoupler.java b/src/main/java/com/ibm/as400/access/ClassDecoupler.java index 42d55ca25..9b212b616 100644 --- a/src/main/java/com/ibm/as400/access/ClassDecoupler.java +++ b/src/main/java/com/ibm/as400/access/ClassDecoupler.java @@ -235,13 +235,13 @@ else if ((authScheme == AS400.AUTHENTICATION_SCHEME_PASSWORD) || (authSchemeToUs static void connectDDMPhase2(OutputStream outStream, InputStream inStream, byte[] userIDbytes, byte[] ddmSubstitutePassword, byte[] iaspBytes, int authScheme, String ddmRDB, String systemName, int connectionID, - byte[] addAuthFactor) throws ServerStartupException, IOException, AS400SecurityException + byte[] addAuthFactor, byte[] verificationID, byte[] clientIPAddr) throws ServerStartupException, IOException, AS400SecurityException { // If the ddmSubstitutePassword length is 8, then we are using DES encryption. // If its length is 20, then we are using SHA encryption. // Build the SECCHK request; we build the request here so that we are not // passing the password around anymore than we have to. - DDMSECCHKRequestDataStream SECCHKReq = new DDMSECCHKRequestDataStream(userIDbytes, ddmSubstitutePassword, iaspBytes, authScheme, addAuthFactor); + DDMSECCHKRequestDataStream SECCHKReq = new DDMSECCHKRequestDataStream(userIDbytes, ddmSubstitutePassword, iaspBytes, authScheme, addAuthFactor, verificationID, clientIPAddr); if (Trace.traceOn_) SECCHKReq.setConnectionID(connectionID); // Send the SECCHK request. diff --git a/src/main/java/com/ibm/as400/access/ConnectionList.java b/src/main/java/com/ibm/as400/access/ConnectionList.java index f3bcf0397..10729010f 100644 --- a/src/main/java/com/ibm/as400/access/ConnectionList.java +++ b/src/main/java/com/ibm/as400/access/ConnectionList.java @@ -265,8 +265,8 @@ PoolItem getConnection(Integer service, boolean secure, ConnectionPoolEventSuppo // If in-use or not right type of AS400 object, skip it. if (item.isInUse() - || (secure && !(item.getAS400Object() instanceof SecureAS400)) - || (!secure && (item.getAS400Object() instanceof SecureAS400)) + || (secure && !(item.getAS400Object().isSecure())) + || (!secure && (item.getAS400Object().isSecure())) || (service != null && !item.getAS400Object().isConnected(service))) continue; @@ -293,8 +293,8 @@ PoolItem getConnection(Integer service, boolean secure, ConnectionPoolEventSuppo // If in-use or not right type of AS400 object, skip it. if (item.isInUse() - || (secure && !(item.getAS400Object() instanceof SecureAS400)) - || (!secure && (item.getAS400Object() instanceof SecureAS400))) + || (secure && !(item.getAS400Object().isSecure())) + || (!secure && (item.getAS400Object().isSecure()))) continue; //@B2A Add a check for locales. If the user did not specify a locale at diff --git a/src/main/java/com/ibm/as400/access/CurrentUser.java b/src/main/java/com/ibm/as400/access/CurrentUser.java index 9449940a1..de81edace 100644 --- a/src/main/java/com/ibm/as400/access/CurrentUser.java +++ b/src/main/java/com/ibm/as400/access/CurrentUser.java @@ -24,15 +24,11 @@ static String getUserID(int vrm) try { if (vrm >= 0x00050400) - { currentUser = NativeMethods.getUserId(); - } else if (vrm >= 0x00050200) { if (NativeMethods.loadSCK()) - { currentUser = getUserIdNative(); - } } else { @@ -40,29 +36,29 @@ else if (vrm >= 0x00050200) currentUser = user.getUserId(); } } - catch (NativeException e) - { + catch (NativeException e) { Trace.log(Trace.ERROR, "Error retrieving current userID:", e); } - catch (Throwable e) - { + catch (Throwable e) { Trace.log(Trace.ERROR, "Error retrieving current userID:", e); } + if (currentUser != null) { - try { //@AC4A + try + { if (Trace.isTraceOn()) Trace.log(Trace.DIAGNOSTIC, "Current userID in EBCDIC: ", currentUser); String userID = SignonConverter.byteArrayToString(currentUser); + if (Trace.isTraceOn()) Trace.log(Trace.DIAGNOSTIC, "Current userID: '" + userID + "'"); return userID; - //@AC4A Start - } catch (AS400SecurityException e) { + } + catch (AS400SecurityException e) { if (Trace.isTraceOn()) Trace.log(Trace.DIAGNOSTIC, "Current userID convert failed, user id characters are not valid"); return null; - } - //@AC4A End - + } } + return null; } @@ -72,18 +68,14 @@ static byte[] getUserInfo(int vrm, byte[] clientSeed, byte[] serverSeed, String try { if (vrm >= 0x00050400) - { return NativeMethods.getUserInfo(clientSeed, serverSeed); - } else if (vrm >= 0x00050200) - { return getUserInfoNative(clientSeed, serverSeed); - } + UnixSocketUser user = new UnixSocketUser(); return user.getSubstitutePassword(clientSeed, serverSeed); } - catch (NativeException e) - { + catch (NativeException e) { throw AS400ImplRemote.returnSecurityException(BinaryConverter.byteArrayToInt(e.data, 0),null,info); } } diff --git a/src/main/java/com/ibm/as400/access/DDMSECCHKRequestDataStream.java b/src/main/java/com/ibm/as400/access/DDMSECCHKRequestDataStream.java index faf0baa63..ffdda8003 100644 --- a/src/main/java/com/ibm/as400/access/DDMSECCHKRequestDataStream.java +++ b/src/main/java/com/ibm/as400/access/DDMSECCHKRequestDataStream.java @@ -24,14 +24,16 @@ class DDMSECCHKRequestDataStream extends DDMDataStream private static final String copyright = "Copyright (C) 1997-2024 International Business Machines Corporation and others."; DDMSECCHKRequestDataStream(byte[] userIDbytes, byte[] authenticationBytes, byte[] iasp, int authScheme, - byte[] addAuthFactor) + byte[] addAuthFactor, byte[] verificationID, byte[] clientIPAddr) { super(new byte[authenticationBytes.length + userIDbytes.length + (iasp == null ? 0 : 22) + ((authScheme == AS400.AUTHENTICATION_SCHEME_PASSWORD || authScheme == AS400.AUTHENTICATION_SCHEME_DDM_EUSERIDPWD) ? 24 : 16) + (((authScheme == AS400.AUTHENTICATION_SCHEME_PASSWORD - || authScheme == AS400.AUTHENTICATION_SCHEME_DDM_EUSERIDPWD) && addAuthFactor != null) ? addAuthFactor.length + 12: 0)]); + || authScheme == AS400.AUTHENTICATION_SCHEME_DDM_EUSERIDPWD) && addAuthFactor != null) ? addAuthFactor.length + 12: 0) + + ((authScheme == AS400.AUTHENTICATION_SCHEME_PROFILE_TOKEN && verificationID != null) ? verificationID.length + 8: 0) + + ((authScheme == AS400.AUTHENTICATION_SCHEME_PROFILE_TOKEN && clientIPAddr != null) ? clientIPAddr.length + 8: 0)]); // Initialize the header: Don't continue on error, not chained, GDS id = D0, // type = RQSDSS, no same request correlation. @@ -39,13 +41,20 @@ class DDMSECCHKRequestDataStream extends DDMDataStream if ((authScheme != AS400.AUTHENTICATION_SCHEME_PASSWORD) && (authScheme != AS400.AUTHENTICATION_SCHEME_DDM_EUSERIDPWD)) { - setLength(16); + byte[] verficationIDBytes = (authScheme == AS400.AUTHENTICATION_SCHEME_PROFILE_TOKEN) ? verificationID : null; + byte[] clientIPAddrBytes = (authScheme == AS400.AUTHENTICATION_SCHEME_PROFILE_TOKEN) ? clientIPAddr : null; + + setLength(16 + + + ((verficationIDBytes != null) ? verficationIDBytes.length + 8 : 0) + + ((clientIPAddrBytes != null) ? clientIPAddrBytes.length + 8 : 0)); setIsChained(true); // setContinueOnError(false); setHasSameRequestCorrelation(true); setType(1); // 1 = RQSDSS - set16bit(10, 6); // Set total length remaining after header. + set16bit(10 + + + ((verficationIDBytes != null) ? verficationIDBytes.length + 8 : 0) + + ((clientIPAddrBytes != null) ? clientIPAddrBytes.length + 8 : 0), 6); // Set total length remaining after header. set16bit(DDMTerm.SECCHK, 8); // Set code point for SECCHK. set16bit(6, 10); // Set LL for SECMEC term. @@ -56,14 +65,46 @@ class DDMSECCHKRequestDataStream extends DDMDataStream else set16bit(DDMTerm.EUSRIDONL, 14); - set16bit(authenticationBytes.length + 10, 16); // Set LL for entire token-related data - set16bit(0xD003, 18); // Set code point + int offset = 16; + + if (authScheme == AS400.AUTHENTICATION_SCHEME_PROFILE_TOKEN) + { + if (verficationIDBytes != null) + { + if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Sending SECCHK request with verification ID."); + + set16bit(8 + verficationIDBytes.length, offset); // LL + set16bit(DDMTerm.SXXVERID, offset + 2); // Term/Code point + set32bit(1208, offset + 4); // ccsid + System.arraycopy(verficationIDBytes, 0, data_, offset + 8, verficationIDBytes.length); // Data + + offset += 8 + verficationIDBytes.length; + } + + if (clientIPAddrBytes != null) + { + if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Sending SECCHK request with client IP address."); - set16bit(authenticationBytes.length + 4, 22); // Set LL token - set16bit(DDMTerm.SECTKN, 24); // Set code point - System.arraycopy(authenticationBytes, 0, data_, 26, authenticationBytes.length); + set16bit(8 + clientIPAddrBytes.length, offset); // LL + set16bit(DDMTerm.SXXCLTIP, offset + 2); // Term/Code point + set32bit(1208, offset + 4); // ccsid + System.arraycopy(clientIPAddrBytes, 0, data_, offset + 8, clientIPAddrBytes.length); // Data + + offset += 8 + clientIPAddrBytes.length; + } + } + + set16bit(authenticationBytes.length + 10, offset); // Set LL for entire token-related data + set16bit(0xD003, offset + 2); // Set code point + + // Request correlator included + offset += 6; + + set16bit(authenticationBytes.length + 4, offset); // Set LL token + set16bit(DDMTerm.SECTKN, offset + 2); // Set code point + System.arraycopy(authenticationBytes, 0, data_, offset + 4, authenticationBytes.length); - int offset = 26 + authenticationBytes.length; + offset += 4 + authenticationBytes.length; } else { diff --git a/src/main/java/com/ibm/as400/access/IFSUserHandle2Rep.java b/src/main/java/com/ibm/as400/access/IFSUserHandle2Rep.java new file mode 100644 index 000000000..02aa8f945 --- /dev/null +++ b/src/main/java/com/ibm/as400/access/IFSUserHandle2Rep.java @@ -0,0 +1,54 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// JTOpen (IBM Toolbox for Java - OSS version) +// +// Filename: IFSCreateUserHandleRep.java +// +// The source code contained herein is licensed under the IBM Public License +// Version 1.0, which has been approved by the Open Source Initiative. +// Copyright (C) 2016-2016 International Business Machines Corporation and +// others. All rights reserved. +// +/////////////////////////////////////////////////////////////////////////////// +package com.ibm.as400.access; + +public class IFSUserHandle2Rep extends IFSDataStream { + + private static final int HANDLE_OFFSET= 24; + private static final int RETURN_CODE_OFFSET = 22; + /** + Get the working directory handle. + @return the working directory handle. + **/ + int getHandle() + { + return get32bit(HANDLE_OFFSET); + } + + /** + Generate a new instance of this type. + @return a reference to the new instance + **/ + public Object getNewDataStream() + { + return new IFSUserHandle2Rep(); + } + + /** + Get the return code. + @return the return code + **/ + int getReturnCode() + { + return get16bit(RETURN_CODE_OFFSET); + } + + /** + Generates a hash code for this data stream. + @return the hash code + **/ + public int hashCode() + { + return 0x802B; + } +} diff --git a/src/main/java/com/ibm/as400/access/ISeriesPrinter.java b/src/main/java/com/ibm/as400/access/ISeriesPrinter.java index a0e1cdf4a..477c56e1b 100644 --- a/src/main/java/com/ibm/as400/access/ISeriesPrinter.java +++ b/src/main/java/com/ibm/as400/access/ISeriesPrinter.java @@ -1563,7 +1563,7 @@ public void refresh() throws AS400SecurityException, ErrorCompletingRequestExcep }; // Note this is not an open list API, even though it starts with QGY. - ProgramCall pc = new ProgramCall(system_, "/QSYS.LIB/QGY.LIB/QGYRPRTA.PGM", parameters); + ProgramCall pc = new ProgramCall(system_, "/QSYS.LIB/QGYRPRTA.PGM", parameters); if (!pc.run()) { throw new AS400Exception(pc.getMessageList()); diff --git a/src/main/java/com/ibm/as400/access/JobList.java b/src/main/java/com/ibm/as400/access/JobList.java index 4aabccc3e..093046dbb 100644 --- a/src/main/java/com/ibm/as400/access/JobList.java +++ b/src/main/java/com/ibm/as400/access/JobList.java @@ -1562,7 +1562,7 @@ public synchronized void load() throws AS400SecurityException, ErrorCompletingRe }; // Call the program. - ProgramCall pc = new ProgramCall(system_, "/QSYS.LIB/QGY.LIB/QGYOLJOB.PGM", parameters); + ProgramCall pc = new ProgramCall(system_, "/QSYS.LIB/QGYOLJOB.PGM", parameters); if (!pc.run()) { diff --git a/src/main/java/com/ibm/as400/access/JobLog.java b/src/main/java/com/ibm/as400/access/JobLog.java index 95d1a8f6e..c2923cf64 100644 --- a/src/main/java/com/ibm/as400/access/JobLog.java +++ b/src/main/java/com/ibm/as400/access/JobLog.java @@ -691,7 +691,7 @@ public synchronized void load() throws AS400SecurityException, ErrorCompletingRe }; // Call the program. This API is not thread safe. - ProgramCall pc = new ProgramCall(system_, "/QSYS.LIB/QGY.LIB/QGYOLJBL.PGM", parameters); // not a threadsafe API + ProgramCall pc = new ProgramCall(system_, "/QSYS.LIB/QGYOLJBL.PGM", parameters); // not a threadsafe API if (!pc.run()) { throw new AS400Exception(pc.getMessageList()); diff --git a/src/main/java/com/ibm/as400/access/ListUtilities.java b/src/main/java/com/ibm/as400/access/ListUtilities.java index 04bed1b0a..16ee25d45 100644 --- a/src/main/java/com/ibm/as400/access/ListUtilities.java +++ b/src/main/java/com/ibm/as400/access/ListUtilities.java @@ -161,7 +161,7 @@ static void closeList(AS400 system, byte[] listHandle) throws AS400SecurityExcep new ProgramParameter(listHandle), new ErrorCodeParameter() }; - ProgramCall pc = new ProgramCall(system, "/QSYS.LIB/QGY.LIB/QGYCLST.PGM", parameters); // not a threadsafe API + ProgramCall pc = new ProgramCall(system, "/QSYS.LIB/QGYCLST.PGM", parameters); // not a threadsafe API if (!pc.run()) { throw new AS400Exception(pc.getMessageList()); @@ -197,7 +197,7 @@ private static byte[] refreshListInformation(byte[] listHandle, ProgramCall pgmC new ErrorCodeParameter() }; - try { pgmCall.setProgram("/QSYS.LIB/QGY.LIB/QGYGTLE.PGM", parameters); } + try { pgmCall.setProgram("/QSYS.LIB/QGYGTLE.PGM", parameters); } catch (java.beans.PropertyVetoException pve) {} // will never happen } @@ -290,7 +290,7 @@ static byte[] retrieveListEntries(AS400 system, byte[] listHandle, int lengthOfR new ErrorCodeParameter() }; - ProgramCall pc = new ProgramCall(system, "/QSYS.LIB/QGY.LIB/QGYGTLE.PGM", parameters); // not a threadsafe API + ProgramCall pc = new ProgramCall(system, "/QSYS.LIB/QGYGTLE.PGM", parameters); // not a threadsafe API byte[] listInformation; int recordsReturned; diff --git a/src/main/java/com/ibm/as400/access/ManagedProfileTokenVault.java b/src/main/java/com/ibm/as400/access/ManagedProfileTokenVault.java index bb84a749c..46b9f8db9 100644 --- a/src/main/java/com/ibm/as400/access/ManagedProfileTokenVault.java +++ b/src/main/java/com/ibm/as400/access/ManagedProfileTokenVault.java @@ -120,310 +120,321 @@ */ class ManagedProfileTokenVault extends ProfileTokenVault implements Cloneable, Serializable { - /** - * Constant that indicates the profile token credential managed by the vault - * should be refreshed every time its raw bytes (i.e. the underlying - * credential) is requested. - */ - private static final int REFRESH_TOKEN_EVERY_TIME = -1; - - /** - * Constant representing the minimum amount of time, in seconds, allowed - * between a refresh of the profile token credential managed by the vault. - */ - private static final int MIN_TOKEN_REFRESH_TIME_INTERVAL = 30; - - /** - * Constant representing the maximum amount of time, in seconds, allowed - * between a refresh of the profile token credential managed by the vault. - */ - private static final int MAX_TOKEN_REFRESH_TIME_INTERVAL = (60 * 59); // 59 minutes - - /** The object that provides a new profile token credential for the vault. */ - private ProfileTokenProvider tokenProvider_; - - /** The profile token credential. */ - private ProfileTokenCredential profileToken_; - - /** - * The amount of time, in seconds, to wait before refreshing the - * existing profile token credential. The maximum value for this - * field is {@link #MAX_TOKEN_REFRESH_TIME_INTERVAL} - */ - private int refreshThreshold_; - - /** - * Constructs a ManagedProfileTokenVault object. A new profile token - * is generated during the construction of the vault using the specified - * token provider. If a new profile token is needed in the future, the - * same token provider will be used. The refresh threshold is set to - * a default value of half the profile token's timeout interval. - * - * @param tokenProvider The provider to use when a new profile token needs to be generated - */ - protected ManagedProfileTokenVault(ProfileTokenProvider tokenProvider) { - this(tokenProvider, REFRESH_TOKEN_EVERY_TIME); - } - - /** - * Constructs a ManagedProfileTokenVault object. A new profile token - * is generated during the construction of the vault using the specified - * token provider. If a new profile token is needed in the future, the - * same token provider will be used. The refresh threshold is set to - * the value specified by the refreshThreshold parameter. - * - * @param tokenProvider The provider to use when a new profile token needs to be generated - * @param refreshThreshold The refresh threshold, in seconds, for the profile token. Used - * by the vault to manage the currency of the profile token to - * help ensure it remains current for an indefinite period of time. - */ - protected ManagedProfileTokenVault(ProfileTokenProvider tokenProvider, int refreshThreshold) { - super(); - try { - profileToken_ = tokenProvider.create(); - encodedCredential_ = store(profileToken_.getToken()); - initRefreshThreshold(refreshThreshold == REFRESH_TOKEN_EVERY_TIME ? profileToken_.getTimeoutInterval() / 2 : refreshThreshold); - } - catch (AS400SecurityException e) { - Trace.log(Trace.ERROR, "Error while created ManagedProfileTokenVault.", e); + /** + * Constant that indicates the profile token credential managed by the vault should be refreshed every time its raw + * bytes (i.e. the underlying credential) is requested. + */ + private static final int REFRESH_TOKEN_EVERY_TIME = -1; + + /** + * Constant representing the minimum amount of time, in seconds, allowed between a refresh of the profile token + * credential managed by the vault. + */ + private static final int MIN_TOKEN_REFRESH_TIME_INTERVAL = 30; + + /** + * Constant representing the maximum amount of time, in seconds, allowed between a refresh of the profile token + * credential managed by the vault. + */ + private static final int MAX_TOKEN_REFRESH_TIME_INTERVAL = (60 * 59); // 59 minutes + + /** The object that provides a new profile token credential for the vault. */ + private ProfileTokenProvider tokenProvider_; + + /** The profile token credential. */ + private ProfileTokenCredential profileToken_; + + /** + * The amount of time, in seconds, to wait before refreshing the existing profile token credential. The maximum + * value for this field is {@link #MAX_TOKEN_REFRESH_TIME_INTERVAL} + */ + private int refreshThreshold_; + + /** + * Constructs a ManagedProfileTokenVault object. A new profile token is generated during the construction of the + * vault using the specified token provider. If a new profile token is needed in the future, the same token provider + * will be used. The refresh threshold is set to a default value of half the profile token's timeout interval. + * + * @param tokenProvider The provider to use when a new profile token needs to be generated + */ + protected ManagedProfileTokenVault(ProfileTokenProvider tokenProvider) { + this(tokenProvider, REFRESH_TOKEN_EVERY_TIME); } - tokenProvider_ = tokenProvider; - } - - /** - * Internal use only. Used to construct an empty vault when we are - * creating a copy of an existing vault. - */ - private ManagedProfileTokenVault() { - super(); - } - - /** - * Returns a copy of this ManagedProfileTokenVault. The new copy will NOT - * be an exact copy of this vault. The characteristics (i.e. refresh - * threshold and token provider) will be exactly the same, but the profile - * token itself is not duplicated. Instead, the new vault copy generates - * its own profile token using the token provider. This non-copy of the - * profile token is required, because the vault must always maintain a 1-to-1 - * mapping between the vault and the profile token it is managing. - * - * @return A newly created ManagedProfileTokenVault with the same - * characteristics as this one, but with its own uniquely - * generated profile token. - */ - public ManagedProfileTokenVault clone() - { - ManagedProfileTokenVault vaultClone = (ManagedProfileTokenVault)super.clone(); - - synchronized(this) + + /** + * Constructs a ManagedProfileTokenVault object. A new profile token is generated during the construction of the + * vault using the specified token provider. If a new profile token is needed in the future, the same token provider + * will be used. The refresh threshold is set to the value specified by the refreshThreshold parameter. + * + * @param tokenProvider The provider to use when a new profile token needs to be generated + * @param refreshThreshold The refresh threshold, in seconds, for the profile token. Used by the vault to manage the + * currency of the profile token to help ensure it remains current for an indefinite period + * of time. + */ + protected ManagedProfileTokenVault(ProfileTokenProvider tokenProvider, int refreshThreshold) { - // - // When we duplicate the fields from an existing managed profile token vault, - // we do NOT duplicate the profile token itself. - // By design, each managed profile token vault contains its very own - // profile token. In order to maintain this 1-to-1 correlation between - // vault and token, we must create a brand new profile token for - // the newly created vault. However, we do copy the refresh threshold - // and token provider from the existing vault, so both the new vault - // and the profile token in it will have the same characteristics - // as the vault we are making a copy of. - // - - vaultClone.refreshThreshold_ = refreshThreshold_; - vaultClone.tokenProvider_ = tokenProvider_; - - try { - ProfileTokenCredential newToken = tokenProvider_.create(); - vaultClone.profileToken_ = newToken; - vaultClone.encodedCredential_ = store(newToken.getToken()); - } - catch (AS400SecurityException e) { - Trace.log(Trace.ERROR, "Error while cloning ManagedProfileTokenVault.", e); - } - return vaultClone; - } - } - - /** - * Purges the contents of the vault. All resources consumed by the - * credential vault are freed, which means the profile token stored in - * the vault will be destroyed. If this method is invoked and the vault - * is already empty, the method simply returns and no exception is thrown. - */ - protected synchronized void empty() { - // Let the super class do any cleanup it needs to - super.empty(); - disposeOfToken(); - } - - /** - * Retrieves the raw profile token credential bytes stored in the vault. - * If the profile token time to expiration is less than the refresh threshold, - * the profile token will be refreshed before returning its bytes. If the - * profile token has expired, a new profile token will be generated using the - * token provider, and the bytes of the newly generated profile token will - * be returned. - * - * @return The credential bytes for the profile token stored in the vault - */ - protected synchronized byte[] getClearCredential() { - // If the vault is empty, build ourselves a new token - if (isEmpty()) { - buildToken(); - return resolve(encodedCredential_); + super(); + try + { + profileToken_ = tokenProvider.create(); + encodedCredential_ = store(profileToken_.getToken()); + initRefreshThreshold(refreshThreshold == REFRESH_TOKEN_EVERY_TIME ? profileToken_.getTimeoutInterval() / 2 : refreshThreshold); + } + catch (AS400SecurityException e) { + Trace.log(Trace.ERROR, "Error while created ManagedProfileTokenVault.", e); + } + + tokenProvider_ = tokenProvider; } - // We have a profile token in the vault, so check if it is current. - // If it is not, then we have missed our opportunity to renew it - // and we will need to start over by creating a brand new token. - if (!profileToken_.isCurrent()) { - // The profile token has already expired. This means we need - // to start all over by creating a new one. - buildToken(); - return resolve(encodedCredential_); + /** + * Internal use only. Used to construct an empty vault when we are creating a copy of an existing vault. + */ + private ManagedProfileTokenVault() { + super(); } - // Check to see how much time is left before the token expires. - // If there is less than 'refreshThreshold' time left, then - // renew the token before returning it. - try { - if ( (isTimeForRefresh()) && (profileToken_.isRenewable()) ) { - profileToken_.refresh(); - encodedCredential_ = store(profileToken_.getToken()); - } - } - catch (Exception e) { - // In case of exception, just try to build a brand new token. - if (Trace.traceOn_) { - Trace.log(Trace.DIAGNOSTIC, "Error while refreshing profile token.", e); - } - buildToken(); - } - return resolve(encodedCredential_); - } - - /** - * Forces the profile token to be refreshed, regardless of how - * much time is left before it expires. - */ - protected synchronized void forceRefresh() { - // See if we have a profile token to refresh. - if ( (isEmpty()) || (!profileToken_.isRenewable()) ) { - // No, so just build a new one - buildToken(); - return; + /** + * Retrieve ProfileTokenCredential object in vault if one exists. + * + * @return The ProfileTokenCredential object or null. + */ + @Override + public ProfileTokenCredential getProfileTokenCredential() { + return profileToken_; } + + /** + * Returns a copy of this ManagedProfileTokenVault. The new copy will NOT be an exact copy of this vault. The + * characteristics (i.e. refresh threshold and token provider) will be exactly the same, but the profile token + * itself is not duplicated. Instead, the new vault copy generates its own profile token using the token provider. + * This non-copy of the profile token is required, because the vault must always maintain a 1-to-1 mapping between + * the vault and the profile token it is managing. + * + * @return A newly created ManagedProfileTokenVault with the same characteristics as this one, but with its own + * uniquely generated profile token. + */ + @Override + public ManagedProfileTokenVault clone() + { + ManagedProfileTokenVault vaultClone = (ManagedProfileTokenVault) super.clone(); - try { - profileToken_.refresh(); - encodedCredential_ = store(profileToken_.getToken()); - } - catch (Exception e) { - // In case of exception, just try to build a brand new token. - if (Trace.traceOn_) { - Trace.log(Trace.DIAGNOSTIC, "Error while forcefully refreshing profile token.", e); - } - buildToken(); - } - } - - /** - * {@inheritDoc} - */ - protected synchronized boolean isEmpty() { - boolean empty = super.isEmpty(); - - if (empty) { - if (profileToken_ != null) { - throw new IllegalStateException("Credential vault is empty, but profile token is not null"); - } - } - return empty; - } - - /** - * Initializes the refresh threshold. - * - * @param threshold The refresh threshold, in seconds - */ - private void initRefreshThreshold(int threshold) { - // The minimum allowed refresh threshold is 30 seconds. - // The maximum allowed is 59 minutes. - if ( (threshold < MIN_TOKEN_REFRESH_TIME_INTERVAL) || (threshold > MAX_TOKEN_REFRESH_TIME_INTERVAL) ) { - throw new IllegalArgumentException("Refresh threshold must between " + - MIN_TOKEN_REFRESH_TIME_INTERVAL + " and " + - MAX_TOKEN_REFRESH_TIME_INTERVAL + " seconds"); + synchronized (this) + { + // + // When we duplicate the fields from an existing managed profile token vault, + // we do NOT duplicate the profile token itself. + // By design, each managed profile token vault contains its very own + // profile token. In order to maintain this 1-to-1 correlation between + // vault and token, we must create a brand new profile token for + // the newly created vault. However, we do copy the refresh threshold + // and token provider from the existing vault, so both the new vault + // and the profile token in it will have the same characteristics + // as the vault we are making a copy of. + // + + vaultClone.refreshThreshold_ = refreshThreshold_; + vaultClone.tokenProvider_ = tokenProvider_; + + try + { + ProfileTokenCredential newToken = tokenProvider_.create(); + vaultClone.profileToken_ = newToken; + vaultClone.encodedCredential_ = store(newToken.getToken()); + } + catch (AS400SecurityException e) { + Trace.log(Trace.ERROR, "Error while cloning ManagedProfileTokenVault.", e); + } + + return vaultClone; + } } - refreshThreshold_ = threshold; - } - - /** - * Unconditionally disposes of the existing profile token, - * and generates a new profile token using the token provider. - */ - private void buildToken() { - try { - // First dispose of the existing token, if it exists - disposeOfToken(); - - // Next create a new one - profileToken_ = tokenProvider_.create(); - - // Finally, store the bytes of the new token in an encoded form - encodedCredential_ = store(profileToken_.getToken()); + + /** + * Purges the contents of the vault. All resources consumed by the credential vault are freed, which means the + * profile token stored in the vault will be destroyed. If this method is invoked and the vault is already empty, + * the method simply returns and no exception is thrown. + */ + @Override + protected synchronized void empty() + { + super.empty(); + disposeOfToken(); } - catch (Exception e) { - if (Trace.traceOn_) { - Trace.log(Trace.DIAGNOSTIC, "Error while building profile token.", e); - } - - // If the build and store of the profile token did not both - // succeed, then get rid of everything. This prevents us from - // getting into a half baked state where the profile token is - // present but the encoded credential is null (not sure how that - // scenario would ever happen anyway, but this protects us from - // it nontheless). - disposeOfToken(); + + /** + * Retrieves the raw profile token credential bytes stored in the vault. If the profile token time to expiration is + * less than the refresh threshold, the profile token will be refreshed before returning its bytes. If the profile + * token has expired, a new profile token will be generated using the token provider, and the bytes of the newly + * generated profile token will be returned. + * + * @return The credential bytes for the profile token stored in the vault + */ + @Override + protected synchronized byte[] getClearCredential() + { + // If the vault is empty, build ourselves a new token + if (isEmpty()) + { + buildToken(); + return resolve(encodedCredential_); + } + + // We have a profile token in the vault, so check if it is current. + // If it is not, then we have missed our opportunity to renew it + // and we will need to start over by creating a brand new token. + if (!profileToken_.isCurrent()) + { + // The profile token has already expired. This means we need + // to start all over by creating a new one. + buildToken(); + return resolve(encodedCredential_); + } + + // Check to see how much time is left before the token expires. + // If there is less than 'refreshThreshold' time left, then + // renew the token before returning it. + try + { + if ((isTimeForRefresh()) && (profileToken_.isRenewable())) + { + profileToken_.refresh(); + encodedCredential_ = store(profileToken_.getToken()); + } + } + catch (Exception e) + { + // In case of exception, just try to build a brand new token. + if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Error while refreshing profile token.", e); + + buildToken(); + } + return resolve(encodedCredential_); } - } - - /** - * Unconditionally disposes of the existing profile token. - */ - private void disposeOfToken() { - try { - // Destroy our profile token - if (profileToken_ != null) { - profileToken_.destroy(); - } + + /** + * Forces the profile token to be refreshed, regardless of how much time is left before it expires. + */ + protected synchronized void forceRefresh() + { + // See if we have a profile token to refresh. + if ((isEmpty()) || (!profileToken_.isRenewable())) + { + // No, so just build a new one + buildToken(); + return; + } + + try + { + profileToken_.refresh(); + encodedCredential_ = store(profileToken_.getToken()); + } + catch (Exception e) + { + // In case of exception, just try to build a brand new token. + if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Error while forcefully refreshing profile token.", e); + + buildToken(); + } } - catch (Exception e) { - Trace.log(Trace.ERROR, "Error while disposing of profile token.", e); + + /** + * {@inheritDoc} + */ + @Override + protected synchronized boolean isEmpty() + { + boolean empty = super.isEmpty(); + + if (empty && profileToken_ != null) + throw new IllegalStateException("Credential vault is empty, but profile token is not null"); + + return empty; } - finally { - profileToken_ = null; - encodedCredential_ = null; + + /** + * Initializes the refresh threshold. + * + * @param threshold The refresh threshold, in seconds + */ + private void initRefreshThreshold(int threshold) + { + // The minimum allowed refresh threshold is 30 seconds. + // The maximum allowed is 59 minutes. + if ( (threshold < MIN_TOKEN_REFRESH_TIME_INTERVAL) || (threshold > MAX_TOKEN_REFRESH_TIME_INTERVAL) ) + { + throw new IllegalArgumentException("Refresh threshold must between " + + MIN_TOKEN_REFRESH_TIME_INTERVAL + " and " + + MAX_TOKEN_REFRESH_TIME_INTERVAL + " seconds"); + } + + refreshThreshold_ = threshold; } - } - - /** - * Determines if it is time to refresh the profile token. This is decided - * by comparing the time left until the profile token expires, and the - * refresh threshold. - * - * @return true if the profile token needs to be refreshed, false if it does not. - * - * @throws AS400SecurityException If an IBM i system security or authentication error occurs - */ - private boolean isTimeForRefresh() throws AS400SecurityException { - if (refreshThreshold_ == REFRESH_TOKEN_EVERY_TIME) { - return true; + + /** + * Unconditionally disposes of the existing profile token, and generates a new profile token using the token + * provider. + */ + private void buildToken() + { + try + { + // First dispose of the existing token, if it exists + disposeOfToken(); + + // Next create a new one + profileToken_ = tokenProvider_.create(); + + // Finally, store the bytes of the new token in an encoded form + encodedCredential_ = store(profileToken_.getToken()); + } + catch (Exception e) + { + if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Error while building profile token.", e); + + // If the build and store of the profile token did not both + // succeed, then get rid of everything. This prevents us from + // getting into a half baked state where the profile token is + // present but the encoded credential is null (not sure how that + // scenario would ever happen anyway, but this protects us from + // it nontheless). + disposeOfToken(); + } } - else if (profileToken_ == null) { - return true; + + /** + * Unconditionally disposes of the existing profile token. + */ + private void disposeOfToken() + { + try { + // Destroy our profile token + if (profileToken_ != null) + profileToken_.destroy(); + } + catch (Exception e) { + Trace.log(Trace.ERROR, "Error while disposing of profile token.", e); + } + finally + { + profileToken_ = null; + encodedCredential_ = null; + } } - else { - return (profileToken_.getTimeToExpiration() < refreshThreshold_); + + /** + * Determines if it is time to refresh the profile token. This is decided by comparing the time left until the + * profile token expires, and the refresh threshold. + * + * @return true if the profile token needs to be refreshed, false if it does not. + * + * @throws AS400SecurityException If an IBM i system security or authentication error occurs + */ + private boolean isTimeForRefresh() throws AS400SecurityException + { + return ((refreshThreshold_ == REFRESH_TOKEN_EVERY_TIME) + || (profileToken_ == null) + || (profileToken_.getTimeToExpiration() < refreshThreshold_)); } - } } diff --git a/src/main/java/com/ibm/as400/access/MessageQueue.java b/src/main/java/com/ibm/as400/access/MessageQueue.java index 50c7213b8..a46339ea9 100644 --- a/src/main/java/com/ibm/as400/access/MessageQueue.java +++ b/src/main/java/com/ibm/as400/access/MessageQueue.java @@ -862,7 +862,7 @@ public synchronized void load() throws AS400SecurityException, ErrorCompletingRe }; // Call the program. - ProgramCall pc = new ProgramCall(system_, "/QSYS.LIB/QGY.LIB/QGYOLMSG.PGM", parameters); // not a threadsafe API + ProgramCall pc = new ProgramCall(system_, "/QSYS.LIB/QGYOLMSG.PGM", parameters); // not a threadsafe API if (!pc.run()) { throw new AS400Exception(pc.getMessageList()); diff --git a/src/main/java/com/ibm/as400/access/ObjectList.java b/src/main/java/com/ibm/as400/access/ObjectList.java index d363a5883..02b204b78 100644 --- a/src/main/java/com/ibm/as400/access/ObjectList.java +++ b/src/main/java/com/ibm/as400/access/ObjectList.java @@ -1393,7 +1393,7 @@ public synchronized void load() throws AS400Exception, AS400SecurityException, E } // Call the program - ProgramCall pc = new ProgramCall(system_, "/QSYS.LIB/QGY.LIB/QGYOLOBJ.PGM", parms); + ProgramCall pc = new ProgramCall(system_, "/QSYS.LIB/QGYOLOBJ.PGM", parms); if (!pc.run()) { throw new AS400Exception(pc.getMessageList()); diff --git a/src/main/java/com/ibm/as400/access/PortMapper.java b/src/main/java/com/ibm/as400/access/PortMapper.java index ab46386d4..7ee3daed1 100644 --- a/src/main/java/com/ibm/as400/access/PortMapper.java +++ b/src/main/java/com/ibm/as400/access/PortMapper.java @@ -178,6 +178,12 @@ static SocketContainer getServerSocket(String systemName, // Now we construct and send a "port map" request to get the port number for the requested service... String fullServiceName = (useSSL != null && useSSL.proxyEncryptionMode_ != SecureAS400.CLIENT_TO_PROXY_SERVER) ? serviceName + "-s" : serviceName; + + // since no non-TLS as-hostcnn server, ensure that it is always as-hostcnn-s. + // This is purely a precautionary step. + if (fullServiceName.equals("as-hostcnn")) + fullServiceName += "-s"; + AS400PortMapDS pmreq = new AS400PortMapDS(fullServiceName); if (Trace.traceOn_) pmreq.setConnectionID(pmSocket.hashCode()); pmreq.write(pmOutstream); diff --git a/src/main/java/com/ibm/as400/access/ProfileHandleImplNative.java b/src/main/java/com/ibm/as400/access/ProfileHandleImplNative.java index fbd95b6f6..6e4f72b6a 100644 --- a/src/main/java/com/ibm/as400/access/ProfileHandleImplNative.java +++ b/src/main/java/com/ibm/as400/access/ProfileHandleImplNative.java @@ -16,32 +16,36 @@ import com.ibm.as400.security.auth.*; /** - The ProfileHandleImplNative class provides an implementation for behavior delegated by a ProfileHandleCredential object. + * The ProfileHandleImplNative class provides an implementation for behavior delegated by a ProfileHandleCredential + * object. **/ public class ProfileHandleImplNative implements ProfileHandleImpl { - // Note: This class needs to be public, because it's referenced by class com.ibm.as400.security.auth.ProfileHandleCredential - private static final String CLASSNAME = "com.ibm.as400.access.ProfileHandleImplNative"; - static - { - if (Trace.traceOn_) Trace.logLoadPath(CLASSNAME); - } + // Note: This class needs to be public, because it's referenced by class + // com.ibm.as400.security.auth.ProfileHandleCredential + private static final String CLASSNAME = "com.ibm.as400.access.ProfileHandleImplNative"; + static { + if (Trace.traceOn_) + Trace.logLoadPath(CLASSNAME); + } private AS400Credential credential_ = null; - static - { - NativeMethods.loadNativeLibraryQyjspart(); + static { + NativeMethods.loadNativeLibraryQyjspart(); } /** - Destroy or clear sensitive information maintained by the credential implementation. -

Subsequent requests may result in a NullPointerException. -

Subclasses should override as necessary to destroy or clear class-specific data. - @exception DestroyFailedException If errors occur while destroying or clearing credential implementation data. + * Destroy or clear sensitive information maintained by the credential implementation. + *

+ * Subsequent requests may result in a NullPointerException. + *

+ * Subclasses should override as necessary to destroy or clear class-specific data. + * + * @exception DestroyFailedException If errors occur while destroying or clearing credential implementation data. **/ public void destroy() throws DestroyFailedException { - releaseHandle(((ProfileHandleCredential)getCredential()).getHandle()); + releaseHandle(((ProfileHandleCredential) getCredential()).getHandle()); credential_ = null; if (Trace.isTraceOn()) Trace.log(Trace.INFORMATION, "Credential implementation destroyed >> " + toString()); } @@ -53,18 +57,17 @@ AS400Credential getCredential() return credential_; } - /** - Generates and returns a profile handle based on the current thread identity. - @return The handle bytes. - @exception RetrieveFailedException If errors occur while generating the handle. - **/ + @Override public native byte[] getCurrentHandle() throws RetrieveFailedException; /** - Returns the number of seconds before the credential is due to expire. -

Subclasses implementing timed credentials must override. - @return The number of seconds before expiration; zero (0) if already expired or if the credential is not identified as expiring based on time. - @exception RetrieveFailedException If errors occur while retrieving timeout information. + * Returns the number of seconds before the credential is due to expire. + *

+ * Subclasses implementing timed credentials must override. + * + * @return The number of seconds before expiration; zero (0) if already expired or if the credential is not + * identified as expiring based on time. + * @exception RetrieveFailedException If errors occur while retrieving timeout information. **/ public int getTimeToExpiration() throws RetrieveFailedException { @@ -72,21 +75,13 @@ public int getTimeToExpiration() throws RetrieveFailedException return 0; } - /** - Returns the version number for the implementation. -

Used to ensure the implementation is valid for specific functions. - @return The version number. - **/ + @Override public int getVersion() { return 1; // mod 3 } - /** - Indicates if the credential is still considered valid for authenticating to associated services or performing related actions. -

An exception is not thrown on failure to remain consistent with the Refreshable interface (even though some credential classes currently avoid the dependency established by implementing the interface). - @return true if valid; false if not valid or if the operation fails. - **/ + @Override public boolean isCurrent() { try @@ -100,26 +95,21 @@ public boolean isCurrent() } } - /** - Updates or extends the validity period for the credential. - @exception RefreshFailedException If the refresh attempt fails. - **/ + @Override public void refresh() throws RefreshFailedException { // Never called; credential is not renewable } /** - Releases OS resources for the given profile handle. - @param handle The handle bytes. - @exception DestroyFailedException If errors occur while releasing the handle. + * Releases OS resources for the given profile handle. + * + * @param handle The handle bytes. + * @exception DestroyFailedException If errors occur while releasing the handle. **/ public native void releaseHandle(byte[] handle) throws DestroyFailedException; - /** - Sets the credential delegating behavior to the implementation object. - @param credential The associated credential. - **/ + @Override public void setCredential(AS400Credential credential) { if (credential == null) @@ -131,19 +121,14 @@ public void setCredential(AS400Credential credential) } /** - Sets the current thread identity based on the given profile handle. - @param handle The handle bytes. - @exception SwapFailedException If errors occur while generating the handle. + * Sets the current thread identity based on the given profile handle. + * + * @param handle The handle bytes. + * @exception SwapFailedException If errors occur while generating the handle. **/ public native void setCurrentHandle(byte[] handle) throws SwapFailedException; - /** - Attempts to swap the thread identity based on this credential. - @param genRtnCr Indicates whether a return credential should be generated, even if supported. When appropriate, not generating a return credential can improve performance and avoid potential problems in creating the credential. - @return A credential capable of swapping back to the original identity; classes not supporting this capability will return null. This value will also be null if genRtnCr is false. - @exception SwapFailedException If errors occur while swapping thread identity. - @exception SecurityException If the caller does not have permission to modify the OS thread identity. - **/ + @Override public AS400Credential swap(boolean genRtnCr) throws SwapFailedException { setCurrentHandle(((ProfileHandleCredential)getCredential()).getHandle()); diff --git a/src/main/java/com/ibm/as400/access/ProfileTokenImplNative.java b/src/main/java/com/ibm/as400/access/ProfileTokenImplNative.java index fb9be876c..833fd413d 100644 --- a/src/main/java/com/ibm/as400/access/ProfileTokenImplNative.java +++ b/src/main/java/com/ibm/as400/access/ProfileTokenImplNative.java @@ -14,6 +14,7 @@ package com.ibm.as400.access; +import java.beans.PropertyVetoException; import java.io.IOException; import com.ibm.as400.security.auth.*; @@ -120,6 +121,13 @@ public byte[] generateToken(String uid, char[] pwd, int type, int timeoutInterva @Override public byte[] generateToken(String uid, int pwdSpecialValue, int type, int timeoutInterval) throws RetrieveFailedException { + return generateToken(uid, pwdSpecialValue, null, AuthenticationIndicator.APPLICATION_AUTHENTICATION, null, null, 0, null, 0, type, timeoutInterval); + } + + private byte[] generateToken(String uid, int pwdSpecialValue, char[] additionalAuthenticationFactor, int authenticationIndicator, + String verificationId, String remoteIpAddress, int remotePort, String localIpAddress, int localPort, + int type, int timeoutInterval) throws RetrieveFailedException + { // Convert password special value from int to string String pwdSpecialVal; switch(pwdSpecialValue) @@ -134,11 +142,53 @@ public byte[] generateToken(String uid, int pwdSpecialValue, int type, int timeo Trace.log(Trace.ERROR, "Password special value = " + pwdSpecialValue + " is not valid."); throw new ExtendedIllegalArgumentException("password special value", ExtendedIllegalArgumentException.PARAMETER_VALUE_NOT_VALID); } + + if (Trace.isTraceOn()) Trace.log(Trace.DIAGNOSTIC, "ProfileTokenImplNative generating profile token w/special value for user: " + uid); + // Call native method and return token bytes, we rely on the fact this class is only called if running on AS400. - return nativeCreateTokenChar(uid.toUpperCase(), pwdSpecialVal.toCharArray(), type, timeoutInterval); + if (!ProfileTokenCredential.useEnhancedProfileTokens() || AS400.nativeVRM.getVersionReleaseModification() <= 0x00070500) + return nativeCreateTokenChar(uid.toUpperCase(), pwdSpecialVal.toCharArray(), type, timeoutInterval); + + return EnhancedProfileTokenImplNative.nativeCreateTokenSpecialPassword(uid.toUpperCase(), pwdSpecialVal.toCharArray(), + additionalAuthenticationFactor, authenticationIndicator, verificationId, remoteIpAddress, remotePort, localIpAddress, localPort, + type, timeoutInterval); } + @Override + public ProfileTokenCredential generateToken(String uid, int pwdSpecialValue, ProfileTokenCredential profileTokenCred) + throws RetrieveFailedException, PropertyVetoException + { + byte[] token = generateToken(uid, pwdSpecialValue, + profileTokenCred.getAdditionalAuthenticationFactor(), + profileTokenCred.getAuthenticationIndicator(), + profileTokenCred.getVerificationID(), + profileTokenCred.getRemoteIPAddress(), + profileTokenCred.getRemotePort(), + profileTokenCred.getLocalIPAddress(), + profileTokenCred.getLocalPort(), + profileTokenCred.getTokenType(), + profileTokenCred.getTimeoutInterval()); + + try { + profileTokenCred.setToken(token); + profileTokenCred.setTokenCreator(ProfileTokenCredential.CREATOR_NATIVE_API); + } + catch (PropertyVetoException e) + { + try { + nativeRemoveFromSystem(token); + credential_ = null; + } catch (DestroyFailedException e1) { + Trace.log(Trace.ERROR, "Unexpected Exception during profile token destroy: ", e); + } + + throw e; + } + + return profileTokenCred; + } + /** * Generates and returns a new profile token based on * the provided information using a password string. @@ -188,10 +238,40 @@ public byte[] generateTokenExtended(String uid, String pwd, int type, int timeou @Override public byte[] generateTokenExtended(String uid, char [] pwd, int type, int timeoutInterval) throws RetrieveFailedException { + return generateTokenExtended(uid, pwd, null, null, null, 0, null, 0, type, timeoutInterval); + } + + private byte[] generateTokenExtended(String uid, char[] pwd, char[] additionalAuthenticationFactor, + String verificationId, String remoteIpAddress, int remotePort, String localIpAddress, int localPort, + int type, int timeoutInterval) throws RetrieveFailedException + { AS400 sys = getCredential().getSystem(); + + // Determine if we are using enhanced profile tokens + boolean useEPT = false; + try { + useEPT = (ProfileTokenCredential.useEnhancedProfileTokens() && sys.getVRM() > 0x00070500); + } + catch (AS400SecurityException|IOException e) { + Trace.log(Trace.ERROR, "Unexpected Exception: ", e); + throw new RetrieveFailedException(); + } + + // The API QSYGENPT requires all parameters to be non-null. + boolean isAAFNull = (additionalAuthenticationFactor == null || additionalAuthenticationFactor.length == 0); + if (isAAFNull) additionalAuthenticationFactor = new char[] { ' ' }; + + boolean isVfyIDNull = (verificationId == null || verificationId.length() == 0); + if (isVfyIDNull) verificationId = " "; + + boolean isRemoteIPNull = (remoteIpAddress == null || remoteIpAddress.length() == 0); + if (isRemoteIPNull) remoteIpAddress = " "; + + boolean isLocalIPNull = (localIpAddress == null || localIpAddress.length() == 0); + if (isLocalIPNull) localIpAddress = " "; // Setup parameters - ProgramParameter[] parmlist = new ProgramParameter[8]; + ProgramParameter[] parmlist = new ProgramParameter[useEPT ? 19 : 8]; // Output: Profile token. parmlist[0] = new ProgramParameter(ProfileTokenCredential.TOKEN_LENGTH); @@ -221,6 +301,43 @@ public byte[] generateTokenExtended(String uid, char [] pwd, int type, int timeo // Input: CCSID of user password. Int to byte[]. Unicode = 13488. parmlist[7] = new ProgramParameter(BinaryConverter.intToByteArray(13488)); + + // If enhanced profile tokens supported then set parameters + if (useEPT) + { + // Input: Additional authentication factor (unicode) + parmlist[8] = new ProgramParameter(BinaryConverter.charArrayToByteArray(additionalAuthenticationFactor)); + + // Input: Length of additional authentication factor + parmlist[9] = new ProgramParameter(BinaryConverter.intToByteArray((isAAFNull) ? 0 : parmlist[8].getInputData().length)); + + // Input: CCSID of additional authentication factor + parmlist[10] = new ProgramParameter(BinaryConverter.intToByteArray(13488)); + + // Input: Authentication indicator (for passwords, it is ignored) + parmlist[11] = new ProgramParameter(BinaryConverter.intToByteArray(0)); + + // Input: Verification ID - must be 30 in length, blank padded + parmlist[12] = new ProgramParameter(CharConverter.stringToByteArray(sys, (verificationId + " ").substring(0, 30))); + + // Input: Remote IP address + parmlist[13] = new ProgramParameter(CharConverter.stringToByteArray(sys, remoteIpAddress)); + + // Input: Length of remote IP address + parmlist[14] = new ProgramParameter(BinaryConverter.intToByteArray((isRemoteIPNull) ? 0 : parmlist[13].getInputData().length)); + + // Input: Remote port + parmlist[15] = new ProgramParameter(BinaryConverter.intToByteArray(remotePort)); + + // Input: Local IP address + parmlist[16] = new ProgramParameter(CharConverter.stringToByteArray(sys, localIpAddress)); + + // Input: Length of local IP address + parmlist[17] = new ProgramParameter(BinaryConverter.intToByteArray((isLocalIPNull) ? 0 : parmlist[16].getInputData().length)); + + // Input: Local port + parmlist[18] = new ProgramParameter(BinaryConverter.intToByteArray(remotePort)); + } ProgramCall programCall = new ProgramCall(sys); @@ -248,6 +365,39 @@ public byte[] generateTokenExtended(String uid, char [] pwd, int type, int timeo return parmlist[0].getOutputData(); } + + @Override + public ProfileTokenCredential generateTokenExtended(String uid, char[] password, + ProfileTokenCredential profileTokenCred) throws RetrieveFailedException, PropertyVetoException + { + byte[] token = generateTokenExtended(uid, password, + profileTokenCred.getAdditionalAuthenticationFactor(), + profileTokenCred.getVerificationID(), + profileTokenCred.getRemoteIPAddress(), + profileTokenCred.getRemotePort(), + profileTokenCred.getLocalIPAddress(), + profileTokenCred.getLocalPort(), + profileTokenCred.getTokenType(), + profileTokenCred.getTimeoutInterval()); + + try { + profileTokenCred.setToken(token); + profileTokenCred.setTokenCreator(ProfileTokenCredential.CREATOR_NATIVE_API); + } + catch (PropertyVetoException e) + { + try { + nativeRemoveFromSystem(token); + credential_ = null; + } catch (DestroyFailedException e1) { + Trace.log(Trace.ERROR, "Unexpected Exception during profile token destroy: ", e); + } + + throw e; + } + + return profileTokenCred; + } // Returns the credential delegating behavior to the implementation // object. @@ -391,13 +541,29 @@ public void refresh() throws RefreshFailedException { } @Override - public byte[] refresh(int type, int timeoutInterval) throws RefreshFailedException { - byte[] token = ((ProfileTokenCredential)getCredential()).getToken(); + public byte[] refresh(int type, int timeoutInterval) throws RefreshFailedException + { + ProfileTokenCredential pt = ((ProfileTokenCredential)getCredential()); + + byte[] token = pt.getToken(); // native method will overwrite bytes passed in; create a copy // to manipulate. byte[] bytes = new byte[ProfileTokenCredential.TOKEN_LENGTH]; System.arraycopy(token, 0, bytes, 0, bytes.length); - nativeRefreshToken(bytes, type, timeoutInterval); + + if (!ProfileTokenCredential.useEnhancedProfileTokens() || AS400.nativeVRM.getVersionReleaseModification() <= 0x00070500) + nativeRefreshToken(bytes, type, timeoutInterval); + else + { + // TODO AMRA - need to change native to throw proper exception + try { + EnhancedProfileTokenImplNative.nativeCreateTokenFromToken(bytes, pt.getVerificationID(), pt.getRemoteIPAddress(), type, timeoutInterval); + } catch (RetrieveFailedException e) { + Trace.log(Trace.ERROR, "Unexpected Exception: ", e); + throw new InternalErrorException(InternalErrorException.UNEXPECTED_EXCEPTION); + } + } + return bytes; } diff --git a/src/main/java/com/ibm/as400/access/ProfileTokenVault.java b/src/main/java/com/ibm/as400/access/ProfileTokenVault.java index 67dfd4107..b70deebfe 100644 --- a/src/main/java/com/ibm/as400/access/ProfileTokenVault.java +++ b/src/main/java/com/ibm/as400/access/ProfileTokenVault.java @@ -79,6 +79,7 @@ protected ProfileTokenVault(ProfileTokenCredential existingCredential) public ProfileTokenVault clone() { ProfileTokenVault vaultClone = (ProfileTokenVault)super.clone(); + vaultClone.profileTokenCredential_ = profileTokenCredential_; return vaultClone; } diff --git a/src/main/java/com/ibm/as400/access/ProgramCall.java b/src/main/java/com/ibm/as400/access/ProgramCall.java index 2008a211d..1eef87d69 100644 --- a/src/main/java/com/ibm/as400/access/ProgramCall.java +++ b/src/main/java/com/ibm/as400/access/ProgramCall.java @@ -216,14 +216,7 @@ public void cancel() { if (Trace.traceOn_) Trace.log(Trace.INFORMATION, this, "Cancelling program " + this.getProgram()); //@L8 try { - //Job job = new Job (new AS400(this.system_), job_.getName(), job_.getUser(), job_.getNumber()); @AB4D - //@AB4A - Job job = null; - if (this.system_ instanceof SecureAS400) { - job = new Job (new SecureAS400(this.system_), job_.getName(), job_.getUser(), job_.getNumber()); - } else { - job = new Job (new AS400(this.system_), job_.getName(), job_.getUser(), job_.getNumber()); - } + Job job = new Job (AS400.newInstance(this.system_.isSecure(), this.system_), job_.getName(), job_.getUser(), job_.getNumber()); job.end(0); } catch (Exception e) { // Do nothing diff --git a/src/main/java/com/ibm/as400/access/SignonGenAuthTokenRequestDS.java b/src/main/java/com/ibm/as400/access/SignonGenAuthTokenRequestDS.java index 307523f44..12a759710 100644 --- a/src/main/java/com/ibm/as400/access/SignonGenAuthTokenRequestDS.java +++ b/src/main/java/com/ibm/as400/access/SignonGenAuthTokenRequestDS.java @@ -19,9 +19,14 @@ // The SignonGenAuthTokenRequestDS class represents the data stream for the 'Generate authentication token on behalf of another user' request. class SignonGenAuthTokenRequestDS extends ClientAccessDataStream { - SignonGenAuthTokenRequestDS(byte[] userIdentity, int profileTokenType, int profileTokenTimeout, int serverLevel) + SignonGenAuthTokenRequestDS(byte[] userIdentity, int profileTokenType, int profileTokenTimeout, int serverLevel, + byte[] verificationID, byte[] clientIPAddr) { - super(new byte[51 + userIdentity.length + (serverLevel < 5 ? 0 : 7)]); + super(new byte[51 + userIdentity.length + + (serverLevel < 5 ? 0 : 7) + + + ((serverLevel >= 18 && null != verificationID && 0 < verificationID.length) ? verificationID.length + 10: 0) + + ((serverLevel >= 18 && null != clientIPAddr && 0 < clientIPAddr.length) ? clientIPAddr.length + 10: 0) + ]); setLength(data_.length); // setHeaderID(0x0000); @@ -57,9 +62,10 @@ class SignonGenAuthTokenRequestDS extends ClientAccessDataStream // Data. System.arraycopy(userIdentity, 0, data_, 51, userIdentity.length); + int offset = 51 + userIdentity.length; + if (serverLevel >= 5) { - int offset = 51 + userIdentity.length; // Set return error messages. // LL set32bit(7, offset); @@ -67,6 +73,39 @@ class SignonGenAuthTokenRequestDS extends ClientAccessDataStream set16bit(0x1128, offset + 4); // Data. data_[offset + 6] = 0x01; + + offset += 7; + } + + if (serverLevel >= 18) + { + if (null != verificationID && 0 < verificationID.length) + { + // LL + set32bit(verificationID.length + 4 + 2 + 4, offset); + // CP + set16bit(0x1130, offset + 4); + // CCSID + set32bit(1208, offset + 6); + // data + System.arraycopy(verificationID, 0, data_, offset + 10, verificationID.length); + + offset += 10 + verificationID.length; + } + + if (null != clientIPAddr && 0 < clientIPAddr.length) + { + // LL + set32bit(clientIPAddr.length + 4 + 2 + 4, offset); + // CP + set16bit(0x1131, offset + 4); + // CCSID + set32bit(1208, offset + 6); + // data + System.arraycopy(clientIPAddr, 0, data_, offset + 10, clientIPAddr.length); + + offset += 10 + clientIPAddr.length; + } } } diff --git a/src/main/java/com/ibm/as400/access/SocketContainer.java b/src/main/java/com/ibm/as400/access/SocketContainer.java index 93b86087c..93396eeec 100644 --- a/src/main/java/com/ibm/as400/access/SocketContainer.java +++ b/src/main/java/com/ibm/as400/access/SocketContainer.java @@ -27,4 +27,5 @@ abstract class SocketContainer abstract OutputStream getOutputStream() throws IOException; abstract void setSoTimeout(int timeout) throws SocketException; abstract int getSoTimeout() throws SocketException; + abstract String getLocalAddress(); } diff --git a/src/main/java/com/ibm/as400/access/SocketContainerInet.java b/src/main/java/com/ibm/as400/access/SocketContainerInet.java index f9106ba93..16ded7f91 100644 --- a/src/main/java/com/ibm/as400/access/SocketContainerInet.java +++ b/src/main/java/com/ibm/as400/access/SocketContainerInet.java @@ -23,31 +23,42 @@ class SocketContainerInet extends SocketContainer { Socket socket_; + @Override void setProperties(Socket socket, String serviceName, String systemName, int port, SSLOptions options) throws IOException { socket_ = socket; } + @Override void close() throws IOException { socket_.close(); } + @Override InputStream getInputStream() throws IOException { return socket_.getInputStream(); } + @Override OutputStream getOutputStream() throws IOException { return socket_.getOutputStream(); } - + + @Override int getSoTimeout() throws SocketException { return socket_.getSoTimeout(); } + @Override void setSoTimeout(int timeout) throws SocketException { socket_.setSoTimeout(timeout); } + + @Override + String getLocalAddress() { + return socket_.getLocalAddress().getHostAddress(); + } } diff --git a/src/main/java/com/ibm/as400/access/SocketContainerJSSE.java b/src/main/java/com/ibm/as400/access/SocketContainerJSSE.java index 03b3042a6..140ebb5dd 100644 --- a/src/main/java/com/ibm/as400/access/SocketContainerJSSE.java +++ b/src/main/java/com/ibm/as400/access/SocketContainerJSSE.java @@ -98,5 +98,9 @@ int getSoTimeout() throws SocketException { void setSoTimeout(int timeout) throws SocketException { sslSocket_.setSoTimeout(timeout); } - + + @Override + String getLocalAddress() { + return sslSocket_.getLocalAddress().getHostAddress(); + } } diff --git a/src/main/java/com/ibm/as400/access/SocketContainerUnix.java b/src/main/java/com/ibm/as400/access/SocketContainerUnix.java index 690e321a7..ee46bb73a 100644 --- a/src/main/java/com/ibm/as400/access/SocketContainerUnix.java +++ b/src/main/java/com/ibm/as400/access/SocketContainerUnix.java @@ -23,7 +23,7 @@ class SocketContainerUnix extends SocketContainer { private UnixSocket usocket_; - + @Override void setProperties(Socket socket, String serviceName, String systemName, int port, SSLOptions options) throws IOException { int serverNumber = 0; @@ -55,29 +55,35 @@ else if (serviceName.equalsIgnoreCase("as-ddm")) usocket_ = new UnixSocket(serverNumber); } + @Override void close() throws IOException { usocket_.close(); } + @Override InputStream getInputStream() throws IOException { return usocket_.getInputStream(); } - OutputStream getOutputStream() throws IOException - { + @Override + OutputStream getOutputStream() throws IOException { return usocket_.getOutputStream(); - } - + @Override int getSoTimeout() throws SocketException { return usocket_.getSoTimeout(); } + @Override void setSoTimeout(int timeout) throws SocketException { usocket_.setSoTimeout(timeout); } - + + @Override + String getLocalAddress() { + return "127.0.0.1"; + } } diff --git a/src/main/java/com/ibm/as400/access/SocketContainerUnix2.java b/src/main/java/com/ibm/as400/access/SocketContainerUnix2.java index 6795b3f3b..3b86c2dee 100644 --- a/src/main/java/com/ibm/as400/access/SocketContainerUnix2.java +++ b/src/main/java/com/ibm/as400/access/SocketContainerUnix2.java @@ -26,6 +26,7 @@ class SocketContainerUnix2 extends SocketContainer private Object lock_ = new Object(); private int timeout_ = 0; /* timeout in milliseconds */ + @Override void setProperties(Socket socket, String serviceName, String systemName, int port, SSLOptions options) throws IOException { int serverNumber = 0; @@ -61,6 +62,7 @@ else if (serviceName.equalsIgnoreCase("as-ddm")) { if (Trace.traceOn_) Trace.log(Trace.WARNING, "Unrecognized serviceName: " + serviceName + ". Defaulting to as-central"); } + try { /* A little background: From jt400Native.jar (NativeMethod.java) @@ -137,66 +139,69 @@ If socketPaseCreate() fails, we cannot just move on down to the socketCreate() } } + @Override void close() throws IOException { - if (!closed_) + if (closed_) + return; + + try { - try + boolean paseCallSucceeded = false; + if(NativeMethods.paseLibLoaded) { - boolean paseCallSucceeded = false; - if(NativeMethods.paseLibLoaded) - { - try{ - if (sd_.length < 2) { - if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Descriptor is not paired:", (sd_.length == 0 ? "null" : Integer.toString(sd_[0]))); - throw new Throwable(); - } - if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Calling NativeMethods.socketPaseClose("+Integer.toString(sd_[0])+")"); - - NativeMethods.socketPaseClose(sd_[0], sd_[1]); - paseCallSucceeded = true; - } - catch(NativeException ne){ - // Got here because of actual exception calling 'close' on host. - if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "NativeException while calling NativeMethods.socketPaseClose("+Integer.toString(sd_[0])+","+Integer.toString(sd_[1])+")"); - throw ne; - } - catch(UnsatisfiedLinkError e){ - // Probably got here because using new jt400Native.jar with old qyjspaseXX.so, - // so we just call the generic-named method in qyjspaseXX.so - if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "UnsatisfiedLinkError while calling NativeMethods.socketPaseClose("+Integer.toString(sd_[0])+","+Integer.toString(sd_[1])+")"); - } - catch(Throwable e){ - // Probably got here because using new jt400Native.jar with old qyjspaseXX.so, - // so we just call the generic-named method in qyjspaseXX.so - if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Throwable while calling NativeMethods.socketPaseClose("+Integer.toString(sd_[0])+","+Integer.toString(sd_[1])+")", e); + try{ + if (sd_.length < 2) { + if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Descriptor is not paired:", (sd_.length == 0 ? "null" : Integer.toString(sd_[0]))); + throw new Throwable(); } - } - - if (!paseCallSucceeded) // try calling the generic-named method - { - if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Calling NativeMethods.socketClose("+Integer.toString(sd_[0])+")"); + if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Calling NativeMethods.socketPaseClose("+Integer.toString(sd_[0])+")"); - NativeMethods.socketClose(sd_[0]); + NativeMethods.socketPaseClose(sd_[0], sd_[1]); + paseCallSucceeded = true; + } + catch(NativeException ne){ + // Got here because of actual exception calling 'close' on host. + if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "NativeException while calling NativeMethods.socketPaseClose("+Integer.toString(sd_[0])+","+Integer.toString(sd_[1])+")"); + throw ne; + } + catch(UnsatisfiedLinkError e){ + // Probably got here because using new jt400Native.jar with old qyjspaseXX.so, + // so we just call the generic-named method in qyjspaseXX.so + if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "UnsatisfiedLinkError while calling NativeMethods.socketPaseClose("+Integer.toString(sd_[0])+","+Integer.toString(sd_[1])+")"); + } + catch(Throwable e){ + // Probably got here because using new jt400Native.jar with old qyjspaseXX.so, + // so we just call the generic-named method in qyjspaseXX.so + if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Throwable while calling NativeMethods.socketPaseClose("+Integer.toString(sd_[0])+","+Integer.toString(sd_[1])+")", e); } } - catch (NativeException e) - { - throw createSocketException(e); - } - finally //@socket2 + + if (!paseCallSucceeded) // try calling the generic-named method { - sd_ = null; //@socket2 - closed_ = true; //@socket2 //add this so if close fails, we don't keep trying to close a broken socket + if (Trace.traceOn_) Trace.log(Trace.DIAGNOSTIC, "Calling NativeMethods.socketClose("+Integer.toString(sd_[0])+")"); + + NativeMethods.socketClose(sd_[0]); } } + catch (NativeException e) + { + throw createSocketException(e); + } + finally //@socket2 + { + sd_ = null; //@socket2 + closed_ = true; //@socket2 //add this so if close fails, we don't keep trying to close a broken socket + } } + @Override protected void finalize() throws IOException { close(); } + @Override InputStream getInputStream() throws IOException { synchronized (lock_) @@ -205,6 +210,7 @@ InputStream getInputStream() throws IOException } } + @Override OutputStream getOutputStream() throws IOException { synchronized (lock_) @@ -468,12 +474,18 @@ public void close() throws IOException } } + @Override int getSoTimeout() throws SocketException { return timeout_; } + @Override void setSoTimeout(int timeout) throws SocketException { timeout_ = timeout; - + } + + @Override + String getLocalAddress() { + return "127.0.0.1"; } } diff --git a/src/main/java/com/ibm/as400/access/UserList.java b/src/main/java/com/ibm/as400/access/UserList.java index 9e12ee388..2b2e1fa0f 100644 --- a/src/main/java/com/ibm/as400/access/UserList.java +++ b/src/main/java/com/ibm/as400/access/UserList.java @@ -513,7 +513,7 @@ public synchronized void load() throws AS400SecurityException, ErrorCompletingRe }; // Call the program. - ProgramCall pc = new ProgramCall(system_, "/QSYS.LIB/QGY.LIB/QGYOLAUS.PGM", parameters); + ProgramCall pc = new ProgramCall(system_, "/QSYS.LIB/QGYOLAUS.PGM", parameters); if (!pc.run()) { throw new AS400Exception(pc.getMessageList()); diff --git a/src/main/java/com/ibm/as400/access/ValidationList.java b/src/main/java/com/ibm/as400/access/ValidationList.java index de0613219..53f381e65 100644 --- a/src/main/java/com/ibm/as400/access/ValidationList.java +++ b/src/main/java/com/ibm/as400/access/ValidationList.java @@ -155,7 +155,7 @@ private void closeList(byte[] handle) throws PersistenceException { ProgramCall pgm = new ProgramCall(getAS400()); ProgramParameter[] parmList = new ProgramParameter[2]; try { - pgm.setProgram(QSYSObjectPathName.toPath("QGY", "QGYCLST", "PGM"), parmList); + pgm.setProgram(QSYSObjectPathName.toPath("QSYS", "QGYCLST", "PGM"), parmList); } catch (PropertyVetoException pve) { Trace.log(Trace.ERROR, pve); } // Refer to documentation for the QGYCLST generic list API for a complete description of parameters @@ -322,7 +322,7 @@ public ValidationListEntry[] getEntries() throws PersistenceException { ProgramParameter[] parmList = new ProgramParameter[7]; int bufferLength = LISTBUFFER_LENGTH_INITIAL; try { - pgm.setProgram(QSYSObjectPathName.toPath("QGY", "QSYOLVLE", "PGM"), parmList); + pgm.setProgram(QSYSObjectPathName.toPath("QSYS", "QSYOLVLE", "PGM"), parmList); } catch (PropertyVetoException pve) { Trace.log(Trace.ERROR, pve); } // Refer to documentation for the QSYOLVLE Security API for a complete description of parameters @@ -420,7 +420,7 @@ private int getNextEntries(byte[] listHandle, int listPosition, ValidationListEn // Try & retrieve all remaining entries in a single call; keeps it simple for now int bufferLength = LISTBUFFER_LENGTH_NEXT; try { - pgm.setProgram(QSYSObjectPathName.toPath("QGY", "QGYGTLE", "PGM"), parmList); + pgm.setProgram(QSYSObjectPathName.toPath("QSYS", "QGYGTLE", "PGM"), parmList); } catch (PropertyVetoException pve) { Trace.log(Trace.ERROR, pve); } // Refer to documentation for the QGYGTLE generic list API for a complete description of parameters @@ -451,7 +451,7 @@ public int getNumberOfEntries() throws PersistenceException { ProgramParameter[] parmList = new ProgramParameter[7]; int bufferLength = 512; try { - pgm.setProgram(QSYSObjectPathName.toPath("QGY", "QSYOLVLE", "PGM"), parmList); + pgm.setProgram(QSYSObjectPathName.toPath("QSYS", "QSYOLVLE", "PGM"), parmList); } catch (PropertyVetoException pve) { Trace.log(Trace.ERROR, pve); } // Refer to documentation for the QSYOLVLE Security API for a complete description of parameters diff --git a/src/main/java/com/ibm/as400/access/list/AspOpenList.java b/src/main/java/com/ibm/as400/access/list/AspOpenList.java index 609fc7130..d15c213e8 100644 --- a/src/main/java/com/ibm/as400/access/list/AspOpenList.java +++ b/src/main/java/com/ibm/as400/access/list/AspOpenList.java @@ -270,7 +270,7 @@ protected byte[] callOpenListAPI() throws AS400SecurityException, ErrorCompletin //parameters[8] = new ProgramParameter(sortInformation) // Call the program. - ProgramCall pc = new ProgramCall(system_, "/QSYS.LIB/QGY.LIB/QYASPOL.PGM", parameters); + ProgramCall pc = new ProgramCall(system_, "/QSYS.LIB/QYASPOL.PGM", parameters); if (!pc.run()) { throw new AS400Exception(pc.getMessageList()); diff --git a/src/main/java/com/ibm/as400/access/list/OpenList.java b/src/main/java/com/ibm/as400/access/list/OpenList.java index 504399694..7d5051b08 100644 --- a/src/main/java/com/ibm/as400/access/list/OpenList.java +++ b/src/main/java/com/ibm/as400/access/list/OpenList.java @@ -163,7 +163,7 @@ public synchronized void close() throws AS400SecurityException, ErrorCompletingR new ProgramParameter(handle_), EMPTY_ERROR_CODE_PARM }; - ProgramCall pc = new ProgramCall(system_, "/QSYS.LIB/QGY.LIB/QGYCLST.PGM", parameters); + ProgramCall pc = new ProgramCall(system_, "/QSYS.LIB/QGYCLST.PGM", parameters); if (!pc.run()) { throw new AS400Exception(pc.getMessageList()); @@ -373,7 +373,7 @@ else if (listOffset + number > length_) EMPTY_ERROR_CODE_PARM }; - ProgramCall pc = new ProgramCall(system_, "/QSYS.LIB/QGY.LIB/QGYGTLE.PGM", parameters); + ProgramCall pc = new ProgramCall(system_, "/QSYS.LIB/QGYGTLE.PGM", parameters); byte[] listInformation = null; int recordsReturned = 0; diff --git a/src/main/java/com/ibm/as400/access/list/SpooledFileOpenList.java b/src/main/java/com/ibm/as400/access/list/SpooledFileOpenList.java index 75205063d..9346d831c 100644 --- a/src/main/java/com/ibm/as400/access/list/SpooledFileOpenList.java +++ b/src/main/java/com/ibm/as400/access/list/SpooledFileOpenList.java @@ -706,7 +706,7 @@ else if (filterCreationDateEnd_ == null) } // Call the program. - ProgramCall pc = new ProgramCall(system_, "/QSYS.LIB/QGY.LIB/QGYOLSPL.PGM", parameters); + ProgramCall pc = new ProgramCall(system_, "/QSYS.LIB/QGYOLSPL.PGM", parameters); if (!pc.run()) { throw new AS400Exception(pc.getMessageList()); diff --git a/src/main/java/com/ibm/as400/data/PcmlDocument.java b/src/main/java/com/ibm/as400/data/PcmlDocument.java index d0ccc2ef3..b90ff4b6c 100644 --- a/src/main/java/com/ibm/as400/data/PcmlDocument.java +++ b/src/main/java/com/ibm/as400/data/PcmlDocument.java @@ -590,6 +590,15 @@ synchronized AS400Message[] getMessageList(String name) throws PcmlException return getProgramNode(name).getMessageList(); } + /** + Returns the option for how many messages will be retrieved for the specified program. + @return A constant indicating how many messages will be retrieved. + **/ + synchronized int getMessageOption(String name) throws PcmlException + { + return getProgramNode(name).getMessageOption(); + } + /** Returns the ProgramCall object that was used in the most recent invocation of {@link #callProgram() callProgram()}. @return The ProgramCall object; null if callProgram has not been called. @@ -692,6 +701,18 @@ boolean getThreadsafeOverride(String program) // @C6A return getProgramNode(program).getThreadsafeOverride(); // @C6A } // @C6A + /** + Specifies the option for how many messages should be retrieved for the specified program. By default, to preserve + compatability, only the messages sent to the program caller and only up to ten messages are retrieved. + This property will only take effect on systems that support the new option. + @param messageOption A constant indicating how many messages to retrieve. + **/ + synchronized void setMessageOption(String program, int messageOption) + throws PcmlException + { + getProgramNode(program).setMessageOption(messageOption); + } + // Add a subtree to the document's hashtable. // This is called to complete the document cloneing process. void addToHashtable(PcmlDocNode newChild) // @C5A diff --git a/src/main/java/com/ibm/as400/data/PcmlProgram.java b/src/main/java/com/ibm/as400/data/PcmlProgram.java index 7c52d3315..265feaad1 100644 --- a/src/main/java/com/ibm/as400/data/PcmlProgram.java +++ b/src/main/java/com/ibm/as400/data/PcmlProgram.java @@ -15,6 +15,7 @@ import com.ibm.as400.access.AS400; import com.ibm.as400.access.AS400Message; +import com.ibm.as400.access.ExtendedIllegalArgumentException; import com.ibm.as400.access.ProgramCall; import com.ibm.as400.access.ServiceProgramCall; // @B1A import com.ibm.as400.access.ProgramParameter; @@ -91,6 +92,7 @@ class PcmlProgram extends PcmlDocNode private AS400Message[] msgList; // Array of AS400Message @C1C private int m_IntReturnValue; // Int return value for a service program call @B1A @C1C private int m_Errno; // Errno for a service program call @B1A @C1C + private int m_MessageOption; /** */ @@ -102,6 +104,7 @@ public PcmlProgram() m_IntReturnValue = 0; // @C1A m_Errno = 0; // @C1A m_ThreadsafeOverrideCalled = false; // @D2A + m_MessageOption = AS400Message.MESSAGE_OPTION_UP_TO_10; } // Constructor @@ -116,6 +119,7 @@ public PcmlProgram(PcmlAttributeList attrs) // @C3C m_IntReturnValue = 0; // @C1A m_Errno = 0; // @C1A m_ThreadsafeOverrideCalled = false; // @D2A + m_MessageOption = AS400Message.MESSAGE_OPTION_UP_TO_10; // ********************************** // Set attribute values @@ -682,6 +686,8 @@ else if (getEntrypoint() != null) // @D1A m_pgmCall.setThreadSafe(getThreadsafeOverride()); // @C6A } + m_pgmCall.setMessageOption(getMessageOption()); + // // Call the target program // @@ -692,11 +698,15 @@ else if (getEntrypoint() != null) // @D1A if (Trace.isTraceOn()) Trace.log(Trace.PCML, "Completed program call: " + m_pgmCall.getProgram()); // - // If the program signalled a message, save the message list. + // Save the message list in all cases + // + msgList = m_pgmCall.getMessageList(); + + // + // If the program signaled a message, stop processing here. // if (m_pgmRc != true) { - msgList = m_pgmCall.getMessageList(); return m_pgmRc; } @@ -883,6 +893,21 @@ AS400Message[] getMessageList() return msgList; } + int getMessageOption() + { + return m_MessageOption; + } + + void setMessageOption(int messageOption) + { + // Validate the messageOption parameter. + if (messageOption < 0 || messageOption > 2) + { + throw new ExtendedIllegalArgumentException("messageOption (" + messageOption + ")", ExtendedIllegalArgumentException.PARAMETER_VALUE_NOT_VALID); + } + m_MessageOption = messageOption; + } + /** Returns whether or not this element is defined as a service program entrypoint. diff --git a/src/main/java/com/ibm/as400/data/ProgramCallDocument.java b/src/main/java/com/ibm/as400/data/ProgramCallDocument.java index d4a258879..8e72b0ffb 100644 --- a/src/main/java/com/ibm/as400/data/ProgramCallDocument.java +++ b/src/main/java/com/ibm/as400/data/ProgramCallDocument.java @@ -790,6 +790,25 @@ public AS400Message[] getMessageList(String name) return m_pcmlDoc.getMessageList(name); } + /** + Returns the option for how many messages will be retrieved for the specified program. + + @param name The name of the <program> element in the PCML document. + @return A constant indicating how many messages will be retrieved. Valid values are: +

    +
  • {@link AS400Message#MESSAGE_OPTION_UP_TO_10 MESSAGE_OPTION_UP_TO_10} +
  • {@link AS400Message#MESSAGE_OPTION_NONE MESSAGE_OPTION_NONE} +
  • {@link AS400Message#MESSAGE_OPTION_ALL MESSAGE_OPTION_ALL} +
+ @exception PcmlException + If an error occurs. + **/ + public int getMessageOption(String name) + throws PcmlException + { + return m_pcmlDoc.getMessageOption(name); + } + /** Returns the number of bytes reserved for output for the named element. @@ -1351,6 +1370,27 @@ public boolean getThreadsafeOverride(String program) return m_pcmlDoc.getThreadsafeOverride(program); // @C6A } + /** + Specifies the option for how many messages should be retrieved for the specified program. By default, to preserve + compatability, only the messages sent to the program caller and only up to ten messages are retrieved. + This property will only take effect on systems that support the new option. + + @param program The name of the <program> element in the PCML document. + @param messageOption A constant indicating how many messages to retrieve. Valid values are: +
    +
  • AS400Message.MESSAGE_OPTION_UP_TO_10 +
  • AS400Message.MESSAGE_OPTION_NONE +
  • AS400Message.MESSAGE_OPTION_ALL +
+ @exception PcmlException + If an error occurs. + **/ + public void setMessageOption(String program, int messageOption) + throws PcmlException + { + m_pcmlDoc.setMessageOption(program, messageOption); + } + /** Saves a PcmlDocument as a serialized resource. @@ -2013,5 +2053,4 @@ public int getTimeout() { return m_timeOut; } //@Y6A End - } diff --git a/src/main/java/com/ibm/as400/security/SecurityMRI.java b/src/main/java/com/ibm/as400/security/SecurityMRI.java index 7aa52d0f5..992f83aa4 100644 --- a/src/main/java/com/ibm/as400/security/SecurityMRI.java +++ b/src/main/java/com/ibm/as400/security/SecurityMRI.java @@ -73,6 +73,9 @@ public class SecurityMRI extends ListResourceBundle // #TRANNOTE ##################################################### { "PROP_NAME_CR_PW_PASSWORD", "password" }, { "PROP_DESC_CR_PW_PASSWORD", "The password value." }, + + { "PROP_NAME_CR_PW_ADDITIONALAUTHENTICATIONFACTOR", "additionalAuthenticationFactor" }, + { "PROP_DESC_CR_PW_ADDITIONALAUTHENTICATIONFACTOR", "The additional authentication factor." }, // #TRANNOTE ##################################################### // #TRANNOTE Profile Handle credential properties. diff --git a/src/main/java/com/ibm/as400/security/auth/AS400BasicAuthenticationCredential.java b/src/main/java/com/ibm/as400/security/auth/AS400BasicAuthenticationCredential.java index b926f7cf8..81cae199c 100644 --- a/src/main/java/com/ibm/as400/security/auth/AS400BasicAuthenticationCredential.java +++ b/src/main/java/com/ibm/as400/security/auth/AS400BasicAuthenticationCredential.java @@ -87,6 +87,79 @@ public void initialize(AS400BasicAuthenticationPrincipal principal, String passw public void initialize(AS400BasicAuthenticationPrincipal principal, char[] password, boolean isPrivate, boolean isReusable, boolean isRenewable, int timeoutInterval) throws Exception; + /** + * Initializes and validates a credential for the local IBM i system. + * + * @param principal The principal identifying the authenticated + * user. + * + * @param password The password for the authenticated user. + * + * @param additionalAuthFactor The additional authentication factor for the + * user + * @param authenticationIndicator Indicates how the caller authenticated the + * user. Ignored for IBM i 7.5 and older + * releases. @see com.ibm.as400.access.AuthenticationIndicator + * + * @param verificationID The verification ID is the label that identifies the + * specific application, service, or action associated + * with the profile handle request. This value must be + * 30-characters or less. This value will be passed to + * the authentication exit program registered under the + * QIBM_QSY_AUTH exit point if the specified user profile + * has *REGFAC as an authentication method. The + * authentication exit program may use the verification + * ID as a means to restrict the use of the user profile. + * If running on an IBM i, the verification ID should be + * the DCM application ID or a similar value that + * identifies the application or service. Ignored for IBM + * i 7.5 and older releases. + * + * @param remoteIPAddress If the API is used by a server to provide access to a + * the system, the remote IP address should be obtained + * from the socket connection (i.e. using + * Socket.getInetAddress). Otherwise, null should be + * passed. Ignored for IBM i 7.5 and older releases. + * + * @param remotePort If the API is used by a server to provide access to a + * the system, the remote port should be obtained from + * the socket connection (i.e. using Socket.getPort ). + * Otherwise, use 0 if there is not an associated + * connection. Ignored for IBM i 7.5 and older releases. + * + * @param localIPAddress If the API is used by a server to provide access to a + * the system, the local IP address should be obtained + * from the socket connection (i.e. using + * Socket.getLocalAddress). Otherwise, null should be + * passed. Ignored for IBM i 7.5 and older releases. + * + * @param localPort If the API is used by a server to provide access to a + * the system, the local port should be obtained from the + * socket connection (Socket.getLocalPort). Otherwise, + * use 0 if there is not an associated connection. + * Ignored for IBM i 7.5 and older releases. + * + * @param isPrivate Indicates whether the credential is considered + * private. + * + * @param isReusable true if the credential can be used to swap thread + * identity multiple times; otherwise false. + * + * @param isRenewable true if the validity period of the credential can be + * programmatically updated or extended; otherwise false. + * + * @param timeoutInterval The number of seconds to expiration when the + * credential is initially created; ignored if the + * credential does not expire based on time. + * + * @exception Exception If an exception occurs. + * + */ + public void initialize(AS400BasicAuthenticationPrincipal principal, char[] password, char[] additionalAuthFactor, + int authenticationIndicator, String verificationID, String remoteIPAddress, int remotePort, + String localIPAddress, int localPort, boolean isPrivate, boolean isReusable, boolean isRenewable, + int timeoutInterval) throws Exception; + /** * Indicates whether the credential is considered private. * diff --git a/src/main/java/com/ibm/as400/security/auth/DefaultProfileTokenProvider.java b/src/main/java/com/ibm/as400/security/auth/DefaultProfileTokenProvider.java index 772fd9ea4..29c5909d9 100644 --- a/src/main/java/com/ibm/as400/security/auth/DefaultProfileTokenProvider.java +++ b/src/main/java/com/ibm/as400/security/auth/DefaultProfileTokenProvider.java @@ -14,6 +14,7 @@ package com.ibm.as400.security.auth; import java.beans.PropertyVetoException; +import java.util.Arrays; import com.ibm.as400.access.AS400; import com.ibm.as400.access.AS400SecurityException; @@ -43,6 +44,15 @@ public class DefaultProfileTokenProvider implements ProfileTokenProvider /** Any extended information needed to create the profile token */ private Object extendedInfo_; + + /** additional factor */ + private char[] additionalFactor_; + + /** Verification ID */ + private String verificationID_; + + /** remote IP address */ + private String remoteIPAddress_; /** * Constructs a new DefaultProfileTokenProvider @@ -232,6 +242,59 @@ protected void setExtendedInfo(Object extendedInfo) // Assume that the caller has verified that the value is non-null. extendedInfo_ = extendedInfo; } + + + /** + * Set the additional authentication factor to be used when generating the profile token. + * Note that a copy of the value will be made and stored in the object. + * + * @param additionalAuthenticationFactor The additional authentication factor. + * @throws PropertyVetoException + */ + public void setAdditionalAuthenticationFactor(char[] additionalAuthenticationFactor) throws PropertyVetoException + { + if (additionalAuthenticationFactor != null && additionalAuthenticationFactor.length > ProfileTokenCredential.MAX_ADDITIONALAUTHENTICATIONFACTOR_LENGTH) + throw new ExtendedIllegalArgumentException("additionalAuthenticationFactor", ExtendedIllegalArgumentException.LENGTH_NOT_VALID); + + additionalFactor_ = (additionalAuthenticationFactor != null) + ? Arrays.copyOf(additionalAuthenticationFactor, additionalAuthenticationFactor.length) : null; + } + + /** + * Returns a copy of the additional authentication factor. + * + * @return The additional authentication factor. The value can be null if it has not been set. + */ + public char[] getAdditionalAuthenticationFactor() { + return (additionalFactor_ != null) + ? Arrays.copyOf(additionalFactor_, additionalFactor_.length) : null; + } + + /** + * Set the verification ID to be associated with the profile token. The + * verification ID is the label that identifies the specific application, + * service, or action associated with the profile token request. + * + * @param verificationID The verification ID. + * @throws PropertyVetoException + */ + public void setVerificationID(String verificationID) throws PropertyVetoException + { + if (verificationID != null && verificationID.length() > ProfileTokenCredential.MAX_VERIFICATIONID_LENGTH) + throw new ExtendedIllegalArgumentException("verificationID", ExtendedIllegalArgumentException.LENGTH_NOT_VALID); + + verificationID_ = verificationID; + } + + /** + * Returns the verification ID associated with the profile token. + * + * @return The verification ID. The value can be null if it has not been set. + */ + public String getVerificationID() + { + return (verificationID_ != null) ? verificationID_ : ProfileTokenCredential.DEFAULT_VERIFICATION_ID; + } /** * Creates and returns a new profile token credential. @@ -258,6 +321,10 @@ public ProfileTokenCredential create() throws AS400SecurityException newToken.setTimeoutInterval(getTimeoutInterval()); newToken.setTokenType(getTokenType()); + newToken.setAdditionalAuthenticationFactor(additionalFactor_); + newToken.setVerificationID(verificationID_); + newToken.setRemoteIPAddress(remoteIPAddress_); + Object extended = getExtendedInfo(); if (extended instanceof Integer) @@ -281,7 +348,7 @@ else if (extended instanceof char[]) throw new InternalErrorException(InternalErrorException.UNEXPECTED_EXCEPTION); } } - + /** * Validate the specified field is set, that is, not null. * diff --git a/src/main/java/com/ibm/as400/security/auth/ProfileHandleCredential.java b/src/main/java/com/ibm/as400/security/auth/ProfileHandleCredential.java index 6b617aec6..ffa3e1496 100644 --- a/src/main/java/com/ibm/as400/security/auth/ProfileHandleCredential.java +++ b/src/main/java/com/ibm/as400/security/auth/ProfileHandleCredential.java @@ -17,246 +17,238 @@ import com.ibm.as400.access.ExtendedIllegalStateException; import com.ibm.as400.access.Trace; import java.beans.PropertyVetoException; + /** * Represents an IBM i system profile handle. * - *

This credential does not support all possible behavior - * for IBM i system profile handles. It is provided to fill a secondary - * role in support of other credentials when running on the - * local IBM i system. A profile handle credential provides the ability - * to store the current thread identity and restore that - * identity after performing a swap based on another - * credential (i.e. ProfileTokenCredential). + *

+ * This credential does not support all possible behavior for IBM i system profile handles. It is provided to fill a + * secondary role in support of other credentials when running on the local IBM i system. A profile handle credential + * provides the ability to store the current thread identity and restore that identity after performing a swap based on + * another credential (i.e. ProfileTokenCredential). * * @see AS400Credential * @see ProfileTokenCredential * */ -public final class ProfileHandleCredential extends AS400Credential { +public final class ProfileHandleCredential extends AS400Credential +{ static final long serialVersionUID = 4L; + private byte[] handle_ = null; - private byte[] handle_ = null; + /** + * Indicates the length of a profile handle (in bytes) + **/ + public static final int HANDLE_LENGTH = 12; - /** - Indicates the length of a profile handle (in bytes) - **/ - public static final int HANDLE_LENGTH = 12; -/** - * Constructs a ProfileHandleCredential object. - * - */ -public ProfileHandleCredential() { - super(); -} -/** - * Compares the specified Object with the credential - * for equality. - * - * @param o - * Object to be compared for equality. - * - * @return - * true if equal; otherwise false. - * - */ -public boolean equals(Object o) { - if (o == null) - return false; - if (this == o) - return true; - if (!(o instanceof ProfileHandleCredential)) - return false; - return - hashCode() == ((ProfileHandleCredential)o).hashCode(); -} -/** - * Returns the actual bytes for the handle as it exists - * on the IBM i system. - * - * @return - * The handle bytes; null if not set. - * - */ -public byte[] getHandle() { - return handle_; -} -/** - * Returns a hash code for this credential. - * - * @return a hash code for this credential. - * - */ -public int hashCode() { - int hash = 19913; - if (handle_ != null) - for (int i=0; i These are the values initialized prior to - * accessing host information for or taking action against - * the credential and not modified thereafter until - * the credential is destroyed. - * - */ -void invalidateProperties() { - super.invalidateProperties(); - handle_ = null; -} -/** - * Sets the handle based on the current thread identity. - * - *

The system property must be set prior to - * invoking this method. - * - *

If successful, this method results in a new profile - * handle being created on the IBM i system. - * - *

This property cannot be changed once a request - * initiates a connection for the object to the - * IBM i system. - * - * @exception AS400SecurityException - * If an IBM i system security or authentication error occurs. - * - * @exception PropertyVetoException - * If the change is vetoed. - * - * @exception ExtendedIllegalStateException - * If the token cannot be initialized due - * to the current state. - * - */ -public void setHandle() throws PropertyVetoException, AS400SecurityException { - // Validate state - validatePropertySet("system", getSystem()); - // Instantiate a new impl but do not yet set as the default impl_ - ProfileHandleImpl impl = (ProfileHandleImpl)getImplPrimitive(); - // Generate and set the handle value - setHandle(impl.getCurrentHandle()); - // If successful, all defining attributes are now set. - // Set the impl for subsequent references. - setImpl(impl); - // Indicate that a new handle was created. - fireCreated(); -} -/** - * Sets the actual bytes for the handle as it exists - * on the IBM i system. - * - *

This method allows a credential to be constructed - * based on an existing handle (i.e. previously created using the - * QSYGETPH system API). - * - *

This property cannot be changed once a request - * initiates a connection for the object to the - * IBM i system. - * - * @param bytes - * The handle bytes. - * - * @exception PropertyVetoException - * If the change is vetoed. - * - * @exception ExtendedIllegalArgumentException - * If the provided value exceeds the maximum - * allowed length. - * - * @exception ExtendedIllegalStateException - * If the property cannot be changed due - * to the current state. - * - */ -public void setHandle(byte[] bytes) throws PropertyVetoException { - // Validate state - validatePropertyChange("handle"); + /** + * Constructs a ProfileHandleCredential object. + * + */ + public ProfileHandleCredential() + { + super(); + } - // Validate parms - if ((bytes != null) && (bytes.length != HANDLE_LENGTH)) { - Trace.log(Trace.ERROR, "Handle of length " + bytes.length + " not valid "); - throw new ExtendedIllegalArgumentException( - "bytes", ExtendedIllegalArgumentException.LENGTH_NOT_VALID); - } + /** + * Compares the specified Object with the credential for equality. + * + * @param o Object to be compared for equality. + * + * @return true if equal; otherwise false. + * + */ + @Override + public boolean equals(Object o) + { + if (o == null) + return false; + if (this == o) + return true; + if (!(o instanceof ProfileHandleCredential)) + return false; + return hashCode() == ((ProfileHandleCredential) o).hashCode(); + } - byte[] old = getHandle(); - fireVetoableChange("handle", old, bytes); - handle_ = bytes; - firePropertyChange("handle", old, bytes); -} -/** - * Indicates if instances of the class are sufficient - * by themselves to change the OS thread identity. - * - *

Typically this behavior is dictated by the type - * of credential and need not be changed for - * individual instances. - * - * @return - * true - * - */ -boolean typeIsStandalone() { - return true; -} -/** - * Validates that all properties required to define the - * credential have been set. - * - *

These are the values initialized prior to - * accessing host information for or taking action against - * the credential and not modified thereafter until - * the credential is destroyed. - * - * @exception ExtendedIllegalStateException - * If a required property is not set. - * - */ -void validateProperties() { - super.validateProperties(); - validatePropertySet("handle", getHandle()); -} + /** + * Returns the actual bytes for the handle as it exists on the IBM i system. + * + * @return The handle bytes; null if not set. + * + */ + public byte[] getHandle() { + return handle_; + } + + /** + * Returns a hash code for this credential. + * + * @return a hash code for this credential. + * + */ + @Override + public int hashCode() + { + int hash = 19913; + if (handle_ != null) + for (int i = 0; i < handle_.length; i++) + hash ^= (int) handle_[i]; + hash ^= (isPrivate() ? 13431 : 14427); + if (getPrincipal() != null) + hash ^= getPrincipal().hashCode(); + if (getSystem() != null) + hash ^= getSystem().getSystemName().hashCode(); + return hash; + } + + /** + * Returns the name of the class providing an implementation for code delegated by the credential that performs + * native optimization when running on an IBM i system. + * + * @return The qualified class name for native optimizations; null if not applicable. + * + */ + @Override + String implClassNameNative() { + return "com.ibm.as400.access.ProfileHandleImplNative"; + } + + /** + * Returns the name of the class providing an implementation for code delegated by the credential when no native + * optimization is to be performed. + * + * @return The qualified class name. + * + */ + @Override + String implClassNameRemote() { + return "com.ibm.as400.security.auth.ProfileHandleImplRemote"; + } + + /** + * Initializes transient data. + * + */ + @Override + void initTransient() { + super.initTransient(); + handle_ = null; + } + + /** + * Reset the value of all properties used to define the credential. + * + *

+ * These are the values initialized prior to accessing host information for or taking action against the credential + * and not modified thereafter until the credential is destroyed. + * + */ + @Override + void invalidateProperties() { + super.invalidateProperties(); + handle_ = null; + } + + /** + * Sets the handle based on the current thread identity. + * + *

+ * The system property must be set prior to invoking this method. + * + *

+ * If successful, this method results in a new profile handle being created on the IBM i system. + * + *

+ * This property cannot be changed once a request initiates a connection for the object to the IBM i system. + * + * @exception AS400SecurityException If an IBM i system security or authentication error occurs. + * + * @exception PropertyVetoException If the change is vetoed. + * + * @exception ExtendedIllegalStateException If the token cannot be initialized due to the current state. + * + */ + public void setHandle() throws PropertyVetoException, AS400SecurityException + { + // Validate state + validatePropertySet("system", getSystem()); + // Instantiate a new impl but do not yet set as the default impl_ + ProfileHandleImpl impl = (ProfileHandleImpl) getImplPrimitive(); + // Generate and set the handle value + setHandle(impl.getCurrentHandle()); + // If successful, all defining attributes are now set. + // Set the impl for subsequent references. + setImpl(impl); + // Indicate that a new handle was created. + fireCreated(); + } + + /** + * Sets the actual bytes for the handle as it exists on the IBM i system. + * + *

+ * This method allows a credential to be constructed based on an existing handle (i.e. previously created using the + * QSYGETPH system API). + * + *

+ * This property cannot be changed once a request initiates a connection for the object to the IBM i system. + * + * @param bytes The handle bytes. + * + * @exception PropertyVetoException If the change is vetoed. + * + * @exception ExtendedIllegalArgumentException If the provided value exceeds the maximum allowed length. + * + * @exception ExtendedIllegalStateException If the property cannot be changed due to the current state. + * + */ + public void setHandle(byte[] bytes) throws PropertyVetoException + { + // Validate state + validatePropertyChange("handle"); + + // Validate parms + if ((bytes != null) && (bytes.length != HANDLE_LENGTH)) { + Trace.log(Trace.ERROR, "Handle of length " + bytes.length + " not valid "); + throw new ExtendedIllegalArgumentException("bytes", ExtendedIllegalArgumentException.LENGTH_NOT_VALID); + } + + byte[] old = getHandle(); + fireVetoableChange("handle", old, bytes); + handle_ = bytes; + firePropertyChange("handle", old, bytes); + } + + /** + * Indicates if instances of the class are sufficient by themselves to change the OS thread identity. + * + *

+ * Typically this behavior is dictated by the type of credential and need not be changed for individual instances. + * + * @return true + * + */ + @Override + boolean typeIsStandalone() { + return true; + } + + /** + * Validates that all properties required to define the credential have been set. + * + *

+ * These are the values initialized prior to accessing host information for or taking action against the credential + * and not modified thereafter until the credential is destroyed. + * + * @exception ExtendedIllegalStateException If a required property is not set. + * + */ + @Override + void validateProperties() + { + super.validateProperties(); + validatePropertySet("handle", getHandle()); + } } diff --git a/src/main/java/com/ibm/as400/security/auth/ProfileHandleImplRemote.java b/src/main/java/com/ibm/as400/security/auth/ProfileHandleImplRemote.java index 1067c3b65..b1068f213 100644 --- a/src/main/java/com/ibm/as400/security/auth/ProfileHandleImplRemote.java +++ b/src/main/java/com/ibm/as400/security/auth/ProfileHandleImplRemote.java @@ -13,35 +13,30 @@ // /////////////////////////////////////////////////////////////////////////////// import com.ibm.as400.access.Trace; -/** - * The ProfileHandleImplRemote class provides an implementation for - * behavior delegated by a ProfileHandleCredential object. - * - */ -class ProfileHandleImplRemote extends AS400CredentialImplRemote implements ProfileHandleImpl { /** - * Generates and returns a profile handle based on - * the current IBM i thread identity. - * - *

The remote implementation always throws an - * exception. The ProfileHandleCredential has little use - * in remote environments and is introduced in a - * limited capacity to support reestablishing thread - * identity after performing a swap based on another - * credential. Swapping the IBM i thread ID is not - * supported in remote environments. - * - * @return - * The handle bytes. - * - * @exception RetrieveFailedException - * If errors occur while generating the handle. + * The ProfileHandleImplRemote class provides an implementation for behavior delegated by a ProfileHandleCredential + * object. * */ -public byte[] getCurrentHandle() throws RetrieveFailedException { - Trace.log(Trace.ERROR, "Unsupported remote operation"); - throw new RetrieveFailedException( - RetrieveFailedException.REQUEST_NOT_SUPPORTED); -} +class ProfileHandleImplRemote extends AS400CredentialImplRemote implements ProfileHandleImpl +{ + /** + * Generates and returns a profile handle based on the current IBM i thread identity. + * + *

+ * The remote implementation always throws an exception. The ProfileHandleCredential has little use in remote + * environments and is introduced in a limited capacity to support reestablishing thread identity after performing a + * swap based on another credential. Swapping the IBM i thread ID is not supported in remote environments. + * + * @return The handle bytes. + * + * @exception RetrieveFailedException If errors occur while generating the handle. + * + */ + public byte[] getCurrentHandle() throws RetrieveFailedException + { + Trace.log(Trace.ERROR, "Unsupported remote operation"); + throw new RetrieveFailedException(RetrieveFailedException.REQUEST_NOT_SUPPORTED); + } } diff --git a/src/main/java/com/ibm/as400/security/auth/ProfileTokenCredential.java b/src/main/java/com/ibm/as400/security/auth/ProfileTokenCredential.java index dcabc9140..80e056506 100644 --- a/src/main/java/com/ibm/as400/security/auth/ProfileTokenCredential.java +++ b/src/main/java/com/ibm/as400/security/auth/ProfileTokenCredential.java @@ -15,10 +15,12 @@ import com.ibm.as400.access.AS400; import com.ibm.as400.access.AS400SecurityException; +import com.ibm.as400.access.AuthenticationIndicator; import com.ibm.as400.access.ExtendedIllegalArgumentException; import com.ibm.as400.access.ExtendedIllegalStateException; import com.ibm.as400.access.Trace; import java.beans.PropertyVetoException; +import java.util.Arrays; import java.util.Random; /** @@ -189,18 +191,45 @@ public final class ProfileTokenCredential extends AS400Credential implements AS4 { static final long serialVersionUID = 4L; + // In order for old applications that do not have ability to change code, you + // can tell the toolbox not to use enhanced profile tokens by setting + // com.ibm.as400.access.AS400.useEnhancedProfileTokens property to false. + private static boolean useEnhancedProfileTokens_ = true; + static { + String property = System.getProperty("com.ibm.as400.access.AS400.useEnhancedProfileTokens"); + if (property != null && property.toLowerCase().equals("false")) + useEnhancedProfileTokens_ = false; + } + private byte[] addr_ = new byte[9]; // Encode/decode adder private byte[] mask_ = new byte[7]; // Encode/decode mask private byte[] token_ = null; // encoded token private int type_ = TYPE_SINGLE_USE; private int timeoutInterval_ = 3600; + private String verificationID_ = DEFAULT_VERIFICATION_ID; + private String localIPAddress_ = null; + private String remoteIPAddress_ = null; + private int localPort_ = 0; + private int remotePort_ = 0; + private char[] additionalAuthenticationFactor_ = null; + private int authenticationIndicator_ = AuthenticationIndicator.APPLICATION_AUTHENTICATION; + private boolean noRefresh_ = false; + private int creator_ = CREATOR_UNKNOWN; + private final static int MAX_USERPROFILE_LENGTH = 10; final static int MAX_PASSWORD_LENGTH = 128; + /** Maximum length of additional authentication factor */ + public final static int MAX_ADDITIONALAUTHENTICATIONFACTOR_LENGTH = 64; + /** Maximum length of verification ID. */ + public final static int MAX_VERIFICATIONID_LENGTH = 30; + /** Maximum length of IP address. */ + public final static int MAX_IPADDRESS_LENGTH = 46; + /** ID indicating a single use token. **/ public final static int TYPE_SINGLE_USE = 1; @@ -212,6 +241,13 @@ public final class ProfileTokenCredential extends AS400Credential implements AS4 /** Indicates the length of a profile token (in bytes) **/ public final static int TOKEN_LENGTH = 32; + + /** ID indicating the creator of token is not known **/ + public final static int CREATOR_UNKNOWN = 0; + /** ID indicating the creator of token the file server **/ + public final static int CREATOR_SIGNON_SERVER = 1; + /** ID indicating the creator of token is a native API **/ + public final static int CREATOR_NATIVE_API = 2; /** * Password special value indicating that the current password is not verified. @@ -242,6 +278,9 @@ public final class ProfileTokenCredential extends AS400Credential implements AS4 */ public final static int PW_NOPWDCHK = 2; + /** Default verification ID that is used when generating a profile token is "QIBM_OS400_JT400". */ + public final static String DEFAULT_VERIFICATION_ID = "QIBM_OS400_JT400"; + /** * Constructs a ProfileTokenCredential object. * @@ -282,7 +321,89 @@ public ProfileTokenCredential() * @param timeoutInterval The number of seconds to expiration, used as the * default value when the token is refreshed (1-3600). */ - public ProfileTokenCredential(AS400 system, byte[] token, int tokenType, int timeoutInterval) + public ProfileTokenCredential(AS400 system, byte[] token, int tokenType, int timeoutInterval) { + this(system, token, tokenType, timeoutInterval, null, null, null, 0, null, 0); + } + + /** + * Constructs and initializes a ProfileTokenCredential object. + * + *

+ * This method allows a credential to be constructed based on an existing token + * (i.e. previously created using the QSYGENPT system API). It is the + * responsibility of the application to ensure the token attributes, such as + * tokenType and timeoutInterval, are consistent with the + * specified token value. + * + * @param system The system associated with the credential. + * + * @param token The actual bytes for the token as it exists on the IBM + * i system. + * + * @param tokenType The type of token provided. Possible types are defined + * as fields on this class: + *

    + *
  • TYPE_SINGLE_USE + *
  • TYPE_MULTIPLE_USE_NON_RENEWABLE + *
  • TYPE_MULTIPLE_USE_RENEWABLE + *
+ * + * @param timeoutInterval The number of seconds to expiration, used as the + * default value when the token is refreshed (1-3600). + * + * @param additionalAuthFactor The additional authentication factor + * for the user + * + * @param verificationID The verification ID is the label that + * identifies the specific application, + * service, or action associated with the + * profile handle request. This value must + * be 30-characters or less. This value + * will be passed to the authentication + * exit program registered under the + * QIBM_QSY_AUTH exit point if the + * specified user profile has *REGFAC as + * an authentication method. The + * authentication exit program may use the + * verification ID as a means to restrict + * the use of the user profile. If running + * on an IBM i, the verification ID should + * be the DCM application ID or a similar + * value that identifies the application + * or service. + * + * @param remoteIPAddress If the API is used by a server to + * provide access to a the system, the + * remote IP address should be obtained + * from the socket connection (i.e. using + * Socket.getInetAddress). Otherwise, null + * should be passed. + * + * @param remotePort If the API is used by a server to + * provide access to a the system, the + * remote port should be obtained from the + * socket connection (i.e. using + * Socket.getPort ). Otherwise, use 0 if + * there is not an associated connection. + * + * @param localIPAddress If the API is used by a server to + * provide access to a the system, the + * local IP address should be obtained + * from the socket connection (i.e. using + * Socket.getLocalAddress). Otherwise, + * null should be passed. + * + * @param localPort If the API is used by a server to + * provide access to a the system, the + * local port should be obtained from the + * socket connection + * (Socket.getLocalPort). Otherwise, use 0 + * if there is not an associated + * connection. + */ + public ProfileTokenCredential(AS400 system, byte[] token, int tokenType, int timeoutInterval, + char[] additionalAuthFactor, String verificationID, + String remoteIPAddress, int remotePort, String localIPAddress, int localPort) { this(); try { @@ -290,6 +411,12 @@ public ProfileTokenCredential(AS400 system, byte[] token, int tokenType, int tim setToken(token); setTokenType(tokenType); setTimeoutInterval(timeoutInterval); + setAdditionalAuthenticationFactor(additionalAuthFactor); + setVerificationID(verificationID); + setRemoteIPAddress(remoteIPAddress); + setLocalIPAddress(localIPAddress); + setRemotePort(remotePort); + setLocalPort(localPort); } catch (PropertyVetoException pve) { AuthenticationSystem.handleUnexpectedException(pve); } @@ -466,6 +593,16 @@ public void initialize(AS400BasicAuthenticationPrincipal principal, String passw @Override public void initialize(AS400BasicAuthenticationPrincipal principal, char[] password, boolean isPrivate, boolean isReusable, boolean isRenewable, int timeoutInterval) throws Exception + { + initialize(principal, password, additionalAuthenticationFactor_, authenticationIndicator_, + verificationID_, remoteIPAddress_, remotePort_, localIPAddress_, localPort_, + isPrivate, isReusable, isRenewable, timeoutInterval); + } + + @Override + public void initialize(AS400BasicAuthenticationPrincipal principal, char[] password, char[] additionalAuthFactor, int authenticationIndicator, + String verificationID, String remoteIPAddress, int remotePort, String localIPAddress, int localPort, + boolean isPrivate, boolean isReusable, boolean isRenewable, int timeoutInterval) throws Exception { if (Trace.isTraceOn()) { @@ -474,6 +611,11 @@ public void initialize(AS400BasicAuthenticationPrincipal principal, char[] passw .append(principal.toString()).append(", isPrivate == ").append(isPrivate) .append(", isReusable == ").append(isReusable).append(", isRenewable == ") .append(isRenewable).append(", timeoutInterval == ").append(timeoutInterval) + .append(", verificationID == ").append(verificationID) + .append(", localIPAddress == ").append(localIPAddress) + .append(", localPort == ").append(localPort) + .append(", remoteIPAddress == ").append(remoteIPAddress) + .append(", remotePort == ").append(remotePort) .toString()); } @@ -502,6 +644,14 @@ else if (isReusable) setTokenType(TYPE_MULTIPLE_USE_NON_RENEWABLE); else setTokenType(TYPE_SINGLE_USE); + + setAdditionalAuthenticationFactor(additionalAuthFactor); + setAuthenticationIndicator(authenticationIndicator); + setVerificationID(verificationID); + setRemoteIPAddress(remoteIPAddress); + setLocalIPAddress(localIPAddress); + setRemotePort(remotePort); + setLocalPort(localPort); // Generate the token setTokenExtended(pr, password); @@ -512,6 +662,8 @@ void invalidateProperties() { super.invalidateProperties(); token_ = null; + verificationID_ = null; + localIPAddress_ = null; } @Override @@ -1011,7 +1163,9 @@ public void setToken(String name, int passwordSpecialValue) throws PropertyVetoE ProfileTokenImpl impl = (ProfileTokenImpl) getImplPrimitive(); // Generate and set the token value - setToken(impl.generateToken(name, passwordSpecialValue, getTokenType(), getTimeoutInterval())); + if (Trace.isTraceOn()) Trace.log(Trace.DIAGNOSTIC, "ProfileTokenCredential generating profile token w/special value for user: " + name); + + impl.generateToken(name, passwordSpecialValue, this); // If successful, all defining attributes are now set. Set the impl for // subsequent references. @@ -1201,8 +1355,7 @@ public void setTokenExtended(String name, char[] password) throws PropertyVetoEx ProfileTokenImpl impl = (ProfileTokenImpl)getImplPrimitive(); // Generate and set the token value - setToken(impl.generateTokenExtended(name, password, - getTokenType(), getTimeoutInterval())); + impl.generateTokenExtended(name, password, this); // If successful, all defining attributes are now set. // Set the impl for subsequent references. @@ -1307,4 +1460,301 @@ public synchronized void allowRefresh() noRefresh_ = false; notify(); } + + /** + * Return whether enhanced profile token should be used based on whether the JVM + * property com.ibm.as400.access.AS400.useEnhancedProfileTokens. By default, enhanced + * profile tokens will be used. Set the property to false if enhanced profile tokens + * should not be used. + * + * @return true if enhanced profile tokens should be used; otherwise, false. + */ + public static boolean useEnhancedProfileTokens() { + return useEnhancedProfileTokens_; + } + + /** + * Set the additional authentication factor to be used when generating the profile token. + * Note that a copy of the value will be made and stored in the object. + * + * @param additionalAuthenticationFactor The additional authentication factor. + * @throws PropertyVetoException + */ + public void setAdditionalAuthenticationFactor(char[] additionalAuthenticationFactor) throws PropertyVetoException + { + validatePropertyChange("additionalAuthenticationFactor"); + + if (additionalAuthenticationFactor != null && additionalAuthenticationFactor.length > ProfileTokenCredential.MAX_ADDITIONALAUTHENTICATIONFACTOR_LENGTH) + throw new ExtendedIllegalArgumentException("additionalAuthenticationFactor", ExtendedIllegalArgumentException.LENGTH_NOT_VALID); + + char[] old = additionalAuthenticationFactor_; + fireVetoableChange("additionalAuthenticationFactor", old, additionalAuthenticationFactor); + additionalAuthenticationFactor_ = (additionalAuthenticationFactor != null) + ? Arrays.copyOf(additionalAuthenticationFactor, additionalAuthenticationFactor.length) : null; + firePropertyChange("additionalAuthenticationFactor", old, additionalAuthenticationFactor); + } + + /** + * Returns a copy of the additional authentication factor. + * + * @return The additional authentication factor. The value can be null if it has not been set. + */ + public char[] getAdditionalAuthenticationFactor() { + return (additionalAuthenticationFactor_ != null) + ? Arrays.copyOf(additionalAuthenticationFactor_, additionalAuthenticationFactor_.length) : null; + } + + /** + * Set the verification ID to be associated with the profile token. The + * verification ID is the label that identifies the specific application, + * service, or action associated with the profile token request. + * + * @param verificationID The verification ID. + * @throws PropertyVetoException + */ + public void setVerificationID(String verificationID) throws PropertyVetoException + { + validatePropertyChange("verificationID"); + + if (verificationID != null && verificationID.length() > ProfileTokenCredential.MAX_VERIFICATIONID_LENGTH) + throw new ExtendedIllegalArgumentException("verificationID", ExtendedIllegalArgumentException.LENGTH_NOT_VALID); + + String old = verificationID_; + fireVetoableChange("verificationID", old, verificationID); + verificationID_ = verificationID; + firePropertyChange("verificationID", old, verificationID); + } + + /** + * Returns the verification ID associated with the profile token. + * + * @return The verification ID. If the value is not set to a value, the default verification ID of + * "QIBM_OS400_JT400" will be returned. + */ + public String getVerificationID() + { + if (!useEnhancedProfileTokens_) + return "*NOUSE"; + + return (verificationID_ != null) ? verificationID_ : DEFAULT_VERIFICATION_ID; + } + + /** + * Set the local IP address to be associated with the profile token. Note that + * the method does not validate the value to ensure it is a valid IP address. + * + * @param localIPAddress The local IP address. + * @throws PropertyVetoException + */ + public void setLocalIPAddress(String localIPAddress) throws PropertyVetoException + { + validatePropertyChange("localIPAddress"); + + if (localIPAddress != null && localIPAddress.length() > ProfileTokenCredential.MAX_IPADDRESS_LENGTH) + throw new ExtendedIllegalArgumentException("localIPAddress", ExtendedIllegalArgumentException.LENGTH_NOT_VALID); + + String old = localIPAddress_; + fireVetoableChange("localIPAddress", old, localIPAddress); + localIPAddress_ = localIPAddress; + firePropertyChange("localIPAddress", old, localIPAddress); + } + + /** + * Returns the local IP address associated with the profile token. + * + * @return The client IP address. The value can be null if it has not been set. + */ + public String getLocalIPAddress() { + return localIPAddress_; + } + + /** + * Set the remote IP address to be associated with the profile token. Note that + * the method does not validate the value to ensure it is a valid IP address. + * + * @param remoteIPAddress IP address. + * @throws PropertyVetoException + */ + public void setRemoteIPAddress(String remoteIPAddress) throws PropertyVetoException + { + validatePropertyChange("remoteIPAddress"); + + if (remoteIPAddress != null && remoteIPAddress.length() > ProfileTokenCredential.MAX_IPADDRESS_LENGTH) + throw new ExtendedIllegalArgumentException("remoteIPAddress", ExtendedIllegalArgumentException.LENGTH_NOT_VALID); + + String old = remoteIPAddress_; + fireVetoableChange("remoteIPAddress", old, remoteIPAddress); + remoteIPAddress_ = remoteIPAddress; + firePropertyChange("remoteIPAddress", old, remoteIPAddress); + } + + /** + * Returns the remote IP address associated with the profile token. + * + * @return The remote IP address. The value can be null if it has not been set. + */ + public String getRemoteIPAddress() { + return useEnhancedProfileTokens_ ? remoteIPAddress_ : "*NOUSE"; + } + + /** + * Set the remote port of the network connection associated with the profile token request. + * A value of 0 indicates that the remote port is not specified. + * + * @param remotePort The remote port. + * @throws PropertyVetoException + */ + public void setRemotePort(int remotePort) throws PropertyVetoException + { + validatePropertyChange("remotePort"); + + if (remotePort < 0 || remotePort > 65535) + throw new ExtendedIllegalArgumentException("remotePort", ExtendedIllegalArgumentException.PATH_NOT_VALID); + + int old = remotePort; + fireVetoableChange("remotePort", old, remotePort); + remotePort_ = remotePort; + firePropertyChange("remotePort", old, remotePort); + } + + /** + * Returns the remote port of the network connection associated with the profile token request. + * + * @return The remote port. A value of 0 indicates that the remote port is not specified. + */ + public int getRemotePort() { + return remotePort_; + } + + /** + * Set the local port of the network connection associated with the profile token request. + * A value of 0 indicates that the local port is not specified. + * + * @param localPort the local Port. + * @throws PropertyVetoException + */ + public void setLocalPort(int localPort) throws PropertyVetoException + { + validatePropertyChange("localPort"); + + if (localPort < 0 || localPort > 65535) + throw new ExtendedIllegalArgumentException("localPort", ExtendedIllegalArgumentException.PATH_NOT_VALID); + + int old = localPort; + fireVetoableChange("localPort", old, localPort); + localPort_ = localPort; + firePropertyChange("localPort", old, localPort); + } + + /** + * Returns the local port of the network connection associated with the profile token request. + * + * @return The local port. A value of 0 indicates that the local port is not specified. + */ + public int getLocalPort() { + return localPort_; + } + + /** + * Set the authentication indicator. The default value for the authentication indicator is + * AuthenticationIndicator.APPLICATION_AUTHENTICATION. @see com.ibm.as400.access.AuthenticationIndicator + * for further information. + * + * @param authenticationIndicator Indicates how the caller authenticated the user. + * @throws PropertyVetoException + */ + public void setAuthenticationIndicator(int authenticationIndicator) throws PropertyVetoException + { + validatePropertyChange("authenticationIndicator"); + + if (authenticationIndicator < 1 || authenticationIndicator > 5) + throw new ExtendedIllegalArgumentException("authenticationIndicator", ExtendedIllegalArgumentException.PATH_NOT_VALID); + + int old = authenticationIndicator; + fireVetoableChange("authenticationIndicator", old, authenticationIndicator); + authenticationIndicator_ = authenticationIndicator; + firePropertyChange("authenticationIndicator", old, authenticationIndicator); + } + + /** + * Returns the authentication indicator. @see com.ibm.as400.access.AuthenticationIndicator + * for further information. + * + * @return The authentication indicator. + */ + public int getAuthenticationIndicator() { + return authenticationIndicator_; + } + + /** + * Returns an integer indicating how profile token was created. + * + * @return The creator of token. Possible values are defined as fields on this + * class: + *
    + *
  • CREATOR_UNKNOWN + *
  • CREATOR_SIGNON_SERVER + *
  • CREATOR_NATIVE_API + *
+ * + */ + public int getTokenCreator() { + return creator_ ; + } + + /** + * Sets the token creator. + * + *

+ * It is the application's responsibility to maintain consistency between + * explicitly set token values (those not generated from a user and password) + * and token attributes, such as the tokenType, timeoutInterval, and tokenCreator. + * + *

+ * This property cannot be changed once a request initiates a connection for the + * object to the IBM i system (for example, refresh). + * + * @param tokenCreator The creator of the token. Possible values are defined as fields on this + * class: + *

    + *
  • CREATOR_UNKNOWN + *
  • CREATOR_SIGNON_SERVER + *
  • CREATOR_NATIVE_API + *
+ * + * @exception PropertyVetoException If the change is vetoed. + * + * @exception ExtendedIllegalArgumentException If the provided value is out of + * range. + * + * @exception ExtendedIllegalStateException If the property cannot be changed + * due to the current state. + * + */ + public void setTokenCreator(int tokenCreator) throws PropertyVetoException + { + // Validate state + validatePropertyChange("tokenCreator"); + + // Validate parms + if (tokenCreator < 0 || tokenCreator > 2) { + Trace.log(Trace.ERROR, "Token creator " + tokenCreator + " out of range"); + throw new ExtendedIllegalArgumentException("type", ExtendedIllegalArgumentException.RANGE_NOT_VALID); + } + + Integer old = Integer.valueOf(creator_); + Integer crt = Integer.valueOf(tokenCreator); + fireVetoableChange("tokenCreator", old, crt); + creator_ = tokenCreator; + firePropertyChange("tokenCreator", old, crt); + } + + /** + * Returns whether token has been set. + * + * @return true if token has been set; false if token has not been set. + */ + boolean isTokenSet() { + return token_ != null; + } } diff --git a/src/main/java/com/ibm/as400/security/auth/ProfileTokenImpl.java b/src/main/java/com/ibm/as400/security/auth/ProfileTokenImpl.java index 1b6d30a54..a2ce4b46c 100644 --- a/src/main/java/com/ibm/as400/security/auth/ProfileTokenImpl.java +++ b/src/main/java/com/ibm/as400/security/auth/ProfileTokenImpl.java @@ -1,5 +1,7 @@ package com.ibm.as400.security.auth; +import java.beans.PropertyVetoException; + //////////////////////////////////////////////////////////////////////////////// // // JTOpen (IBM Toolbox for Java - OSS version) @@ -96,6 +98,35 @@ public interface ProfileTokenImpl extends AS400CredentialImpl */ byte[] generateToken(String uid, int pwdSpecialValue, int type, int timeoutInterval) throws RetrieveFailedException; + /** + * Generates and returns a new profile token based on the provided information + * using a password special value. + * + * @param uid The name of the user profile for which + * the token is to be generated. + * + * @param pwdSpecialValue A password special value. Possible + * types are defined as fields on the + * ProfileTokenCredential class: + *
    + *
  • PW_NOPWD + *
  • PW_NOPWDCHK + *
+ *

+ * + * @param profileTokenCred The profile token credential to be initialized + * with the token bytes. + * + * + * @return The profile token credential that was passed in with the token bytes initialized. + * + * @exception RetrieveFailedException If errors occur while generating the token. + * @exception PropertyVetoException If errors occur while setting the profile token credential. + * + */ + ProfileTokenCredential generateToken(String uid, int pwdSpecialValue, ProfileTokenCredential profileTokenCred) + throws RetrieveFailedException, PropertyVetoException; + /** * Generates and returns a new profile token based on the provided information * using a password string. @@ -125,6 +156,27 @@ public interface ProfileTokenImpl extends AS400CredentialImpl */ byte[] generateTokenExtended(String uid, char[] pwd, int type, int timeoutInterval) throws RetrieveFailedException; + /** + * Generates and returns a new profile token based on a user profile, password, + * and additional authentication factor. + * + * @param uid The name of the user profile for which + * the token is to be generated. + * + * @param password The password for the user + * + * @param profileTokenCred The profile token credential to be initialized + * with the token bytes. + * + * + * @return The profile token credential that was passed in with the token bytes initialized. + * + * @exception RetrieveFailedException If errors occur while generating the token. + * @exception PropertyVetoException If errors occur while setting the profile token credential. + */ + ProfileTokenCredential generateTokenExtended(String uid, char[] password, ProfileTokenCredential profileTokenCred) + throws RetrieveFailedException, PropertyVetoException; + /** * Updates or extends the validity period for the credential. * diff --git a/src/main/java/com/ibm/as400/security/auth/ProfileTokenImplRemote.java b/src/main/java/com/ibm/as400/security/auth/ProfileTokenImplRemote.java index cb41bfd66..616b70ee5 100644 --- a/src/main/java/com/ibm/as400/security/auth/ProfileTokenImplRemote.java +++ b/src/main/java/com/ibm/as400/security/auth/ProfileTokenImplRemote.java @@ -13,6 +13,9 @@ package com.ibm.as400.security.auth; +import java.beans.PropertyVetoException; +import java.io.IOException; + import com.ibm.as400.access.*; /** @@ -69,6 +72,15 @@ public byte[] generateToken(String uid, String pwd, int type, int timeoutInterva @Override public byte[] generateToken(String uid, int pwdSpecialValue, int type, int timeoutInterval) throws RetrieveFailedException + { + return generateToken(uid, pwdSpecialValue, null, AuthenticationIndicator.APPLICATION_AUTHENTICATION, + null, null, 0, null, 0, type, timeoutInterval); + + } + + private byte[] generateToken(String uid, int pwdSpecialValue, char[] additionalAuthenticationFactor, + int authenticationIndicator, String verificationId, String remoteIpAddress, int remotePort, + String localIpAddress, int localPort, int type, int timeoutInterval) throws RetrieveFailedException { // Convert password special value from enumerated int to String String pwd; @@ -89,8 +101,31 @@ public byte[] generateToken(String uid, int pwdSpecialValue, int type, int timeo // do not use with real passwords AS400 sys = getCredential().getSystem(); + + // Determine if we are using enhanced profile tokens + boolean useEPT = false; + try { + useEPT = (ProfileTokenCredential.useEnhancedProfileTokens() && sys.getVRM() > 0x00070500); + } + catch (AS400SecurityException|IOException e) { + Trace.log(Trace.ERROR, "Unexpected Exception: ", e); + throw new RetrieveFailedException(); + } + + // The API QSYGENPT requires all parameters to be non-null. + boolean isAAFNull = (additionalAuthenticationFactor == null || additionalAuthenticationFactor.length == 0); + if (isAAFNull) additionalAuthenticationFactor = new char[] { ' ' }; + + boolean isVfyIDNull = (verificationId == null || verificationId.length() == 0); + if (isVfyIDNull) verificationId = " "; - ProgramParameter[] parmlist = new ProgramParameter[6]; + boolean isRemoteIPNull = (remoteIpAddress == null || remoteIpAddress.length() == 0); + if (isRemoteIPNull) remoteIpAddress = " "; + + boolean isLocalIPNull = (localIpAddress == null || localIpAddress.length() == 0); + if (isLocalIPNull) localIpAddress = " "; + + ProgramParameter[] parmlist = new ProgramParameter[useEPT ? 19 : 6]; // Output: Profile token parmlist[0] = new ProgramParameter(ProfileTokenCredential.TOKEN_LENGTH); @@ -115,6 +150,55 @@ public byte[] generateToken(String uid, int pwdSpecialValue, int type, int timeo // Input/output: Error code. NULL. parmlist[5] = new ProgramParameter(BinaryConverter.intToByteArray(0)); + + // If enhanced profile tokens supported then set parameters + if (useEPT) + { + // -- Optional Parameter Group 1 + + // Input: Length of user password. Int to byte[]. Special value is used, thus must be 10 + parmlist[6] = new ProgramParameter(BinaryConverter.intToByteArray(10)); + + // Input: CCSID of user password. Int to byte[]. Special value is used, thus must be 37 + parmlist[7] = new ProgramParameter(BinaryConverter.intToByteArray(37)); + + // -- Optional Parameter Group 2 + + // Input: Additional authentication factor (unicode) + parmlist[8] = new ProgramParameter(BinaryConverter.charArrayToByteArray(additionalAuthenticationFactor)); + + // Input: Length of additional authentication factor + parmlist[9] = new ProgramParameter(BinaryConverter.intToByteArray((isAAFNull) ? 0 : parmlist[8].getInputData().length)); + + // Input: CCSID of additional authentication factor + parmlist[10] = new ProgramParameter(BinaryConverter.intToByteArray(13488)); + + // Input: Authentication indicator (for passwords, it is ignored) + parmlist[11] = new ProgramParameter(BinaryConverter.intToByteArray(authenticationIndicator)); + + // Input: Verification ID - must be 30 in length, blank padded + parmlist[12] = new ProgramParameter(CharConverter.stringToByteArray(sys, (verificationId + " ").substring(0, 30))); + + // Input: Remote IP address + parmlist[13] = new ProgramParameter(CharConverter.stringToByteArray(sys, remoteIpAddress)); + + // Input: Length of remote IP address + parmlist[14] = new ProgramParameter(BinaryConverter.intToByteArray((isRemoteIPNull) ? 0 : parmlist[13].getInputData().length)); + + // Input: Remote port + parmlist[15] = new ProgramParameter(BinaryConverter.intToByteArray(remotePort)); + + // Input: Local IP address + parmlist[16] = new ProgramParameter(CharConverter.stringToByteArray(sys, localIpAddress)); + + // Input: Length of local IP address + parmlist[17] = new ProgramParameter(BinaryConverter.intToByteArray((isLocalIPNull) ? 0 : parmlist[16].getInputData().length)); + + // Input: Local port + parmlist[18] = new ProgramParameter(BinaryConverter.intToByteArray(remotePort)); + } + + if (Trace.isTraceOn()) Trace.log(Trace.DIAGNOSTIC, "ProfileTokenImpleRemote generating profile token w/special value for user: " + uid); ProgramCall programCall = new ProgramCall(sys); @@ -136,6 +220,39 @@ public byte[] generateToken(String uid, int pwdSpecialValue, int type, int timeo return parmlist[0].getOutputData(); } + + @Override + public ProfileTokenCredential generateToken(String uid, int pwdSpecialValue, ProfileTokenCredential profileTokenCred) + throws RetrieveFailedException, PropertyVetoException + { + byte[] token = generateToken(uid, pwdSpecialValue, + profileTokenCred.getAdditionalAuthenticationFactor(), + profileTokenCred.getAuthenticationIndicator(), + profileTokenCred.getVerificationID(), + profileTokenCred.getRemoteIPAddress(), + profileTokenCred.getRemotePort(), + profileTokenCred.getLocalIPAddress(), + profileTokenCred.getLocalPort(), + profileTokenCred.getTokenType(), + profileTokenCred.getTimeoutInterval()); + + try { + profileTokenCred.setToken(token); + profileTokenCred.setTokenCreator(ProfileTokenCredential.CREATOR_NATIVE_API); + } + catch (PropertyVetoException e) + { + try { + removeFromSystem(getCredential().getSystem(), token); + } catch (DestroyFailedException e1) { + Trace.log(Trace.ERROR, "Unexpected Exception during profile token destroy: ", e); + } + + throw e; + } + + return profileTokenCred; + } /** * Generates and returns a new profile token based on @@ -196,14 +313,22 @@ public byte[] generateTokenExtended(String uid, String pwd, int type, int timeou } @Override - public byte[] generateTokenExtended(String uid, char[] pwd, int type, int timeoutInterval) throws RetrieveFailedException + public byte[] generateTokenExtended(String uid, char[] pwd, int type, int timeoutInterval) throws RetrieveFailedException { + return generateTokenExtended(uid, pwd, null, null, null, 0, null, 0, type, timeoutInterval).getToken(); + } + + private ProfileTokenCredential generateTokenExtended(String uid, char[] password, char[] additionalAuthenticationFactor, + String verificationId, String remoteIpAddress, int remotePort, String localIpAddress, int localPort, + int type, int timeoutInterval) throws RetrieveFailedException { // Use the AS400 object to obtain the token. // This will obtain the token by interacting with the IBM i // system signon server and avoid transmitting a cleartext password. - byte[] tkn = null; + ProfileTokenCredential ptTemp = null; try { - tkn = getCredential().getSystem().getProfileToken(uid, pwd, type, timeoutInterval).getToken(); + ptTemp = getCredential().getSystem().getProfileToken(uid, password, additionalAuthenticationFactor, + type, timeoutInterval, + verificationId, remoteIpAddress); } catch (AS400SecurityException se) { throw new RetrieveFailedException(se.getReturnCode()); @@ -212,7 +337,40 @@ public byte[] generateTokenExtended(String uid, char[] pwd, int type, int timeou AuthenticationSystem.handleUnexpectedException(e); } - return tkn; + return ptTemp; + } + + @Override + public ProfileTokenCredential generateTokenExtended(String uid, char[] password, + ProfileTokenCredential profileTokenCred) throws RetrieveFailedException, PropertyVetoException + { + ProfileTokenCredential ptTemp = generateTokenExtended(uid, password, + profileTokenCred.getAdditionalAuthenticationFactor(), + profileTokenCred.getVerificationID(), + profileTokenCred.getRemoteIPAddress(), + profileTokenCred.getRemotePort(), + profileTokenCred.getLocalIPAddress(), + profileTokenCred.getLocalPort(), + profileTokenCred.getTokenType(), + profileTokenCred.getTimeoutInterval()); + + try { + profileTokenCred.setToken(ptTemp.getToken()); + profileTokenCred.setTokenCreator(ptTemp.getTokenCreator()); + profileTokenCred.setRemoteIPAddress(ptTemp.getRemoteIPAddress()); + } + catch (PropertyVetoException e) + { + try { + removeFromSystem(getCredential().getSystem(), ptTemp.getToken()); + } catch (DestroyFailedException e1) { + Trace.log(Trace.ERROR, "Unexpected Exception during profile token destroy: ", e); + } + + throw e; + } + + return profileTokenCred; } @Override @@ -250,13 +408,45 @@ public byte[] refresh(int type, int timeoutInterval) throws RefreshFailedExcepti ProfileTokenCredential tgt = (ProfileTokenCredential)getCredential(); AS400 sys = tgt.getSystem(); ProgramCall programCall = new ProgramCall(tgt.getSystem()); + + // Determine if we are using enhanced profile tokens + boolean useEPT = false; + try { + useEPT = (ProfileTokenCredential.useEnhancedProfileTokens() && sys.getVRM() > 0x00070500); + } + catch (AS400SecurityException|IOException e) { + Trace.log(Trace.ERROR, "Unexpected Exception: ", e); + throw new RefreshFailedException(); + } + + // Parameters cannot be null! + String verificationId = tgt.getVerificationID(); + boolean isVfyIDNull = (verificationId == null || verificationId.length() == 0); + if (isVfyIDNull) verificationId = " "; + + String remoteIpAddress = tgt.getRemoteIPAddress(); + boolean isRemoteIPNull = (remoteIpAddress == null || remoteIpAddress.length() == 0); + if (isRemoteIPNull) remoteIpAddress = " "; - ProgramParameter[] parmlist = new ProgramParameter[5]; + ProgramParameter[] parmlist = new ProgramParameter[useEPT ? 8 : 5]; + parmlist[0] = new ProgramParameter(ProfileTokenCredential.TOKEN_LENGTH); parmlist[1] = new ProgramParameter(new AS400ByteArray(ProfileTokenCredential.TOKEN_LENGTH).toBytes(tgt.getToken())); parmlist[2] = new ProgramParameter(new AS400Bin4().toBytes(timeoutInterval)); parmlist[3] = new ProgramParameter(new AS400Text(1, sys.getCcsid(), sys).toBytes(Integer.toString(type))); parmlist[4] = new ProgramParameter(new AS400Bin4().toBytes(0)); + + if (useEPT) + { + // Input: Verification ID - must be 30 in length, blank padded + parmlist[5] = new ProgramParameter(CharConverter.stringToByteArray(sys, (verificationId + " ").substring(0, 30))); + + // Input: Remote IP address + parmlist[6] = new ProgramParameter(CharConverter.stringToByteArray(sys, remoteIpAddress)); + + // Input: Length of remote IP address + parmlist[7] = new ProgramParameter(BinaryConverter.intToByteArray((isRemoteIPNull) ? 0 : parmlist[13].getInputData().length)); + } try { programCall.setProgram(QSYSObjectPathName.toPath("QSYS", "QSYGENFT", "PGM"), parmlist); @@ -285,31 +475,34 @@ public byte[] refresh(int type, int timeoutInterval) throws RefreshFailedExcepti */ void removeFromSystem() throws DestroyFailedException { - ProfileTokenCredential tgt = (ProfileTokenCredential)getCredential(); - AS400 sys = tgt.getSystem(); - ProgramCall programCall = new ProgramCall(sys); + ProfileTokenCredential pt = (ProfileTokenCredential)getCredential(); + removeFromSystem(pt.getSystem(), pt.getToken()); + } + + private static void removeFromSystem(AS400 sys, byte[] token) throws DestroyFailedException + { + ProgramCall programCall = new ProgramCall(sys); - ProgramParameter[] parmlist = new ProgramParameter[3]; - parmlist[0] = new ProgramParameter( - new AS400Text(10, sys.getCcsid(), sys).toBytes("*PRFTKN")); - parmlist[1] = new ProgramParameter(new AS400Bin4().toBytes(0)); - parmlist[2] = new ProgramParameter(new AS400ByteArray(ProfileTokenCredential.TOKEN_LENGTH).toBytes(tgt.getToken())); + ProgramParameter[] parmlist = new ProgramParameter[3]; + parmlist[0] = new ProgramParameter(new AS400Text(10, sys.getCcsid(), sys).toBytes("*PRFTKN")); + parmlist[1] = new ProgramParameter(new AS400Bin4().toBytes(0)); + parmlist[2] = new ProgramParameter(new AS400ByteArray(ProfileTokenCredential.TOKEN_LENGTH).toBytes(token)); - try - { - programCall.setProgram(QSYSObjectPathName.toPath("QSYS", "QSYRMVPT", "PGM"), parmlist); - programCall.suggestThreadsafe(); // Run on-thread if possible. - if (!programCall.run()) { - Trace.log(Trace.ERROR, "Call to QSYRMVPT failed."); - throw new DestroyFailedException(); - } - } - catch (java.io.IOException|java.beans.PropertyVetoException|InterruptedException e) { - AuthenticationSystem.handleUnexpectedException(e); - } - catch (Exception e) { - throw new DestroyFailedException(programCall.getMessageList()); - } + try + { + programCall.setProgram(QSYSObjectPathName.toPath("QSYS", "QSYRMVPT", "PGM"), parmlist); + programCall.suggestThreadsafe(); // Run on-thread if possible. + if (!programCall.run()) { + Trace.log(Trace.ERROR, "Call to QSYRMVPT failed."); + throw new DestroyFailedException(); + } + } + catch (java.io.IOException|java.beans.PropertyVetoException|InterruptedException e) { + AuthenticationSystem.handleUnexpectedException(e); + } + catch (Exception e) { + throw new DestroyFailedException(programCall.getMessageList()); + } } /** diff --git a/src/main/java/com/ibm/as400/security/auth/Swapper.java b/src/main/java/com/ibm/as400/security/auth/Swapper.java index 1a488c261..da9e66604 100644 --- a/src/main/java/com/ibm/as400/security/auth/Swapper.java +++ b/src/main/java/com/ibm/as400/security/auth/Swapper.java @@ -21,218 +21,227 @@ import java.sql.SQLWarning; /** - Provides utility methods to perform credential swaps for existing remote connections. - The methods in this class allow you to do work under - a different user, providing you've obtained a {@link ProfileTokenCredential ProfileTokenCredential} - probably from {@link com.ibm.as400.access.AS400#getProfileToken(String,String) AS400.getProfileToken()}. -

- Comparison with the swap() methods of ProfileTokenCredential: - -

- The swap() methods of this class have as one of their arguments either an AS400 object or a Connection object. The contract of these methods is to swap the profile in use for a specified connection. Here is the usage pattern: -

+ * Provides utility methods to perform credential swaps for existing remote connections. The methods in this class allow
+ * you to do work under a different user, providing you've obtained a {@link ProfileTokenCredential
+ * ProfileTokenCredential} probably from {@link com.ibm.as400.access.AS400#getProfileToken(String,String)
+ * AS400.getProfileToken()}.
+ * 

+ * Comparison with the swap() methods of ProfileTokenCredential: + * + *

+ * The swap() methods of this class have as one of their arguments either an AS400 object or a Connection + * object. The contract of these methods is to swap the profile in use for a specified connection. Here is the + * usage pattern: + * + *

  AS400 conn1 = new AS400(sysName, userID, password);
  ProfileTokenCredential myCred = new ProfileTokenCredential(....);
  Swapper.swap(conn1, myCred);
  // conn1 is now running under the new (swapped-to) profile
- 
- -

- In constrast, the contract of the swap() methods of class {@link ProfileTokenCredential ProfileTokenCredential} is to swap the profile in use for the current thread of execution. They don't swap the profile in use for a specific AS400 object, but rather for any subsequently-created AS400 objects. Here is the usage pattern: -

+ * 
+ * + *

+ * In constrast, the contract of the swap() methods of class {@link ProfileTokenCredential + * ProfileTokenCredential} is to swap the profile in use for the current thread of execution. They don't swap + * the profile in use for a specific AS400 object, but rather for any subsequently-created AS400 objects. Here + * is the usage pattern: + * + *

  AS400 conn1 = new AS400();
  ProfileTokenCredential myCred = new ProfileTokenCredential(....);
  myCred.swap();
  AS400 conn2 = new AS400();  // conn2 is running under the swapped-to profile.
  // conn1 is still running under the original profile.
- 
- -

- The Swapper.swap() methods are useful for swapping credentials for existing remote connections. The ProfileTokenCredential.swap() methods are useful for swapping the current thread of execution when running natively in an IBM i JVM. -

- This class is mostly based on a prototype contributed by Steve Johnson-Evers. + *

+ * + *

+ * The Swapper.swap() methods are useful for swapping credentials for existing remote connections. The + * ProfileTokenCredential.swap() methods are useful for swapping the current thread of execution when + * running natively in an IBM i JVM. + *

+ * This class is mostly based on a prototype contributed by Steve Johnson-Evers. **/ public class Swapper { - // Prevent instantiation of this class. - private Swapper() {} - - - /** - Swaps the profile on the specified system. - This method calls system API QSYSETP ("Set To Profile Token"). -

- Note: This method is intended for use with remote connections only, - and only swaps the profile used by - {@link com.ibm.as400.access.CommandCall CommandCall}, - {@link com.ibm.as400.access.ProgramCall ProgramCall}, and - {@link com.ibm.as400.access.ServiceProgramCall ServiceProgramCall}. - If your Java application is running "natively", that is, on-thread on the - IBM i JVM, and you wish to swap the current thread to a different profile, - use one of the swap() methods of - {@link ProfileTokenCredential ProfileTokenCredential} instead of this method. - - @param system The remote IBM i system. - @param newCredential The credential to use for the swap. - @exception AS400SecurityException If a security or authority error occurs. - @exception IOException If an error occurs while communicating with the system. - @see ProfileTokenCredential#swap() - @see ProfileTokenCredential#swap(boolean) - **/ - public static void swap(AS400 system, ProfileTokenCredential newCredential) - throws AS400SecurityException, IOException - { - if (system == null) throw new NullPointerException("system"); - if (newCredential == null) throw new NullPointerException("newCredential"); - - // If running natively, suggest the use of ProfileTokenCredential.swap() instead. - if (system.canUseNativeOptimizations()) + // Prevent instantiation of this class. + private Swapper() { - Trace.log(Trace.WARNING, "When running natively, swaps should be performed via ProfileTokenCredential.swap() instead of Swapper.swap()."); } - swapToToken(system, newCredential.getToken()); - newCredential.fireSwapped(); - } - - - /** - Swaps the profile on the specified JDBC connection. - This method calls system API QSYSETP ("Set To Profile Token"). - - @param connection A JDBC connection to the IBM i system. - Must be an instance of - {@link com.ibm.as400.access.AS400JDBCConnection} or {@link com.ibm.as400.access.AS400JDBCConnectionHandle}. - @param newCredential The credential to use for the swap. - @exception AS400SecurityException If a security or authority error occurs. - @exception IOException If an error occurs while communicating with the system. - @exception SQLException If the connection is not open, or an error occurs. - **/ - public static void swap(Connection connection, ProfileTokenCredential newCredential) - throws AS400SecurityException, IOException, SQLException - { - if (connection == null) throw new NullPointerException("connection"); - if (newCredential == null) throw new NullPointerException("newCredential"); - - swapToToken(connection, newCredential.getToken()); - newCredential.fireSwapped(); - } - - - /** - Swaps the profile, using the specified profile token. - This method calls system API QSYSETP ("Set To Profile Token"). -

- Note: This method is intended for use with remote connections only, - and only swaps the profile used by - {@link com.ibm.as400.access.CommandCall CommandCall}, - {@link com.ibm.as400.access.ProgramCall ProgramCall}, and - {@link com.ibm.as400.access.ServiceProgramCall ServiceProgramCall}. - If your Java application is running "natively", that is, on-thread on the - IBM i JVM, and you wish to swap the current thread to a different profile, - use one of the swap() methods of - {@link ProfileTokenCredential ProfileTokenCredential} instead of this method. - - @param system The IBM i system. - @param token The bytes from {@link ProfileTokenCredential#getToken()} - @exception AS400SecurityException If a security or authority error occurs. - @exception IOException If an error occurs while communicating with the system. - @see #swap(AS400, ProfileTokenCredential) - **/ - public static void swapToToken(AS400 system, byte[] token) - throws AS400SecurityException, IOException - { - if (system == null) throw new NullPointerException("system"); - if (token == null) throw new NullPointerException("token"); - - // API takes 2 parameters: A char(32) profile token and error code - ProgramParameter[] parmList = new ProgramParameter[2]; - - // Input: Profile token (32A) - parmList[0] = new ProgramParameter(token); - - // Input/Output: Error code - parmList[1] = new ErrorCodeParameter(); - - // Call the program - ProgramCall pgm = new ProgramCall(system, "/QSYS.LIB/QSYSETPT.PGM", parmList); - pgm.suggestThreadsafe(); // Run on-thread if possible; allows app to use disabled profile. - - try + /** + * Swaps the profile on the specified system. This method calls system API QSYSETP ("Set To Profile Token"). + *

+ * Note: This method is intended for use with remote connections only, and only swaps the profile used by + * {@link com.ibm.as400.access.CommandCall CommandCall}, {@link com.ibm.as400.access.ProgramCall ProgramCall}, and + * {@link com.ibm.as400.access.ServiceProgramCall ServiceProgramCall}. If your Java application is running + * "natively", that is, on-thread on the IBM i JVM, and you wish to swap the current thread to a different profile, + * use one of the swap() methods of {@link ProfileTokenCredential ProfileTokenCredential} instead of + * this method. + * + * @param system The remote IBM i system. + * @param newCredential The credential to use for the swap. + * @exception AS400SecurityException If a security or authority error occurs. + * @exception IOException If an error occurs while communicating with the system. + * @see ProfileTokenCredential#swap() + * @see ProfileTokenCredential#swap(boolean) + **/ + public static void swap(AS400 system, ProfileTokenCredential newCredential) throws AS400SecurityException, IOException { - if (!pgm.run()) { - throw new SwapFailedException(pgm.getMessageList()); - } - - ///TBD experiment - AS400Message[] msgs = pgm.getMessageList(); - if (msgs != null && msgs.length != 0) - { - System.out.println("Messages returned from QSYSETPT:"); /// - for (int i=0; icall statement to pass the token to QSYSETPT. - - @param connection A JDBC connection to the IBM i system. - @param token The bytes from {@link ProfileTokenCredential#getToken()} - @exception AS400SecurityException If a security or authority error occurs. - @exception IOException If an error occurs while communicating with the system. - @exception SQLException If the connection is not open, or an error occurs. - @see #swap(Connection, ProfileTokenCredential) - **/ - public static void swapToToken(Connection connection, byte[] token) - throws AS400SecurityException, IOException, SQLException - { - if (connection == null) throw new NullPointerException("connection"); - if (token == null) throw new NullPointerException("token"); - - // Note: Since we _always_ submit our JDBC requests via the Database Server, we don't really have a "Toolbox native" mode when using a JDBC connection. So the swap should behave the same, whether the Java app is running remotely or natively on IBM i. - StringBuffer sql = new StringBuffer(80); - sql.append("CALL QSYS"); - sql.append(connection.getMetaData().getCatalogSeparator()); - sql.append("QSYSETPT (X'"); - - for (int i=0; i + * Note: This method is intended for use with remote connections only, and only swaps the profile used by + * {@link com.ibm.as400.access.CommandCall CommandCall}, {@link com.ibm.as400.access.ProgramCall ProgramCall}, and + * {@link com.ibm.as400.access.ServiceProgramCall ServiceProgramCall}. If your Java application is running + * "natively", that is, on-thread on the IBM i JVM, and you wish to swap the current thread to a different profile, + * use one of the swap() methods of {@link ProfileTokenCredential ProfileTokenCredential} instead of + * this method. + * + * @param system The IBM i system. + * @param token The bytes from {@link ProfileTokenCredential#getToken()} + * @exception AS400SecurityException If a security or authority error occurs. + * @exception IOException If an error occurs while communicating with the system. + * @see #swap(AS400, ProfileTokenCredential) + **/ + public static void swapToToken(AS400 system, byte[] token) throws AS400SecurityException, IOException + { + if (system == null) + throw new NullPointerException("system"); + if (token == null) + throw new NullPointerException("token"); + + // API takes 2 parameters: A char(32) profile token and error code + ProgramParameter[] parmList = new ProgramParameter[2]; + + // Input: Profile token (32A) + parmList[0] = new ProgramParameter(token); + + // Input/Output: Error code + parmList[1] = new ErrorCodeParameter(); + + // Call the program + ProgramCall pgm = new ProgramCall(system, "/QSYS.LIB/QSYSETPT.PGM", parmList); + pgm.suggestThreadsafe(); // Run on-thread if possible; allows app to use disabled profile. + + try + { + if (!pgm.run()) + throw new SwapFailedException(pgm.getMessageList()); + + /// TBD experiment + AS400Message[] msgs = pgm.getMessageList(); + if (msgs != null && msgs.length != 0) + { + System.out.println("Messages returned from QSYSETPT:"); /// + for (int i = 0; i < msgs.length; i++) { + System.out.println(msgs[i].toString()); + } + } + } + catch (AS400SecurityException|RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); } - } } - } + /** + * Swaps the profile, using the specified profile token. This method uses SQL's call statement to pass + * the token to QSYSETPT. + * + * @param connection A JDBC connection to the IBM i system. + * @param token The bytes from {@link ProfileTokenCredential#getToken()} + * @exception AS400SecurityException If a security or authority error occurs. + * @exception IOException If an error occurs while communicating with the system. + * @exception SQLException If the connection is not open, or an error occurs. + * @see #swap(Connection, ProfileTokenCredential) + **/ + public static void swapToToken(Connection connection, byte[] token) throws AS400SecurityException, IOException, SQLException + { + if (connection == null) + throw new NullPointerException("connection"); + if (token == null) + throw new NullPointerException("token"); + + // Note: Since we _always_ submit our JDBC requests via the Database Server, we don't really have a "Toolbox + // native" mode when using a JDBC connection. So the swap should behave the same, whether the Java app is + // running remotely or natively on IBM i. + StringBuffer sql = new StringBuffer(80); + sql.append("CALL QSYS"); + sql.append(connection.getMetaData().getCatalogSeparator()); + sql.append("QSYSETPT (X'"); + + for (int i = 0; i < token.length; i++) + { + int unsignedByte = token[i]; + if (unsignedByte < 0) + unsignedByte = 256 + unsignedByte; + else if (unsignedByte < 16) + sql.append('0'); + + sql.append(Integer.toHexString(unsignedByte).toUpperCase()); + } + + sql.append("', X'0000')"); + + Statement stmt = null; + try + { + stmt = connection.createStatement(); + stmt.execute(sql.toString()); + /// TBD: Check stmt for returned messages, warnings, etc + SQLWarning warning = stmt.getWarnings(); + if (warning != null) + System.out.println("SQLWarning: " + warning.getErrorCode() + ": " + warning.getSQLState()); /// + } + finally + { + if (stmt != null) + { + try { + stmt.close(); + } catch (Exception e) { + if (Trace.isTraceOn()) Trace.log(Trace.ERROR, "Error while closing statement", e); + } + } + } + } } diff --git a/src/main/resources/com/ibm/as400/resource/RJobList.pcml b/src/main/resources/com/ibm/as400/resource/RJobList.pcml index 0ac558c15..3583193ba 100644 --- a/src/main/resources/com/ibm/as400/resource/RJobList.pcml +++ b/src/main/resources/com/ibm/as400/resource/RJobList.pcml @@ -29,7 +29,7 @@ - + @@ -93,7 +93,7 @@ - + @@ -107,7 +107,7 @@ - + diff --git a/src/main/resources/com/ibm/as400/resource/RJobLog.pcml b/src/main/resources/com/ibm/as400/resource/RJobLog.pcml index 553e7aba6..d339cfb7c 100644 --- a/src/main/resources/com/ibm/as400/resource/RJobLog.pcml +++ b/src/main/resources/com/ibm/as400/resource/RJobLog.pcml @@ -30,7 +30,7 @@ - + @@ -66,7 +66,7 @@ - + @@ -80,7 +80,7 @@ - + diff --git a/src/main/resources/com/ibm/as400/resource/RMessageQueue.pcml b/src/main/resources/com/ibm/as400/resource/RMessageQueue.pcml index eb3ea7524..4eb69512a 100644 --- a/src/main/resources/com/ibm/as400/resource/RMessageQueue.pcml +++ b/src/main/resources/com/ibm/as400/resource/RMessageQueue.pcml @@ -22,7 +22,7 @@ - + @@ -68,7 +68,7 @@ - + @@ -82,7 +82,7 @@ - + diff --git a/src/main/resources/com/ibm/as400/resource/RPrinter.pcml b/src/main/resources/com/ibm/as400/resource/RPrinter.pcml index ea4b0759b..4a9995de4 100644 --- a/src/main/resources/com/ibm/as400/resource/RPrinter.pcml +++ b/src/main/resources/com/ibm/as400/resource/RPrinter.pcml @@ -15,7 +15,7 @@ - + diff --git a/src/main/resources/com/ibm/as400/resource/RPrinterList.pcml b/src/main/resources/com/ibm/as400/resource/RPrinterList.pcml index d7c317f44..b5b933f7c 100644 --- a/src/main/resources/com/ibm/as400/resource/RPrinterList.pcml +++ b/src/main/resources/com/ibm/as400/resource/RPrinterList.pcml @@ -15,7 +15,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -76,7 +76,7 @@ - + diff --git a/src/main/resources/com/ibm/as400/resource/RUserList.pcml b/src/main/resources/com/ibm/as400/resource/RUserList.pcml index 73746e9c8..6a2189797 100644 --- a/src/main/resources/com/ibm/as400/resource/RUserList.pcml +++ b/src/main/resources/com/ibm/as400/resource/RUserList.pcml @@ -15,7 +15,7 @@ - + @@ -41,7 +41,7 @@ - + @@ -55,7 +55,7 @@ - +