From d5a298fe7459be7c948c512991826d8d21e8d1c3 Mon Sep 17 00:00:00 2001 From: Nadir K Amra Date: Wed, 28 Aug 2024 00:50:34 -0500 Subject: [PATCH 01/10] Add methods to obtain local IP address from a socket connection Signed-off-by: Nadir K Amra --- .../com/ibm/as400/access/AS400Server.java | 4 + .../com/ibm/as400/access/SocketContainer.java | 1 + .../ibm/as400/access/SocketContainerInet.java | 13 ++- .../ibm/as400/access/SocketContainerJSSE.java | 6 +- .../ibm/as400/access/SocketContainerUnix.java | 18 ++-- .../as400/access/SocketContainerUnix2.java | 98 +++++++++++-------- 6 files changed, 89 insertions(+), 51 deletions(-) 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/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 bee70b2fc..ae457fac0 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"; } } From b0d9da60a5df7771a40d4ec91c20419c89ec78be Mon Sep 17 00:00:00 2001 From: Nadir K Amra Date: Wed, 28 Aug 2024 01:02:28 -0500 Subject: [PATCH 02/10] Reformatting and fix to ensure as-hostcnn is always TLS in port mapper Signed-off-by: Nadir K Amra --- .../java/com/ibm/as400/access/PortMapper.java | 6 + .../com/ibm/as400/access/ProgramCall.java | 9 +- .../com/ibm/as400/security/auth/Swapper.java | 389 +++++++++--------- 3 files changed, 206 insertions(+), 198 deletions(-) diff --git a/src/main/java/com/ibm/as400/access/PortMapper.java b/src/main/java/com/ibm/as400/access/PortMapper.java index 301a1ac3f..046b6e0c8 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/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/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); + } + } + } + } } From b0b7cc4ece8674c6659b573d595fca1bb4876221 Mon Sep 17 00:00:00 2001 From: Nadir K Amra Date: Wed, 28 Aug 2024 01:04:28 -0500 Subject: [PATCH 03/10] Reformatting Signed-off-by: Nadir K Amra --- .../as400/access/ProfileHandleImplNative.java | 95 ++-- .../auth/ProfileHandleCredential.java | 446 +++++++++--------- .../auth/ProfileHandleImplRemote.java | 49 +- 3 files changed, 281 insertions(+), 309 deletions(-) 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/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); + } } From 6801aef070c5b85d2e0e32268d2471d1b1323c85 Mon Sep 17 00:00:00 2001 From: Nadir K Amra Date: Wed, 28 Aug 2024 01:08:42 -0500 Subject: [PATCH 04/10] Kerberos support for user handles Signed-off-by: Nadir K Amra --- .../ibm/as400/access/IFSUserHandle2Rep.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/main/java/com/ibm/as400/access/IFSUserHandle2Rep.java 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; + } +} From 88d2fc449d0d5321255968f7bcb4fe220e7d975e Mon Sep 17 00:00:00 2001 From: Nadir K Amra Date: Fri, 30 Aug 2024 18:07:46 -0500 Subject: [PATCH 05/10] MFA - enhanced profile token and additional factor Signed-off-by: Nadir K Amra --- src/main/java/com/ibm/as400/access/AS400.java | 172 ++++- .../ibm/as400/access/AS400GenAuthTknDS.java | 32 +- .../com/ibm/as400/access/AS400ImplRemote.java | 158 ++++- .../ibm/as400/access/AS400JDBCDataSource.java | 46 +- .../com/ibm/as400/access/AS400JDBCDriver.java | 53 +- .../access/AS400JDBCManagedDataSource.java | 38 +- .../com/ibm/as400/access/AS400StrSvrDS.java | 73 ++- .../com/ibm/as400/access/ClassDecoupler.java | 4 +- .../com/ibm/as400/access/ConnectionList.java | 8 +- .../access/DDMSECCHKRequestDataStream.java | 61 +- .../access/ManagedProfileTokenVault.java | 589 +++++++++--------- .../as400/access/ProfileTokenImplNative.java | 111 +++- .../access/SignonGenAuthTokenRequestDS.java | 45 +- .../com/ibm/as400/security/SecurityMRI.java | 3 + .../AS400BasicAuthenticationCredential.java | 73 +++ .../auth/DefaultProfileTokenProvider.java | 69 +- .../security/auth/ProfileTokenCredential.java | 466 +++++++++++++- .../as400/security/auth/ProfileTokenImpl.java | 179 ++++++ .../security/auth/ProfileTokenImplRemote.java | 216 ++++++- 19 files changed, 1932 insertions(+), 464 deletions(-) diff --git a/src/main/java/com/ibm/as400/access/AS400.java b/src/main/java/com/ibm/as400/access/AS400.java index 9e6f87dc3..164263a21 100644 --- a/src/main/java/com/ibm/as400/access/AS400.java +++ b/src/main/java/com/ibm/as400/access/AS400.java @@ -1107,6 +1107,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. *

