How to configure systems to use JSON Web Token

JSON Web Token(JWT) is a technique of authenticating across systems. It depends on trust, and technology similar to TLS and SSL.

The high level picture

A client wants to use a service on a server.

On the client machine

  • The client creates a payload containing authentication information, such as subject(=userid), issuer(=machine/company name), issued time, and expiry time.
  • This payload is signed using a private key. Where signing is doing a checksum on the payload and encrypted the checksum with the private key.
  • The payload and signature are sent to the server.

On the server machine

  • The server has a list of definitions, containing a issuer and other information, the name of a keyring, and the name of a certificate. The trust comes from if you trust the client then you store the public key for the client in the keyring. If you do not trust the client, you do not make the certificate available.
  • When the data from the client machine arrives, the list of definitions is processed until a match is found. It checks the issuer and other fields, (it can use filters) and if they match, checks the signature of the payload to find the public key in the keyring, and validates the signature.
  • If everything matches, information from the payload and the definition on the server are used to look up a list of subject, and issuer to get the RACF userid to be used.
  • The thread switches to the userid and does the application work.

How to create a JWT

  • z/OSMF can do this for you
  • You can create your own on z/OS – but you need to know how to do a checksum and encrypt with a private key. ICSF provides services which do this.
  • use Python.

Use z/OSMF

In the logical server.xml file

<featureManager> 
<feature>jwtSso-1.0</feature>
</featureManager>

<jwtSso cookieName="jwtToken"
jwtBuilderRef="zOSMFBuilder"
includeLtpaCookie="true"
useLtpaIfJwtAbsent="true"
/>

<jwtBuilder id="zOSMFBuilder"
issuer="zOSMF"
keyAlias="CONN2.IZUDFLT"
expiresInSeconds="10"
jwkEnabled="false"
signatureAlgorithm="RS384"
/>

<mpJwt id="myMpJwt"
issuer="zOSMF"
wksUri="https://${izu.jwks.hostname}:${izu.https.port}/jwt/ibm/api/zOSMFBuilder/jwk"
signatureAlgorithm="RS384"
/>

Interesting fields are

  • issuer=”zOSMF” this is put into the payload. It is used to help identify the realm
  • keyAlias=”CONN2.IZUDFLT”. This is the certificate with the private key in the keyring which is used to sign the JWT
  • expiresInSeconds=”10″ how long the JWT is valid for. During testing I set this to 600 seconds.
  • jwksUri=”https://${izu.jwks.hostname}:${izu.https.port}/jwt/ibm/api/zOSMFBuilder/jwk” if you want the server to issue a request to the client asking for validation, this is the URL the server would use.

Use Python

This is very easy

from datetime import datetime, timezone, timedelta
import jwt

# open the private key and read the contents
pemfile = open("/home/colinpaice/ssl/ssl2/colinpaice.key.pem", 'r')
keystring = pemfile.read()
pemfile.close()

# Create the header. Specify the type, and the encryption algorithm
header= {
"typ": "JWT",
"alg": "RS256"
}

# get time in number of seconds since "day 0"
now = datetime.now(timezone.utc)
payload = {
"token_type": "Bearer",
"sub": "ADCDC", # userid
"upn": "ADCDC",
"realm": "PYTHON",
"iss": "PYTHON", # issuer
"iat": now,
"exp": now + timedelta(seconds=6000) # valid for 6000 seconds

}

# encrypt it and create the jwt
token = jwt.encode(payload, keystring, algorithm='RS256')

print(token) # display it

Backend server set up

The page Configuration example: Securing a Liberty web application with a JWT and CICS transaction security explains how to do it for Liberty in a CICS region.

For my MQWEB server I added to mqwebuser.xml

<featureManager> 
<feature>openidConnectClient-1.0</feature>
</featureManager>

<openidConnectClient id="RSCOOKIE"
clientId="COLINCOOK"
realmName="zOSMF"
inboundPropagation="required"
issuerIdentifier="zOSMF"
mapIdentityToRegistryUser="false"
signatureAlgorithm="RS384"
jwkEndpointUrl="https://10.1.1.2:10443/jwt/ibm/api/zOSMFBuilder/jwk"
trustAliasName="CONN2.IZUDFLT"
trustStoreRef="defaultKeyStore"
userIdentifier="sub"
>
</openidConnectClient>

mapIdentityToRegistryUser=”false” says use the realm in this definition and the userid(subject) from the payload to look up the in the RACF RACMAP to get the userid.

If you specify “true” it uses the userid(subject) from the payload, and the sysplex name. This means a userid “COLIN” from a z/OS system, and a userid “COLIN” from Linux – get the same userid on z/OS.

Note: If they public key for the JWT is not in the keyring, add it, and restart the server.

Map subject(userid) and realm to get a userid.

See RACMAP (Create, delete, list, or query a distributed identity filter)

For example

RACMAP ID(IBMUSER) MAP USERIDFILTERNAME("IBMUSER")  REGISTRY("zPROD") WITHLABEL("zPROD")
RACMAP ID(NOONE) MAP USERIDFILTERNAME("IBMUSER") REGISTRY("LINUX") WITHLABEL("LINUX")
RACMAP ID(ZILTCH) MAP USERIDFILTERNAME("*") REGISTRY("*") WITHLABEL("CATCHALL")