@@ -1987,7 +2005,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(); @@ -2094,7 +2112,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); @@ -2107,6 +2162,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) { @@ -2638,7 +2695,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); @@ -2658,6 +2749,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) { @@ -2682,7 +2776,7 @@ public ProfileTokenCredential getProfileToken(int tokenType, int timeoutInterval return profileToken; } - + /** * Authenticates the given user profile and password and returns a corresponding ProfileTokenCredential if * successful. @@ -2816,6 +2910,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); @@ -2840,6 +2978,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) { @@ -3180,6 +3321,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. @@ -5589,11 +5746,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/AS400ImplRemote.java b/src/main/java/com/ibm/as400/access/AS400ImplRemote.java index 59a9d95a2..18aa2c7b7 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,7 @@ public SignonInfo changePassword(String systemName, boolean systemNameLocal, } // Get a socket connection. - // TODO AMRA - need to first authenticate to hostcnn + // TODO AMRA - need to first authenticate to hostcnn? boolean needToDisconnect = (signonServer_ == null); signonConnect(); @@ -752,6 +753,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(); @@ -940,9 +951,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 +972,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); @@ -1111,11 +1127,16 @@ else if (passwordLevel_ < 4) } } - 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 +1154,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 +1169,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 +1193,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 +1209,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() @@ -1578,10 +1626,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 +1684,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); @@ -1689,9 +1741,8 @@ synchronized AS400Server getConnection(int service, int overridePort, boolean fo 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_); + socketContainer = PortMapper.getServerSocket((systemNameLocal_) ? "localhost" : systemName_, + AS400.HOSTCNN, overridePort, useSSLConnection_, socketProperties_, mustUseNetSockets_); connectionID = socketContainer.hashCode(); inStream = socketContainer.getInputStream(); @@ -3519,7 +3570,7 @@ 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? + // TODO AMRA can/should we share signoninfo object? signonInfo_ = new SignonInfo(); signonInfo_.currentSignonDate = signonRep.getCurrentSignonDate(); signonInfo_.lastSignonDate = signonRep.getLastSignonDate(); @@ -3841,8 +3892,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 +3908,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 +3930,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 +5159,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 8960d8555..b1b4e844d 100644 --- a/src/main/java/com/ibm/as400/access/AS400JDBCDriver.java +++ b/src/main/java/com/ibm/as400/access/AS400JDBCDriver.java @@ -541,10 +541,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; @@ -751,12 +748,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 @@ -941,10 +933,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); @@ -1186,33 +1176,18 @@ static AS400 initializeAS400(JDDataSourceURL dataSourceUrl, // Create the AS400 object, so we can create a Connection via loadImpl2. AS400 as400 = null; - try { - if (secure) - { - 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); - - } - else + try { - 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); + 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); } - } 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/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/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/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/ProfileTokenImplNative.java b/src/main/java/com/ibm/as400/access/ProfileTokenImplNative.java index fb9be876c..94345beba 100644 --- a/src/main/java/com/ibm/as400/access/ProfileTokenImplNative.java +++ b/src/main/java/com/ibm/as400/access/ProfileTokenImplNative.java @@ -120,6 +120,15 @@ 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); + } + + + @Override + public 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,9 +143,17 @@ 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); } /** @@ -188,10 +205,41 @@ 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); + } + + @Override + public 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 +269,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); @@ -391,13 +476,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/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/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/ProfileTokenCredential.java b/src/main/java/com/ibm/as400/security/auth/ProfileTokenCredential.java index dcabc9140..7abcc9a24 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,13 @@ 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.ProfileTokenImplNative; import com.ibm.as400.access.Trace; import java.beans.PropertyVetoException; +import java.util.Arrays; import java.util.Random; /** @@ -189,18 +192,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 +242,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 +279,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 +322,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 +412,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 +594,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 +612,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 +645,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 +663,8 @@ void invalidateProperties() { super.invalidateProperties(); token_ = null; + verificationID_ = null; + localIPAddress_ = null; } @Override @@ -1011,7 +1164,12 @@ 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); + + setToken(impl.generateToken(name, passwordSpecialValue, additionalAuthenticationFactor_, authenticationIndicator_, getVerificationID(), + getRemoteIPAddress(), getRemotePort(), getLocalIPAddress(), getLocalPort(), + getTokenType(), getTimeoutInterval())); + setTokenCreator(ProfileTokenCredential.CREATOR_NATIVE_API); // If successful, all defining attributes are now set. Set the impl for // subsequent references. @@ -1201,8 +1359,13 @@ public void setTokenExtended(String name, char[] password) throws PropertyVetoEx ProfileTokenImpl impl = (ProfileTokenImpl)getImplPrimitive(); // Generate and set the token value - setToken(impl.generateTokenExtended(name, password, + setToken(impl.generateTokenExtended(name, password, getAdditionalAuthenticationFactor(), getVerificationID(), + getRemoteIPAddress(), getRemotePort(), getLocalIPAddress(), getLocalPort(), getTokenType(), getTimeoutInterval())); + + // When remote, it will be already set to CREATOR_FILE_SERVER. + if (impl instanceof ProfileTokenImplNative) + setTokenCreator(ProfileTokenCredential.CREATOR_NATIVE_API); // If successful, all defining attributes are now set. // Set the impl for subsequent references. @@ -1307,4 +1470,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_FILESERVER + *
  • CREATOR_NATIVEAPI + *
+ * + */ + 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 type The creator of the token. Possible values are defined as fields on this + * class: + *

    + *
  • CREATOR_UNKNOWN + *
  • CREATOR_FILESERVER + *
  • CREATOR_NATIVEAPI + *
+ * + * @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(tokenCreator); + Integer typ = Integer.valueOf(tokenCreator); + fireVetoableChange("tokenCreator", old, typ); + creator_ = tokenCreator; + firePropertyChange("tokenCreator", old, typ); + } + + /** + * 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..9a1a8b3ae 100644 --- a/src/main/java/com/ibm/as400/security/auth/ProfileTokenImpl.java +++ b/src/main/java/com/ibm/as400/security/auth/ProfileTokenImpl.java @@ -96,6 +96,107 @@ 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 additionalAuthenticationFactor The additional authentication factor + * for the user. + * Ignored for IBM i 7.5 and older releases. + * + * @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 type The type of token. Possible types are + * defined as fields on the + * ProfileTokenCredential class: + *

    + *
  • TYPE_SINGLE_USE + *
  • TYPE_MULTIPLE_USE_NON_RENEWABLE + *
  • TYPE_MULTIPLE_USE_RENEWABLE + *
+ *

+ * + * @param timeoutInterval The number of seconds to expiration. + * + * @return The token bytes. + * + * @exception RetrieveFailedException If errors occur while generating the + * token. + * + */ + 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; + /** * Generates and returns a new profile token based on the provided information * using a password string. @@ -125,6 +226,84 @@ 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 additionalAuthenticationFactor 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. + * + * + * @param type The type of token. Possible types are + * defined as fields on the + * ProfileTokenCredential class: + *

    + *
  • ProfileTokenCredential.TYPE_SINGLE_USE + *
  • ProfileTokenCredential.TYPE_MULTIPLE_USE_NON_RENEWABLE + *
  • ProfileTokenCredential.TYPE_MULTIPLE_USE_RENEWABLE + *
+ * + * @param timeoutInterval The number of seconds to expiration. + * + * @return The token bytes. + * @exception RetrieveFailedException If errors occur while generating the + * token. + */ + byte[] generateTokenExtended(String uid, char[] password, char[] additionalAuthenticationFactor, String verificationId, + String remoteIpAddress, int remotePort, String localIpAddress, int localPort, int type, int timeoutInterval) + throws RetrieveFailedException; + /** * 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..8585fafe4 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,8 @@ package com.ibm.as400.security.auth; +import java.io.IOException; + import com.ibm.as400.access.*; /** @@ -69,6 +71,16 @@ 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); + + } + + @Override + public 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 = " "; + + boolean isRemoteIPNull = (remoteIpAddress == null || remoteIpAddress.length() == 0); + if (isRemoteIPNull) remoteIpAddress = " "; + + boolean isLocalIPNull = (localIpAddress == null || localIpAddress.length() == 0); + if (isLocalIPNull) localIpAddress = " "; - ProgramParameter[] parmlist = new ProgramParameter[6]; + 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); @@ -196,14 +280,106 @@ 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); + } + + /** + * 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 additionalAuthenticationFactor 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. + * This parameter is not used in a remote + * environment. The host server will + * retrieve the port from the network 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. + * This parameter is not used in a remote + * environment. The host server will + * retrieve the information from the system. + * + * @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. + * This parameter is not used in a remote + * environment. The host server will + * retrieve the port from the network connection. + * + * + * @param type The type of token. Possible types are + * defined as fields on the + * ProfileTokenCredential class: + *
    + *
  • ProfileTokenCredential.TYPE_SINGLE_USE + *
  • ProfileTokenCredential.TYPE_MULTIPLE_USE_NON_RENEWABLE + *
  • ProfileTokenCredential.TYPE_MULTIPLE_USE_RENEWABLE + *
+ * + * @param timeoutInterval The number of seconds to expiration. + * + * @return The token bytes. + * @exception RetrieveFailedException If errors occur while generating the + * token. + */ + @Override + public byte[] 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; try { - tkn = getCredential().getSystem().getProfileToken(uid, pwd, type, timeoutInterval).getToken(); + tkn = getCredential().getSystem().getProfileToken(uid, password, additionalAuthenticationFactor, type, timeoutInterval, + verificationId, remoteIpAddress).getToken(); } catch (AS400SecurityException se) { throw new RetrieveFailedException(se.getReturnCode()); @@ -250,13 +426,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); From 10e62f2ec01e8adbe07c05276e356b104fb2140c Mon Sep 17 00:00:00 2001 From: Nadir K Amra Date: Wed, 4 Sep 2024 17:58:27 -0500 Subject: [PATCH 06/10] Enhanced profile token support for profile tokens Signed-off-by: Nadir K Amra --- .../com/ibm/as400/access/AS400ImplRemote.java | 554 +++++++++--------- .../com/ibm/as400/access/CurrentUser.java | 32 +- .../as400/access/ProfileTokenImplNative.java | 75 ++- .../ibm/as400/access/ProfileTokenVault.java | 1 + .../security/auth/ProfileTokenCredential.java | 32 +- .../as400/security/auth/ProfileTokenImpl.java | 161 +---- .../security/auth/ProfileTokenImplRemote.java | 217 ++++--- 7 files changed, 505 insertions(+), 567 deletions(-) diff --git a/src/main/java/com/ibm/as400/access/AS400ImplRemote.java b/src/main/java/com/ibm/as400/access/AS400ImplRemote.java index 18aa2c7b7..37dc9aded 100644 --- a/src/main/java/com/ibm/as400/access/AS400ImplRemote.java +++ b/src/main/java/com/ibm/as400/access/AS400ImplRemote.java @@ -370,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(); @@ -880,6 +879,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. @@ -1002,129 +1002,129 @@ 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); - } } if (Trace.isTraceOn()) Trace.log(Trace.DIAGNOSTIC, "AS400ImplRemote generating profile token for user: " + userId); @@ -1549,13 +1549,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"); @@ -1708,128 +1707,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); @@ -1838,6 +1741,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() @@ -3351,7 +3383,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_; @@ -3407,18 +3439,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); @@ -3449,10 +3481,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 && @@ -3570,7 +3600,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? signonInfo_ = new SignonInfo(); signonInfo_.currentSignonDate = signonRep.getCurrentSignonDate(); signonInfo_.lastSignonDate = signonRep.getLastSignonDate(); @@ -3782,8 +3811,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 @@ -3823,9 +3852,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(); @@ -3858,7 +3886,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(); 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/ProfileTokenImplNative.java b/src/main/java/com/ibm/as400/access/ProfileTokenImplNative.java index 94345beba..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.*; @@ -122,10 +123,8 @@ public byte[] generateToken(String uid, char[] pwd, int type, int timeoutInterva 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); } - - @Override - public byte[] generateToken(String uid, int pwdSpecialValue, char[] additionalAuthenticationFactor, int authenticationIndicator, + 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 { @@ -156,6 +155,40 @@ public byte[] generateToken(String uid, int pwdSpecialValue, char[] additionalAu 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. @@ -208,8 +241,7 @@ public byte[] generateTokenExtended(String uid, char [] pwd, int type, int timeo return generateTokenExtended(uid, pwd, null, null, null, 0, null, 0, type, timeoutInterval); } - @Override - public byte[] generateTokenExtended(String uid, char[] pwd, char[] additionalAuthenticationFactor, + 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 { @@ -333,6 +365,39 @@ public byte[] generateTokenExtended(String uid, char[] pwd, char[] additionalAut 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. 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/security/auth/ProfileTokenCredential.java b/src/main/java/com/ibm/as400/security/auth/ProfileTokenCredential.java index 7abcc9a24..80e056506 100644 --- a/src/main/java/com/ibm/as400/security/auth/ProfileTokenCredential.java +++ b/src/main/java/com/ibm/as400/security/auth/ProfileTokenCredential.java @@ -18,7 +18,6 @@ import com.ibm.as400.access.AuthenticationIndicator; import com.ibm.as400.access.ExtendedIllegalArgumentException; import com.ibm.as400.access.ExtendedIllegalStateException; -import com.ibm.as400.access.ProfileTokenImplNative; import com.ibm.as400.access.Trace; import java.beans.PropertyVetoException; import java.util.Arrays; @@ -1166,10 +1165,7 @@ public void setToken(String name, int passwordSpecialValue) throws PropertyVetoE // Generate and set the token value if (Trace.isTraceOn()) Trace.log(Trace.DIAGNOSTIC, "ProfileTokenCredential generating profile token w/special value for user: " + name); - setToken(impl.generateToken(name, passwordSpecialValue, additionalAuthenticationFactor_, authenticationIndicator_, getVerificationID(), - getRemoteIPAddress(), getRemotePort(), getLocalIPAddress(), getLocalPort(), - getTokenType(), getTimeoutInterval())); - setTokenCreator(ProfileTokenCredential.CREATOR_NATIVE_API); + impl.generateToken(name, passwordSpecialValue, this); // If successful, all defining attributes are now set. Set the impl for // subsequent references. @@ -1359,13 +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, getAdditionalAuthenticationFactor(), getVerificationID(), - getRemoteIPAddress(), getRemotePort(), getLocalIPAddress(), getLocalPort(), - getTokenType(), getTimeoutInterval())); - - // When remote, it will be already set to CREATOR_FILE_SERVER. - if (impl instanceof ProfileTokenImplNative) - setTokenCreator(ProfileTokenCredential.CREATOR_NATIVE_API); + impl.generateTokenExtended(name, password, this); // If successful, all defining attributes are now set. // Set the impl for subsequent references. @@ -1703,8 +1693,8 @@ public int getAuthenticationIndicator() { * class: *
    *
  • CREATOR_UNKNOWN - *
  • CREATOR_FILESERVER - *
  • CREATOR_NATIVEAPI + *
  • CREATOR_SIGNON_SERVER + *
  • CREATOR_NATIVE_API *