The registry is the realm name from the server definitions.

if a valid userid is returned from the mapping, the thread is changed to run as that userid, and execute the application work – as that userid.

The backend server is a little more complex

You need a <openidConnectClient ../> for each “client” system the server supports.

You can specify one <authFilter../> to restrict what the <openidConnectClient ../> processes. For example if can be an IP address(10.1.0.2) or a range (10.1.*.*), or restrict it by URL.

For the <openidConnectClient ../> definition to match it needs

  • The signature validated – so the public key needs to be in a keyring. (You could have a different keyring for each realm)
  • The issuer from the payload to match the definition
  • The input data must pass the authFilter.

You need to plan for

  • the realms you need,
  • the mapping of subjects and realms to userids in the server,
  • public keys and keyrings

Some useful links

My Server’s definition

<featureManager> 
<feature>transportSecurity-1.0</feature>
<feature>openidConnectClient-1.0</feature>
</featureManager>

<openidConnectClient id="RSCOOKIE"

headerName="colin"

clientId="COLINCOO2"
realmName="zOSMF"
inboundPropagation="required"
issuerIdentifier="zOSMF"
mapIdentityToRegistryUser="false"
signatureAlgorithm="RS384"
jwkEndpointUrl="https://10.1.1.2:10443/jwt/ibm/api/zOSMFBuilder/jwk"
trustAliasName="CONN1.IZUDFLT"
trustStoreRef="defaultKeyStore"
userIdentifier="sub"
>
<authFilter id="afint">
<remoteAddress id="myAddress" ip="10.1.0.2" matchType="equals" />
</authFilter >

</openidConnectClient>

<keyStore
id="defaultKeyStore"
filebased="false"
location="safkeyring://IZUSVR/CCPKeyring.IZUDFLT"
password="password" readOnly="true" type="JCERACFKS"
/>

Where

  • headerName=”colin”. My curl request has -H “colin: $token” where token is the bash variable with the is the JWT token. Without this, the request needs -H “Authorization: Bearer $token”
  • realmName=”zOSMF” used with the userid to lookup in the RACMAP table for the z/OS userid to use
  • signatureAlgorithm=”RS384″ matches the value in the JWT. (I had problems using RS256)
  • trustAliasName=”CONN1.IZUDFLT” the name of the certificate to use on the server
  • trustStoreRef=”defaultKeyStore” points to the keyring definition

Setting mqweb trace on z/OS and other useful hints on tracing

I spent time trying to track down in the MQWEB server, why my JSON Web Token was not working as I expected . I found it hard to trace the problem, and tried many trace parameters till I found some which provided the information I needed. It was not an easy journey.

This blog entry covers

How to set the trace

I tried the trace

setmqweb properties -k traceSpec -v ...

described here, but this was slow and had the side effect in that it inserted blank lines to the mqwebuser.xml file, and made each xml entry one long line, and lost all of my nice formatting!

Alternatives

Using the z/OS console command

For example

f csq9web,LOGGING='*=info:zos.*=finest' 

Issuing the command from the console means that the mqwebuser.xml file is not changed, and when you restart the MQWEB server it comes up with what you specified – rather than the last setmqweb command.

Editing the mqwebuser.xml manually

For MQ the trace definition is taken from the variable traceSpec.

For example

<variable 
name="traceSpec"
value="*=info"
/>

I just added some more definitions

<variable 
name="traceSpec"
value="*=info"
/>

<variable
name="traceSpec"
value="*=info:com.ibm.ws.webcontainer.security.ProviderAuthenticationResult=all"
/>

The last value is used – so my trace definition was used.

I could easily move these around to get different traces.

When the MQWEB server is restarted, the last definition will be used – so remember to move the normal entry to the end of the definitions (with value=”=info” or similar).

Using a more complex value

The trace entry I was given was over 150 characters long, and was difficult to enter into the file or on the command line. I had to enter it in pieces, and kept getting blanks in the wrong place. You can use symbol substitution

<variable 
name="t1"
value="*=info"
/>

<variable
name="t2"
value="zos.*=all"
/>
<variable
name="traceSpec"
value="${t1}:${t2}"
/>

This produced a trace *=info:zos.=all

Whenever you change the trace, check in the message.log or trace.log file, and fix any problems.

If the update is successful, there should be an entry in the job log, such as

[AUDIT ] CWWKG0017I: The server configuration was successfully updated in 0.265 seconds.

If you do not get this, then check the log files (again).

The trace files did not have enough information in them.

Some of the traces I was given to solve my problem produced thousands of lines of output. It was hard to find the records of interest.

One record was

∇8/9/25, 16:18:52:470 GMT   ∆ 00000102 Authenticatio <  getStatus Exit 
FAILURE

Where Authenticatio is a small part of the trace id.

I specified

 <logging traceFormat="ENHANCED" /> 

See traceFormat and value ENHANCED.

This gave me

8/9/25, 16:22:37:516 GMT ∆ 000000f1 id=dd803e1d com.ibm.ws.webcontainer.security.AuthenticationResult < getSta…
FAILURE

You can see a more complete trace entry (com.ibm.ws.webcontainer.security.AuthenticationResult ) for the record.

You can now specify this trace entry in the <variable name=”t2″ value=”…” . And restrict which entries you want, for example value=”com.ibm.ws.webcontainer.security.*=all” .

Once I had found which trace records I wanted, I went back traceFormat=”SIMPLE” because the output was easier to read.

Useful trace entries

To get JWT information

io.openliberty.security.*=all

Why JWT faildation failed

org.apache.http.client.*=all

My definitions for these were

name="t1" 
value="*=info"
/>

<variable
name="t3"
value="org.apache.http.client.*=all"
/>

<variable
name="t4"
value="io.openliberty.security.*=all"
/>

<variable
name="traceSpec"
value="${t1}:${t3}:${t4}"
/>

<logging traceFormat="SIMPLE" />

What level of trace do you need – too much info?

A trace entry with value=”io.openliberty.security.*=all” can produce a lot of output.

You may get enough to debug your problem using value=”io.openliberty.security.*=fine“, value=”io.openliberty.security.*=finer“, or value=”io.openliberty.security.*=finest

Putting your definitions in a separate file

I put some of my definitions in a separate file trace.xml

<server> 
<!-- always specify this one -->
<variable
name="t1"
value="*=info"
/>
...
</server>

You can incorporate these changes using

<include location="trace.xml"/> 

<!-- and use the definitions -->
<variable
name="traceSpec2"
value="${t1}:${t2}:${t3}"
/>

If you change the trace.xml file, it will not cause MQWEB to reprocess it. You need to make a change (such as change a blank to a blank) to mqwebuser.xml for MQWEB to notice and process the file.

Displaying the trace record so it fits in the window

A trace record can be hundred of characters long – and requires to scroll sideways many pages.

The blog post Processing lines in ASCII files in ISPF edit macros has a useful ISPF edit macro which displays a row from the file, flowed so it fits into the screen width – and as the trace files are in ASCII, converts the output to displayable EBCDIC.

This made looking at the data much easier.

How I do tracing

I am the only person on my z/OS machine, which make debugging problems much easier. With all my attempts to resolve problems, I found my trace log and message log files were getting too large for me to use ISPF edit on them.
Below is how I use the trace files, it generally works.

  • I edit the file, delete most of the record to leave one or two records, then save it.
  • I run my test
  • I edit the file again and it should have only the entries added since the first step.

Note, if you delete the file the logging code, detects the file is deleted, and stops writing to it. If you delete rows, then records are written to the end of the current file.

MQWEB and passtickets

The RACF PassTicket is a (one-time-only/short duration) password that is generated by a requesting product or function. It is an alternative to the RACF password.
You create a passticket specifying the userid and the application, and a one off password is generated. You can specify a validity period.

By default the passticket has replay protection – in that once used, the passticket cannot be used again, and so prevent replay. You can allow a passticket to be used more than once either by specifying APPLDATA(‘NO REPLAY PROTECTION’) for basic pass tickets, or REPLAY(YES) for enhanced pass tickets.

The server can use the function __login__applid() (or similar function) to run a thread as the specified userid. You pass the userid, password (pass ticket) and the application to use.

The MQWeb server is code running on top of Liberty Web server.

For my MQWeb server, running as started task CSQ9WEB, it was configured so my mqweb/mqwebuser.xml configuration file had <safCredentials profilePrefix=”MQWEB“…./>

I created a passticket for my userid COLIN, and application MQWEB, and I was able to logon to the the MQWEB server using userid COLIN and with the pass ticket as my password.

Should all red flags be green?

This question came out of a discussion on an MQ forum, where the question was if MQ does one time delivery, how come he got the same message twice?

Different sorts of MQ gets.

There are a variety of patterns for getting a message.

  • Destructive get out of sync-point. One application can get the message. It is removed from the system. As part of the MQGET logic there is a commit of the get so once it has gone it has gone. This is usually used for non persistent message. Persistent messages are usually processed within sync-point, but there are valid cases when the get of a persistent out of sync-point is valid.
  • Destructive get within sync-point. One application can get the message. The queue manger holds a lock on the message which makes it invisible to other applications. When the commit is issued (either explicitly or implicitly) , the message is deleted. If the application rolls back (either implicitly of explicitly) the message becomes visible on the queue again, and the lock released.
  • Browse. One or more applications can get the message when using the get-with-browse option. Sync-point does not come into the picture, because there are no changes to the message.
  • One problem with get-with-browse is you can have many application instances browsing the queue, and they may do the same work on a message, wasting resources. To help with this, there is cooperative browse. This is effectively browse and hide. This allows a queue monitor application to browse the message, and start a transaction saying process “this” message. A second instance of the queue monitor will not see the message. If the message has not been got within a specified time interval the “hide” is removed, and so the message becomes visible. See Avoiding repeated delivery of browsed messages.

The customer’s question was, that as the get was destructive, how come the message was processed twice – could this be a bug in MQ?

The careful reader may have spotted why a message can be got twice.

Why the message was processed “twice”.

Consider an application which does the following

MQGET destructive, in sync-point