* */ @@ -1724,12 +1714,12 @@ public int getTokenCreator() { * This property cannot be changed once a request initiates a connection for the * object to the IBM i system (for example, refresh). * - * @param type The creator of the token. Possible values are defined as fields on this + * @param tokenCreator The creator of the token. Possible values are defined as fields on this * class: *
    *
  • CREATOR_UNKNOWN - *
  • CREATOR_FILESERVER - *
  • CREATOR_NATIVEAPI + *
  • CREATOR_SIGNON_SERVER + *
  • CREATOR_NATIVE_API *
* * @exception PropertyVetoException If the change is vetoed. @@ -1752,11 +1742,11 @@ public void setTokenCreator(int tokenCreator) throws PropertyVetoException throw new ExtendedIllegalArgumentException("type", ExtendedIllegalArgumentException.RANGE_NOT_VALID); } - Integer old = Integer.valueOf(tokenCreator); - Integer typ = Integer.valueOf(tokenCreator); - fireVetoableChange("tokenCreator", old, typ); + Integer old = Integer.valueOf(creator_); + Integer crt = Integer.valueOf(tokenCreator); + fireVetoableChange("tokenCreator", old, crt); creator_ = tokenCreator; - firePropertyChange("tokenCreator", old, typ); + firePropertyChange("tokenCreator", old, crt); } /** 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 9a1a8b3ae..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) @@ -112,90 +114,18 @@ public interface ProfileTokenImpl extends AS400CredentialImpl * *

* - * @param additionalAuthenticationFactor The additional authentication factor - * for the user. - * Ignored for IBM i 7.5 and older releases. - * - * @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 profileTokenCred The profile token credential to be initialized + * with the token bytes. * - * @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. - * + * @return The profile token credential that was passed in with the token bytes initialized. * - * @param type The type of token. Possible types are - * defined as fields on the - * ProfileTokenCredential class: - *

    - *
  • TYPE_SINGLE_USE - *
  • TYPE_MULTIPLE_USE_NON_RENEWABLE - *
  • TYPE_MULTIPLE_USE_RENEWABLE - *
- *

- * - * @param timeoutInterval The number of seconds to expiration. - * - * @return The token bytes. - * - * @exception RetrieveFailedException If errors occur while generating the - * token. + * @exception RetrieveFailedException If errors occur while generating the token. + * @exception PropertyVetoException If errors occur while setting the profile token credential. * */ - 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; + ProfileTokenCredential generateToken(String uid, int pwdSpecialValue, ProfileTokenCredential profileTokenCred) + throws RetrieveFailedException, PropertyVetoException; /** * Generates and returns a new profile token based on the provided information @@ -235,75 +165,18 @@ byte[] generateToken(String uid, int pwdSpecialValue, char[] additionalAuthentic * * @param password The password for the user * - * @param additionalAuthenticationFactor 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 profileTokenCred The profile token credential to be initialized + * with the token bytes. * - * @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. + * @return The profile token credential that was passed in with the token bytes initialized. * - * @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. - * - * - * @param type The type of token. Possible types are - * defined as fields on the - * ProfileTokenCredential class: - *

    - *
  • ProfileTokenCredential.TYPE_SINGLE_USE - *
  • ProfileTokenCredential.TYPE_MULTIPLE_USE_NON_RENEWABLE - *
  • ProfileTokenCredential.TYPE_MULTIPLE_USE_RENEWABLE - *
- * - * @param timeoutInterval The number of seconds to expiration. - * - * @return The token bytes. - * @exception RetrieveFailedException If errors occur while generating the - * token. + * @exception RetrieveFailedException If errors occur while generating the token. + * @exception PropertyVetoException If errors occur while setting the profile token credential. */ - byte[] generateTokenExtended(String uid, char[] password, char[] additionalAuthenticationFactor, String verificationId, - String remoteIpAddress, int remotePort, String localIpAddress, int localPort, int type, int timeoutInterval) - throws RetrieveFailedException; - + 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 8585fafe4..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,7 @@ package com.ibm.as400.security.auth; +import java.beans.PropertyVetoException; import java.io.IOException; import com.ibm.as400.access.*; @@ -77,8 +78,7 @@ public byte[] generateToken(String uid, int pwdSpecialValue, int type, int timeo } - @Override - public byte[] generateToken(String uid, int pwdSpecialValue, char[] additionalAuthenticationFactor, + 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 { @@ -220,6 +220,39 @@ public byte[] generateToken(String uid, int pwdSpecialValue, char[] additionalAu 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 @@ -281,105 +314,21 @@ 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); + return generateTokenExtended(uid, pwd, null, null, null, 0, null, 0, type, timeoutInterval).getToken(); } - /** - * 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 additionalAuthenticationFactor 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. - * This parameter is not used in a remote - * environment. The host server will - * retrieve the port from the network 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. - * This parameter is not used in a remote - * environment. The host server will - * retrieve the information from the system. - * - * @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. - * This parameter is not used in a remote - * environment. The host server will - * retrieve the port from the network connection. - * - * - * @param type The type of token. Possible types are - * defined as fields on the - * ProfileTokenCredential class: - *
    - *
  • ProfileTokenCredential.TYPE_SINGLE_USE - *
  • ProfileTokenCredential.TYPE_MULTIPLE_USE_NON_RENEWABLE - *
  • ProfileTokenCredential.TYPE_MULTIPLE_USE_RENEWABLE - *
- * - * @param timeoutInterval The number of seconds to expiration. - * - * @return The token bytes. - * @exception RetrieveFailedException If errors occur while generating the - * token. - */ - @Override - public byte[] generateTokenExtended(String uid, char[] password, char[] additionalAuthenticationFactor, + 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, password, additionalAuthenticationFactor, type, timeoutInterval, - verificationId, remoteIpAddress).getToken(); + ptTemp = getCredential().getSystem().getProfileToken(uid, password, additionalAuthenticationFactor, + type, timeoutInterval, + verificationId, remoteIpAddress); } catch (AS400SecurityException se) { throw new RetrieveFailedException(se.getReturnCode()); @@ -388,7 +337,40 @@ public byte[] generateTokenExtended(String uid, char[] password, char[] addition 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 @@ -493,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()); + } } /** From cd5e3ba7ce9a5bcd894745645e5043e440352cfb Mon Sep 17 00:00:00 2001 From: Nadir K Amra Date: Thu, 5 Sep 2024 10:17:18 -0500 Subject: [PATCH 07/10] Get rid of connectToPort methods Signed-off-by: Nadir K Amra --- src/main/java/com/ibm/as400/access/AS400.java | 40 -------- .../java/com/ibm/as400/access/AS400Impl.java | 5 - .../com/ibm/as400/access/AS400ImplProxy.java | 30 ------ .../com/ibm/as400/access/AS400ImplRemote.java | 96 ------------------- 4 files changed, 171 deletions(-) diff --git a/src/main/java/com/ibm/as400/access/AS400.java b/src/main/java/com/ibm/as400/access/AS400.java index 164263a21..455bd33fa 100644 --- a/src/main/java/com/ibm/as400/access/AS400.java +++ b/src/main/java/com/ibm/as400/access/AS400.java @@ -1951,46 +1951,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() { 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 37dc9aded..c97e75bd6 100644 --- a/src/main/java/com/ibm/as400/access/AS400ImplRemote.java +++ b/src/main/java/com/ibm/as400/access/AS400ImplRemote.java @@ -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 { @@ -1360,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) From f3d46bb0389f153e26e860eca240050d14c64b4d Mon Sep 17 00:00:00 2001 From: Parker Young <143426808+pjyoung-ibm@users.noreply.github.com> Date: Wed, 28 Aug 2024 10:03:12 -0500 Subject: [PATCH 08/10] Replace all usages of QGY.LIB APIs with their QSYS.LIB equivalents. In V5R3, the "Open List APIs" were moved from SS1 Option 12 (in library QGY) to SS1 *BASE (in library QSYS). Stub programs were left in QGY to just route the call over to the new QSYS programs, so this has technically continued to work just fine. However, the wording in the V5R3 Memo to Users stated that "These router programs and the library QGY will be removed in a future release. You should change any applications that make library-qualified calls to the Open List APIs in library QGY to call the APIs in library QSYS." As support for connecting to V5R2 was dropped with JTOpen 6.1 (2007), it seems like we can safely do this now. This has the added benefit of removing another dependency on Option 12, and instead shifting that requirement over to the base OS (especially since all these programs did was call things in *BASE, so Option 12 isn't really required for these at all). Ref: https://public.dhe.ibm.com/systems/power/docs/systemi/v5r3/en_US/rzaq9.pdf (page 52) Signed-off-by: Parker Young <143426808+pjyoung-ibm@users.noreply.github.com> --- pcml/RJobList.pcml | 6 +++--- pcml/RJobLog.pcml | 6 +++--- pcml/RMessageQueue.pcml | 6 +++--- pcml/RPrinter.pcml | 2 +- pcml/RPrinterList.pcml | 6 +++--- pcml/RUserList.pcml | 6 +++--- src/main/java/com/ibm/as400/access/ISeriesPrinter.java | 2 +- src/main/java/com/ibm/as400/access/JobList.java | 2 +- src/main/java/com/ibm/as400/access/JobLog.java | 2 +- src/main/java/com/ibm/as400/access/ListUtilities.java | 6 +++--- src/main/java/com/ibm/as400/access/MessageQueue.java | 2 +- src/main/java/com/ibm/as400/access/ObjectList.java | 2 +- src/main/java/com/ibm/as400/access/UserList.java | 2 +- src/main/java/com/ibm/as400/access/list/AspOpenList.java | 2 +- src/main/java/com/ibm/as400/access/list/OpenList.java | 4 ++-- .../java/com/ibm/as400/access/list/SpooledFileOpenList.java | 2 +- src/main/resources/com/ibm/as400/resource/RJobList.pcml | 6 +++--- src/main/resources/com/ibm/as400/resource/RJobLog.pcml | 6 +++--- .../resources/com/ibm/as400/resource/RMessageQueue.pcml | 6 +++--- src/main/resources/com/ibm/as400/resource/RPrinter.pcml | 2 +- src/main/resources/com/ibm/as400/resource/RPrinterList.pcml | 6 +++--- src/main/resources/com/ibm/as400/resource/RUserList.pcml | 6 +++--- 22 files changed, 45 insertions(+), 45 deletions(-) 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/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/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/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/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/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 @@ - + From b1a11eeee32df930ebc01c9b23215991c525f5e5 Mon Sep 17 00:00:00 2001 From: Parker Young <143426808+pjyoung-ibm@users.noreply.github.com> Date: Wed, 28 Aug 2024 10:31:45 -0500 Subject: [PATCH 09/10] Change a few more QGY.LIB API calls to their QSYS.LIB equivalents. Missed these in ed6da9b72c1f05f6d575245b044db3c55f50ae2a because I searched for "QGY.LIB" when making my initial changes, but these used QSYSObjectPathName.toPath() to build the name. See that commit for more details on this change. Signed-off-by: Parker Young <143426808+pjyoung-ibm@users.noreply.github.com> --- src/main/java/com/ibm/as400/access/ValidationList.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 From 5702fe2878e89b0f4b9940763e2282d16753f045 Mon Sep 17 00:00:00 2001 From: Parker Young <143426808+pjyoung-ibm@users.noreply.github.com> Date: Thu, 27 Jun 2024 11:09:59 -0500 Subject: [PATCH 10/10] Add getMessageOption() and setMessageOption() to ProgramCallDocument Allows users to specify how many messages should be returned when calling a program. This option already existed on ProgramCalls, but there was no way to set this through (X)PCML calls. These changes expose that option now. Also made a change to always save the message list after the program call is run. Previously, the message list would only be saved if the program call signaled an error. However, the underlying ProgramCall would always save the message list off no matter the result. So match that behavior for (X)PCML calls. Signed-off-by: Parker Young <143426808+pjyoung-ibm@users.noreply.github.com> --- .../java/com/ibm/as400/data/PcmlDocument.java | 21 ++++++++++ .../java/com/ibm/as400/data/PcmlProgram.java | 29 ++++++++++++- .../ibm/as400/data/ProgramCallDocument.java | 41 ++++++++++++++++++- 3 files changed, 88 insertions(+), 3 deletions(-) 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 - }