Write “processed message id …. ” to the log
Update DB2 record
Commit

You might the see following in the log

processed message id x’aabbccdd01′.
processed message id .x’aabbccdd02′.
processed message id x’eeffccdd17′. .

Expanding the transaction to give more details

MQGET destructive, in sync-point
Write “processed message id …. ” to the log
Update DB2 record

If DB2 update worked then commit
else backout

If there was a DB2 problem, you could get the following on the log:

processed message id x’aabbccdd01′.
processed message id x’aabbccdd01′.
processed message id x’eeffccdd17′. .

You then say “Ah Ha – MQ delivered the message twice”. Which is true, but you should be saying “Ah Ha – MQ delivered the message but the application didn’t want it. The second time MQ delivered it, the application processed it”. Perhaps change the MQ phrase to “MQ does one time successful delivery“.

Why is this blog post called Should all red flags be green?

A proper programmer (compared to a coder), will treat a non successful transaction as a red flag, and take an action because it is an abnormal situation. For example write a message to an error log

  1. Transaction ABCD rolled back because “DB2 deadlock on ACCOUNTS table”
  2. Transaction ABCD rolled back because “MQ PUT to REPLYQUEUE failed – queue full”
  3. Transaction ABCD rolled back because “CICS is shutting down”

The Architects and systems programmers can look at these messages and take action.

For example with DB2, investigate the lock held duration. Can you reduce the time the lock is held, perhaps by rearranging with within a unit of work, for example “MQGET, MQPUT reply, DB2 update, commit” instead of “MQGET, DB2 update, MQPUT of reply, commit.

For MQ queue full, make the maximum queue depth bigger, or find out why the queue wasn’t being drained.

CICS shutting down.You may always get some reasons to rollback.

Once you have put an action plan in place to reduce the number of red flags, you can mark the action item complete, change its status from red to green and keep the project managers happy (who love green closed action items).

Note: This may be a never ending task!

After thought

In the online discussion, Morag pointed out that perhaps the same message was put twice. Which would show the same symptoms. This could have been due to a put out of syncpoint, and the transaction rolled back.

Debugging AT-TLS session problems

I deliberately misconfigured AT-TLS to see how easy it would be to identify and resolve the problems from an AT-TLS perspective. It turned out worse than I expected. There is little information on the z/OS to help you.

I configured TTLSEnvironmentAction {trace 255 } (see the bottom of this blog) and refreshed the PAGENT. I had configured SYSLOGD so records for *.TCPIP.*.* went to /var/log/TCPIP.

I reran my MQ client application and got

  • from MQ on Linux, in file /var/mqm/errors/AMQERR01.LOG return code 2393 (MQRC_SSL_INITIALIZATION_ERROR).
  • On Linux there was a file /var/mqm/trace/AMQ.SSL.TRC – which only IBM can format!
  • From TCPIP on z/OS EZD1287I TTLS Error RC: 402 Initial Handshake LOCAL: 10.1.1.2..1414 REMOTE: 10.1.0.2..53900 JOBNAME: CSQ9CHIN RULE: REMOTE-TO-CSQ1 USERID: START1 GRPID: 0000001B ENVID: 0000000B CONNID: 0000006E This give
    • the address of my client,
    • the name of the chinit
    • which AT-TLS rule was used

The message EZD1287I TTLS Error RC: 402 Initial Handshake pointed me to Cryptographic Services System Secure Sockets Layer Programming – No SSL cipher specifications. The first reason was

The client and server cipher specifications do not contain at least one value in common. Client and server cipher specifications might be limited depending on which System SSL FMIDs are installed. See Cipher suite definitions for more information. Server cipher specifications are dependent on the type of algorithms that are used by the server certificate (RSA, DSA, ECDSA, or Diffie-Hellman), which might limit the options available during cipher negotiation.

MQ Trace

I took an MQ trace and formatted it. I used grep to find which file had “Cipher” in it.

Within this file I searched for Start of GSKit TLS Handshake Transcript.

This had information sent to the server as part of the handshake, and further down it had the reason code. You can see from the example that the fields and their values have been displayed (so cipher spec 003c is displayed as tls_rsa_with_aes_128_cbc_sha256)

Start of GSKit TLS Handshake Transcript (1119 bytes)
 <client_hello>
 client_version 
 TLSV12
 random 
   gsksslDissector_32Bits
   7f9d66d8
   gsksslDissector_Opaque
   Length: 28
   3E 5B 45 66 EE A3 C1 9F FB 81 0C 2F 38 19 DF 95     >[Ef......./8...
   5A 1B 54 CC B8 CB B6 C9 87 39 5E 88                 Z.T......9^.
 session_id 
 Length: 00
 cipher_suites 
 Length: 04
 00 FF 00 3C                                         ...<
 tls_ri_scsv,tls_rsa_with_aes_128_cbc_sha256
 compression_methods 
 Length: 01
 00                                                  .
 Extensions
 Length: 74
 00 0D 00 18 00 16 06 01 05 01 04 01 03 01 02 01     ................
 06 03 05 03 04 03 03 03 02 03 02 02 00 00 00 2A     ...............*
 00 28 00 00 25 73 79 73 74 65 6D 32 65 2D 64 65     .(..%system2e-de
 66 32 65 2D 73 76 72 63 6F 6E 6E 2E 63 68 6C 2E     f2e-svrconn.chl.
 6D 71 2E 69 62 6D 2E 63 6F 6D                       mq.ibm.com
  Extension Count: 2
  signature_algorithms 13
   rsa:sha512,rsa:sha384,rsa:sha256,rsa:sha224,rsa:sha1,  
   ecdsa:sha512,ecdsa:sha384,ecdsa:sha256,ecdsa:sha224,
   ecdsa:sha1,dsa:sha1
  server_name 0
   system2e-def2e-svrconn.chl.mq.ibm.com
End of GSKit TLS Handshake Transcript
{  rriEvent
 ...
 RetCode = 20009665, rc1 = 420, rc2 = 0, Comment1='SYSTEM.DEF.SVRCONN', 
 Comment2='gsk_secure_soc_init', Comment3='10.1.1.2(1414)'
 ...
}

With this trace, I am able to see what was sent to z/OS.

The AT-TLS Trace

The trace ( configured in syslogd to be in /var/log/TCPIP) had a one line entry with (I’ve reformatted it to make it easier to read).

Map CONNID: 0000006B 
LOCAL: 10.1.1.2..1414 
REMOTE:
10.1.0.2..53898 
JOBNAME: CSQ9CHIN 
USERID: START1 
TYPE: InBound 
STATUS: Enabled 
RULE: REMOTE-TO-CSQ1 
ACTIONS:
CSQ1-GROUP-ACTION CSQ1-INBOUND-ENVIRONMENT-ACTION N/A

and data

RC: 0 Connection Init
Initial Handshake ACTIONS: CSQ1-GROUP-ACTION CSQ1-INBOUND-ENVIRONMENT-ACTION N/A HS-Server
RC: 0 Call GSK_SECURE_SOCKET_OPEN - 00000052FD6228F0
RC: 0 Set GSK_FD(300) - 000000000000006B
RC: 0 Set GSK_USER_DATA(200) - 000000007EC32430
RECV CIPHER 160303007B 

and one loooong record with

RECV CIPHER 
010000770303749ED51D8DC7794EE6AC36B01FD115F38A4B0812D35 
C80A5F95DB840C35735CA00000400FF003C0100004A000D00180016 
060105010401030102010603050304030303020302020000002A002 
800002573797374656D32652D64656632652D737672636F6E6E2E63 
686C2E6D712E69626D2E636F6D 
SEND CIPHER 15030300020228 

From the AT-TLS trace of the data received from the client, it is the data as received, and has not been split down into useful fields.

I could not find any documentation on how to format this string. It is not easy to create a program to format this (and get it right), for example converting cipher spec 003c to TLS_RSA_WITH_AES_128_CBC_SHA256. However I have a REXX exec which works in ISPF and decodes the data into fields, but not the contents of the fields – so the cipher spec is reported as 003c

I had some success taking this data, and creating a file which Wireshark could process. See Wireshark – using external data: Bodging a hex dump file. This was not always successful, as it looks like the data is truncated, and can have non hex data in the hex stream.

Note, the System SSL server started task, GSKSRVR, can capture System SSL trace. The output is like

Job TCPIP     Process 0101001D  Thread 00000001  read_v3_client_hello            
Received CLIENT-HELLO message                                                  

with no detailed information

Tracing just one session

If you have a busy system you could get trace data for many sessions. You may want to set up a TLS rule, so you use a “debug port”, or you specify the remote host IP address, and port, using information from the error message

EZD1287I TTLS Error RC: 402 Initial Handshake LOCAL: 10.1.1.2..1414 REMOTE: 10.1.0.2..53900

And dont forget…

And do not forget to reset the TTLSEnvironmentAction entry to reset the trace, and to refresh the PAGENT.

Trying to use PCF and decode the output?

I struggled to decode PCF output; for example decode PCF type 1203 and its value 20. MQ provide most of what you need, there is just one little link in the chain which is missing

In the MQ provided CMQSTRC header file are routines for converting the values to strings

For example

char *MQSYSP_STR (MQLONG v)                                   
{                                                             
  char *c;                                                    
  switch (v)                                                  
  {                                                           
  case          0: c = "MQSYSP_NO"; break;                    
  case          1: c = "MQSYSP_YES"; break;                   
  case          2: c = "MQSYSP_EXTENDED"; break;              
  case         10: c = "MQSYSP_TYPE_INITIAL"; break;          
  case         11: c = "MQSYSP_TYPE_SET"; break;              
  case         12: c = "MQSYSP_TYPE_LOG_COPY"; break;         
  case         13: c = "MQSYSP_TYPE_LOG_STATUS"; break;       
  case         14: c = "MQSYSP_TYPE_ARCHIVE_TAPE"; break;     
  case         20: c = "MQSYSP_ALLOC_BLK"; break;             

so if you know this your type is a System Parameter, you can use MQSYSP_STR(20) to get back the data item MQSYSP_ALLOC_BLK.

The bit that is missing is the mapping between PCF type and the function call.

In GitHub I’ve created this mapping for all of the PCF types (MQMAP.h). This has for example

MQMAP(MQIA_APPL_TYPE, MQAT_STR),  //     1
MQMAP(MQIA_DEF_INPUT_OPEN_OPTION, MQOO_STR),  //     4
MQMAP(MQIA_DEF_PERSISTENCE, MQPER_STR),  //     5
MQMAP(MQIA_DEFINITION_TYPE, MQQDT_STR),  //     7

I also provide some routines to help you call this and prettify the value.

I’ve also provided some code so you just need to issue

getPCFValue(MQLONG what, 
            MQLONG value, 
            char **pWhat, 
            char **pValue, 
            char **pPValue);

Where

  • what is the PCF data type (MQIA_TRIGGER_TYPE)
  • value is the PCF value (for example 3)
  • pWhat gets the name of the PCF data type (“Trigger_Type”)
  • pValue gets the value returned from the MQ provided function(“MQTT_DEPTH”)
  • pPValue gets the prettified value, with the prefix removed, and the remained made more readable(“Depth”)

This will allow you to run along PCF data and display all the data and values in a similar manner to a display command.

The data in MQMAP.h is in numerical sequence, so I can use a binary search to quickly find the mapping function. I also provide a small C function which takes the MQMAP.h file, checks it is in order and displays it, so it can be sorted.

Compiling 64 bit C programs (with MQ) – and using dataset aliases

I wanted to compile one of the MQ samples to run as a 64 bit program. The sample comes compiled as 31 bit.

How did I compile the sample to make it 64 bit?

There is documentation on building 64 bit applications; it provides a compile and link JCL example. However, I wanted to do a compile, bind and go.

//COLINCC JOB 1,MSGCLASS=H
//JOBLIB JCLLIB ORDER=(PP.CBC.ZOS204.SCCNPRC)
//COMPILE EXEC PROC=EDCQCBG,  Needed for 64 bit compile
//   LIBPRFX=CEE,   This is an alias 
//   LNGPRFX=PP.CBC.ZOS204,
//   BPARM='MAP,XREF,RENT,DYNAM=DLL',
//   CPARM='OPTFILE(DD:SYSOPTF)',
//   GPARM='M801 CP0000 1'
//COMPILE.SYSLIB DD DISP=SHR,DSN=&LIBPRFX..SCEEH.SYS.H
//   DD DISP=SHR,DSN=&LIBPRFX..SCEEH.H
//   DD DISP=SHR,DSN=MQM.V900.SCSQC370
//COMPILE.SYSIN DD DISP=SHR,DSN=BETACP.MP1B.JCL(CSQ4BCG1)
//COMPILE.SYS DD DISP=SHR,DSN=&LIBPRFX..SCEEH.SYS.H
//COMPILE.SYSOPTF DD *
WARN 64 RENT LP64 LO SOURCE LIST
NOMARGINS EXPMAC SHOWINC XREF
LANGLVL(EXTENDED) SSCOM DLL
DEFINE(_ALL_SOURCE)
/*
//BIND.SCSQDEFS DD DISP=SHR,DSN=MQM.V900.SCSQDEFS
//BIND.SYSIN DD *  
  INCLUDE SCSQDEFS(CSQBMQ2X)
//GO.STEPLIB   DD DISP=SHR,DSN=MQM.V900.SCSQANLE
//   DD DISP=SHR,DSN=MQM.V900.SCSQLOAD
//   DD DISP=SHR,DSN=MQM.V900.SCSQAUTH

Using ALIASes

The above JCL used

  • an alias (CEE) for some libraries. The CEE alias was defined as SYMBOLIC PP.ADLE370.&SYSLEVEL and RESOLVED PP.ADLE370.ZOS204
  • explicit High Level Qualifier PP.CBC.ZOS204 for other libraries.

If your system provides aliases for data sets, it is better to use the alias, rather than the explicit data set name (or HLQ). When the system is upgraded (to ZOS205), the symbolic will be updated from ZOS204 to ZOS205, the alias will be updated automatically, and any JCL using the alias will still work. If you used the explicit dataset name, when your system is upgraded the dataset may not be available, and you need to update your JCL.

What does an alias point to?

The easiest way I found to see what an alias points to use ISPF 3.4 to list datasets beginning with the alias, for example CEE.SCEEH, and browse it. This browsed dataset was PP.ADLE370.ZOS204.SCEEH, so the alias CEE represents PP.ADLE370.ZOS204.

You can also use a LISTCAT command. (Use ISPF 3.4 and use the I prefix command) and change the command to LISTCAT ENTRIES(CEE.* ) ALL then type EXEC on the command line to execute it.

This gives output like

LISTCAT ENTRIES(CEE.* ) ALL
...
ALIAS --------- CEE.SCEEBIND                                                      
     IN-CAT --- ICFCAT.PLEXH.CATALOG3                                             
     HISTORY                                                                      
       RELEASE----------------2     CREATION--------0000.000                      
     ENCRYPTIONDATA                                                               
       DATA SET ENCRYPTION-----(NO)                                               
     ASSOCIATIONS                                                                 
       SYMBOLIC-PP.ADLE370.&SYSLEVEL..SCEEBIND                                    
       RESOLVED-PP.ADLE370.ZOS204.SCEEBIND                                        

Problems with mqinqmp

I struggled to get MQ in Python to work with Message properties when running on z/OS. I eventually found that mqinqmp() in 64 bit mode was not always working (31 bit mode was fine).

Also the documentation for mqinqmp() is not very clear.

You pass to mqinqmp(), a property name (or a generic like %), a buffer, the size of the buffer, the length of the data, and some other parameters (such as get first, or get next). It returns the property name, the property value ( possibly truncated) , and the true length of the property..

If your buffer is too small to contain all of the property, you get reason code MQRC_PROPERTY_VALUE_TOO_BIG, and should get the true length of the property, its name, type, and length.

This worked fine when it was a 31 bit program. When I ran it as a 64 bit program, it did not return the true length, and did not return the property name.

The documentation says

DataLength Type: MQLONG – output

This is the length in bytes of the actual property value as returned in the Value area.

If DataLength is less than the property value length, DataLength is still filled in on return from the MQINQMP call. This allows the application to determine the size of the buffer required to accommodate the property value, and then reissue the call with a buffer of the appropriate size.

The DataLength is the actual length of the property. (The value returned in the Value area is truncated at Value Length).

What should happen

After you get the MQRC_PROPERTY_VALUE_TOO_BIG reason code, you reallocate the buffer with a size of DataLength and retry the request. It should work the second time.

For me the DataLength was 0, so every time around the loop it got MQRC_PROPERTY_VALUE_TOO_BIG.

Understanding PCF cmdscope on z/OS

I wanted to check the response to a PCF command when cmdscope=”*” was issued.

It took a while to understand it. It feels like it was implemented by different people with different strategies. I’ve documented what I found in case someone else struggles down this path. As an extra challenge I used Python to issue the command and process the responses.

High level view of the cmdscope reply

A PCF request is sent to a server, and responses are turned to the specified reply-to-queue. These reply messages all have the same correlid, but different msgids.

  • The first message gives information about the remainder of the messages, and how many queue managers are involved.
  • The last message is a message saying “finished”, and loosely ties up with the first messages.
  • In between there are sets of messages from each queue manager. There are one or more messages in the set. Each set has its own identifier.

More information

The first message

  • This has a Response id – it is not used anywhere else.
  • Says there replies from N queue manager. The value of N is given.
  • There is a Response Set structure (A) giving the Response id for the “end of command scope” message.
  • There are one or more Response Set structures giving the Response id for each queue manager. There are N of these structures.

The last message has a Response-id which matches the Response set (A). The content of this last message is “the request (with command scope) has finished”.

Each queue manager returns a set one or more messages.

  • All messages in the set have a Response-id for the set; which is given in the first message above. The name of the queue manager providing the information is given.
  • The last message in the set has the “Last message in the set” flag set in the PCF header.

Response-id values

Each message has a Response-id field. This has a value like “CSQ M801 GM802…” where the … is hex data. The original request was to queue manager M801, and in this case the response was for queue manager M802. There are several field that look similar to this, for example “CSQ M801 RM801…”. The content of this response is not an API.

How many messages should I expect?

The answer is “it depends”.

  • You will get a matching first and last message (2)
  • You will get a set of messages for each queue manager
    • There will be one or more messages with data.
    • The number of messages with data varies. For example the Inquire archive request returns returns the values at startup. Sysp Type : Type Initial.
    • If a Set Archive command has been issue, a second record with Sysp Type : Type Set, will be present, which shows the parameters which have been changed since startup.
    • A last in set message.

You know when you have the last message when you have received the “the request (with command scope) has finished” message.

At a practical level…

I wrote code like that below, to get all the messages in a request. If is more complex than written as the PCF body may not have the structure with the data type.

cmdscopeloop = no
_______________

Loop: Get message with PCF header and PCF body
## If cmdscope was specified, keep looping until end cmdscope message
If header.type ==MQCFT_XR_MSG and
body[MQIACF_COMMAND_INFO] == MQCMDI_CMDSCOPE_ACCEPTED
then cmdscopeloop = yes

If header.type ==MQCFT_XR_MSG and
body[MQIACF_COMMAND_INFO] == MQCMDI_CMDSCOPE_COMPLETED
then cmdscopeloop = no
## Requests like inquire chinit have a command accepted
If header.type ==MQCFT_XR_MSG and
body[MQIACF_COMMAND_INFO] == MQCMDI_COMMAND_ACCEPTED
then state = “CommandAccepted”

## Most messages have
if header.type ==MQCFT_XR_ITEM
then state = “Item”

## The summary record means end of set.
##For many requests this is end of data
if header.type ==MQCFT_XR_SUMMARY
then state = “endset”

## see if we need to keep looping.
if state == “endset” and cmdscopeloop == no
then return
else loop to get more messages

The request and detailed responses

I used PCF with the command MQCMD_INQUIRE_ARCHIVE, cmdscope=”*”. I send the request to queue manager M801. There were three queue managers in the QSG: M801, M802, M803.

The data description like “Response Q Mgr Name”, is decoded from the PCF value, and post processed to make it more readable, using the MQ sample code (provided on MQ Midrange).

Values like b’M801′ is a Python byte string, or hexadecimal string. Other character data may be in UTF-8 code page.

Response (1) meta information

PCF.Type 17 MQCFT_XR_MSG
PCF.Command 114 MQCMD_INQUIRE_ARCHIVE
PCF.MsgSeq 1
PCF.Control 1 Last message in set
PCF.CompCode 0
PCF.Reason 0
PCF.ParmCount 8

  1. Response Id : CSQ M801 RM801…
  2. Response Q Mgr Name : b’M801′
  3. Command Info : Cmdscope Accepted
  4. Cmdscope Q Mgr Count : 3
  5. Response Set : CSQ M801 SM801… This is for the final “end of cmdscope” message
  6. Response Set : CSQ M801 GM801… These are for the data returned from a queue manager
  7. Response Set : CSQ M801 GM802…
  8. Response Set : CSQ M801 GM803…

Response (2), for queue manager M801. The initial values

PCF.Type 18 MQCFT_XR_ITEM
PCF.Command 114 MQCMD_INQUIRE_ARCHIVE
PCF.MsgSeq 1
PCF.Control 0 Not last message in set
PCF.CompCode 0
PCF.Reason 0
PCF.ParmCount 19

  1. Response Id : CSQ M801 GM801… Matching a response set value in the first message
  2. Response Q Mgr Name : b’M801′
  3. Sysp Type : Type Initial
  4. Sysp Archive Unit1 : b’TAPE’
    ….

19. Sysp Quiesce Interval : 5

Response (3), for queue manager M801. The fields which have been set

On queue manager M801 the command SET ARCHIVE UNIT(DISK) was issued. If the DISPLAY ARCHIVE command is issued it will display DISK under the “SET value” column.

PCF.Type 18 MQCFT_XR_ITEM
PCF.Command 114 MQCMD_INQUIRE_ARCHIVE
PCF.MsgSeq 2
PCF.Control 0 Not last message in set
PCF.CompCode 0
PCF.Reason 0
PCF.ParmCount 4

  1. Response Id : CSQ M801 GM801…
  2. Response Q Mgr Name : b’M801′
  3. Sysp Type : Type Set
  4. Sysp Archive Unit1 : b’DISK’

Response(4), for queue manager M801. End of queue manager’s data

PCF.Type 19 MQCFT_XR_SUMMARY
PCF.Command 114 MQCMD_INQUIRE_ARCHIVE
PCF.MsgSeq 3
PCF.Control 1 Last message in set
PCF.CompCode 0
PCF.Reason 0
PCF.ParmCount 2

  1. Response Id : CSQ M801 GM801…
  2. Response Q Mgr Name : b’M801′

Response (5), for queue manager M802. The initial values

PCF.Type 18 MQCFT_XR_ITEM
PCF.Command 114 MQCMD_INQUIRE_ARCHIVE
PCF.MsgSeq 1
PCF.Control 0 Not last message in set
PCF.CompCode 0
PCF.Reason 0
PCF.ParmCount 19

  1. Response Id : CSQ M801 GM802…
  2. Response Q Mgr Name : b’M802′
  3. Sysp Type : Type Initial
  4. Sysp Archive Unit1 : b’TAPE’

Response (6), for queue manager M802. End of queue manager’s data

PCF.Type 19 MQCFT_XR_SUMMARY
PCF.Command 114 MQCMD_INQUIRE_ARCHIVE
PCF.MsgSeq 2
PCF.Control 1 Last message in set
PCF.CompCode 0
PCF.Reason 0
PCF.ParmCount 2

  1. Response Id : CSQ M801 GM802…
  2. Response Q Mgr Name : b’M802′

Response (7), for queue manager M803. The initial values

PCF.Type 18 MQCFT_XR_ITEM
PCF.Command 114 MQCMD_INQUIRE_ARCHIVE
PCF.MsgSeq 1
PCF.Control 0 Not last message in set
PCF.CompCode 0
PCF.Reason 0
PCF.ParmCount 19

  1. Response Id : CSQ M801 GM803…
  2. Response Q Mgr Name : b’M803′
  3. Sysp Type : Type Initial
  4. Sysp Archive Unit1 : b’TAPE’

Response (8), for queue manager M803. End of queue manager’s data

PCF.Type 19 MQCFT_XR_SUMMARY
PCF.Command 114 MQCMD_INQUIRE_ARCHIVE
PCF.MsgSeq 2
PCF.Control 1 Last message in set
PCF.CompCode 0
PCF.Reason 0
PCF.ParmCount 2

  1. Response Id : CSQ M801 GM803…
  2. Response Q Mgr Name : b’M803′

Response (9), for queue manager M801. End of command.

PCF.Type 17 MQCFT_XR_MSG
PCF.Command 114 MQCMD_INQUIRE_ARCHIVE
PCF.MsgSeq 1
PCF.Control 1 Last message in set
PCF.CompCode 0
PCF.Reason 0
PCF.ParmCount 3

  1. CSQ M801 SM801…
  2. Response Q Mgr Name : b’M801′
  3. Command Info : Cmdscope Completed