SecureMyi.com Security and Systems Management Newsletter for the IBM i May 28, 2014 - Vol 4, Issue 9
Using CL Command Exit Programs Part 1
By Dan Riehl
Ever since the first version of the CPF operating system was shipped with the System/38, system administrators have grappled with the problems inherent in customizing the IBM supplied Control Language command set for the System/38 and later for the AS/400 and System i. One major source of irritation was that when a new release of the operating system was installed, any changes made directly to the IBM supplied commands in the QSYS library were obliterated. So, for each new operating system release, the system administrator would have to re-apply all of the command changes.
Over time, we all got a bit smarter and figured out that we could make a copy of the IBM supplied command in our own library, and modify the copy. By strategically placing our own library, perhaps named ALTQSYS, ahead of QSYS in the system portion of the library list, we could effectively override the IBM supplied version. So when a new release of the operating system was installed, it would replace the IBM supplied version of the command in the QSYS library, but leave our customized version in our ALTQSYS intact.
Since Control Language commands do not often undergo fundamental changes that impact the majority of us, the ALTQSYS copy of a command created at a previous release of the operating system normally functioned correctly. However, there were certainly exceptions, in which the ALTQSYS version of the commands would fail after installing a new release of the operating system. Still not a perfect solution, but normally manageable.
If you examine the system portion of the library list on your System i using the command DSPSYSVAL QSYSLIBL, you will most likely see an ALTQSYS library ahead of QSYS. You will many times also see the name of a vendor supplied library. Some System i software vendors seem to make a habit of placing their library on your system library list ahead of QSYS in order to override the processing of the IBM supplied command set. This is particularly true in the case of software utility and tool vendors.
Most of us have accepted the fact that we need libraries above QSYS in our library list to support our customized versions and vendor supplied versions of CL commands and other objects. But there is an inherent danger in this scheme. Any library existing above QSYS on the system library list can potentially open the System i to a severe security exposure; the Trojan Horse. If a user can create or change objects in a library above QSYS, or in QSYS for that matter, that user can introduce their own program or command that can do major damage to your system.
In an attempt to eliminate the need for libraries above QSYS, IBM, in OS/400 V4R5 enabled a facility that may make it possible to remove the libraries above QSYS from the system portion of the library list. I say may, because the support provided as of OS/400 V4R5 deals specifically with CL command customization, but does not account for other customized objects that may need to appear before QSYS.
The enhanced support as of V4R5 was the CL command analyzer exit points. These exit points provide the ability to change the processing done by a Control Language command at run-time.
CL Command Exit Point Programs
CL has always had validity checking programs and command processing programs. Over the years IBM has added prompt override programs, prompt choice programs and prompt control programs. Back in V4R5, IBM added the support for command analyzer exit programs that further enhance CL's capabilities, and more importantly, can enhance and simplify your system administration, configuration and security.
In V4R5 two new exit points were added to the OS/400 registration facility (WRKREGINF) that allow you to control the processing of CL commands. These two exit points are the Command Analyzer Change exit point, and the Command Analyzer Retrieve exit point.
In this article, the focus will be on the command analyzer change exit point, the retrieve exit point will be covered in a later issue, as will the new support for specifying the time(Before/After) of the command exit point programs. We'll also look at some Command Exit Point Secrets that are not documented features, but with some testing and research, you can do magic with CL Command Exit Programs.
The Command Analyzer Change Exit Point
This exit point allows you to intercept a CL command before it is executed, and perform your own custom processing, which might simply be changing some parameter values, or might even consist of running a totally different command in a totally different library.
As an example, if the SBMJOB command is run, you may use the exit point program to tell the command analyzer to run the SBMJOB command found in another library, instead of the one found first on the job's library list. Note: It is this capability that allows us to possibly remove our libraries from ahead of QSYS.
Here's another example that points out other possibilities and is the theme of the sample exit program provided in Figure 2. If the command WRKJOB is executed by someone that has a user class of *USER, you can, in the exit program, tell the command analyzer to run the DSPJOB command instead. (Not that there is a huge difference in these two commands, but I am simply illustrating how this works.)
This exit point opens up a whole new world of possibilities for software vendors and IBM i, iSeries system administrators.
When I first heard of this new capability, my assumption was that I would be able to add an exit program to a CL command using the CHGCMD(Change Command) CL command. After all, this was the way you change the command processing program and the validity checker. Upon my first perusal of the documentation for these new exit points, it became clear to me that my assumption was incorrect. Over the past several releases of OS/400 and IBM i, IBM has really embraced the Exit Point Registration facility. The exit point programs are added to a command through the Registration facility, WRKREGINF(Work with Registration Information) and the ADDEXITPGM(Add Exit Program) command.
Exit Points and Exit Programs---Revisted
An exit point is a point in an application at which it can optionally call an external program to perform customized processing. The OS/400 command analyzer as of V4R5 included exit points where you can hook your own program into the command processing logic. To identify your exit program, and the command for which it will be called, you use the ADDEXITPGM (Add Exit Program) command, specifying the command name and library for which this exit program will be used, and the name and library of your exit program that will process the particular command. When a request is made to the command analyzer to run a command, it checks the exit point registry to see if you have an exit program assigned to the requested command. If you do, it calls your exit program, passing parameters that contain the information about the requested command. Your exit program then processes that information, and takes the appropriate action.
Each exit point has an assigned name and an exit point interface. The exit point interface is a list of input and output parameters the command analyzer exchanges with your exit program. The name of the exit point for the command analyzer change command exit point is QIBM_QCA_CHG_COMMAND. This exit point occurs before the command analyzer prompts the command, and before the validity checking program is called. The name of the exit point interface for this exit point is named CHGC0100. This exit point interface is different from most in that your exit program is not passed the CHGC0100 interface in separate parameters, but as one big lump of data. Your program needs to parse out the individual data elements of the CHGC0100 parameter. In addition to the CHGC0100 parameter, your exit program is passed two additional parameters as shown in Figure 1.
Figure 1 The Change Command Exit Point Interface
As with all registered exit points, you can add your own exit point program using the ADDEXITPGM(Add Exit program) command. In the case of the Change Command exit point, you need to specify the name of the command, and the library in which the command resides in the PGMDTA parameter as in the following example.
ADDEXITPGM EXITPNT(QIBM_QCA_CHG_COMMAND) +
TEXT('Exit program for WRKJOB') +
PGMDTA(*JOB 20 'WRKJOB QSYS')
The PGMDTA parameter must be specified with a data length of 20. You must specify the command name in the first 10 positions, and the command library in the next 10 positions, as in the example. The command analyzer is directed to use the exit program WRKJOBEXIT in library MYLIB whenever a request is made to run the WRKJOB command found in the QSYS library.
There are certain commands for which exit point programs may not be defined. These include CL compiler directing statements, commands found in libraries QSYS38 and QUSER38, and a few others. Following is the list of commands for which exit programs may not be registered.
Figure 2 below, contains a sample exit program that may be used for the WRKJOB command. The exit program checks to see if the user running the WRKJOB command has a user class of *USER. If so, the program tells the command analyzer to run the DSPJOB command instead. While the functionality of the example is quite simple, it does address the difficult parts of writing your own exit program.
Figure 2 Command Exit Point Program Example
/* Program Name: WRKJOBEXIT */
/* Purpose: This is the exit program for the Command WRKJOB. */
/* Exit Point IS QIBM_QCA_CHG_COMMAND */
/*Parameter format for parm1 is CHGC0100. */
/* - - - - - - - - - - - - - -- - - - - - - */
/* Copyright© 2002-2014 Dan Riehl, The 400 School, Inc. */
PGM PARM(&ExitInfo &NewString &Newlength) A
DCL &Class *CHAR 10
DCL &OffsetDec *DEC (7 0)
DCL &CmdLenDec *DEC (7 0)
DCL &ExitInfo *CHAR 2000 /* CHGC0100 interface data */
/* Exit point interface CHGC0100 for QIBM_CA_CHG_COMMAND exit point */
/* Input parameters */
DCL &ExitPoint *CHAR 20 /* Exit Point name */B
DCL &ExitFormat *CHAR 8 /* Exit Point Format */B
DCL &CmdName *CHAR 10 /* Command name being executed */B
DCL &CmdLib *CHAR 10 /* Command Library */B
DCL &Change *CHAR 1 /* Change allowed? 1=yes 0=no */B
DCL &Prompt *CHAR
1 /* Prompt requested? 1=yes 0=no */B
DCL &Offset *CHAR 4 /* Offset to command string */B
DCL &CmdLength *CHAR 4 /* Command string length */B
DCL &CmdString *CHAR 2000 /* Command String */B
/* Output Parameters */
DCL &NewString *CHAR 2000 /* Replace with this command */
DCL &NewLength *CHAR 4 /* Length of new command */
/* 0 = no new command */
/* Error handling variables */
DCL &MsgID *CHAR 7
DCL &MsgFile *CHAR 10
DCL &MsgFLib *CHAR 10
DCL &MsgData *CHAR 512
MONMSG CPF0000 EXEC(GOTO ERROR)
/* Parse out the exit info data */
CHGVAR &ExitPoint %SST(&ExitInfo 1 20) C
CHGVAR &ExitFormat %SST(&ExitInfo 21 8) C
CHGVAR &CmdName %SST(&ExitInfo 29 10) C
CHGVAR &CmdLib %SST(&ExitInfo 39 10) C
CHGVAR &Change %SST(&ExitInfo 49 1) C
CHGVAR &Prompt %SST(&ExitInfo 50 1) C
CHGVAR &Filler %SST(&ExitInfo 51 2) C
CHGVAR &Offset %SST(&ExitInfo 53 4) C
CHGVAR &CmdLength %SST(&ExitInfo 57 4) C
CHGVAR &CmdLenDec %BIN(&Cmdlength)
CHGVAR &OffsetDec (%BIN(&Offset) + 1) /* Set offset */
CHGVAR &CmdString %SST(&ExitInfo &OffsetDec &CmdLenDec)
IF (&Change = '0') GOTO ENDIT
Change is not allowed */ D
IF (&Class = '*USER') DO /* If a user class of *USER */
CHGVAR &NewString 'QSYS/DSPJOB' E
CHGVAR %BIN(&Newlength) 11
/* Error handling */
ERROR: RCVMSG MSGTYPE(*LAST) MSGDTA(&MsgData) +
MSGID(&MsgID) MSGF(&MsgFile)  +
MONMSG CPF0000 EXEC(RETURN)
SNDPGMMSG MSGID(&MsgID) MSGF(&MSGFLIB/&MsgFile) +
MONMSG CPF0000 EXEC(RETURN)
Examining the WRKJOB command Exit Program
At letter A in Figure 2, you can see that the exit programs receives the three parameters that are discussed in Figure 1. As mentioned, the first parameter contains a CHGC0100 data structure that must be parsed into it's individual fields. At B, the fields are defined that will be used to store the CHGC0100 format data once it is parsed. At C , the CHGC0100 format is parsed into it's component parts. In this example, some extra work is done to parse the command string, and determine the offset to the command. While the sample doesn't need or use this information, the code is provided for instructional purposes so that you can use this section as a template to write your own exit programs.
At D in Figure 2, the change indicator is queried to determine if the command is changeable by the exit program. If a command is library qualified when it is passed to the command analyzer, as in the command QSYS/WRKJOB, it is not changeable by the exit program, and the change indicator will have a value of '0'. This is by design. There are a few other occasions when a command cannot be changed, these consist of commands that:
Back in V4R5, IBM introduced the new qualifier value of *SYSTEM. The purpose of this new qualifier is to allow a sort of "non qualified" qualified reference to an object. For example, if a command is specified as *SYSTEM/WRKJOB the WRKJOB command in the QSYS library will be used. The *SYSTEM qualifier means to get the object from QSYS, while not explicitly identifying QSYS. IF you want to qualify commands in your applications, and still want to use exit programs to change the commands, you can use the *SYSTEM qualifier, which, under the covers will actually be a qualification to library QSYS.
At E in Figure 2, the replacement command is set to QSYS/DSPJOB if the user running the WRKJOB command has a user class of *USER. The exit program then ends.
You may notice that CL variables used in the exit program to store command strings are 2000 bytes long. In V4R5 Control Language character variables could not be longer than 2000 bytes. While 2000 bytes is certainly sufficient to hold the WRKJOB command string in this example, the actual length of a command string that can be processed through the exit point interface is 32,000 bytes. In IBM i 7.1 the length of a CL *CHAR variable can be up to 32767 bytes, so adjust the code as needed.
The sample program is written in Control Language in an attempt to make the exit program simple to read and understand. Exit programs do not need to be written in any particular language, and do not need to be written for ILE, but certainly can be.
The Exit Point
Hopefully this walk through and example code will be helpful to you. This new capability to control commands at run-time opens whole realms of possibilities in application development and system administration. Try this out on a few commands that you want to customize. You may find that you can remove a library or two that sit above QSYS on your system library list, and that would certainly be worth the time and effort.
For more information on this exit point check the IBM i Information Center documentation at:
Using the Replace Command (QCARPLCM) API
In V4R5, IBM supplied an exit point program for use with the Command Analyzer Change Exit point. If you specify this program, QCARPLCM, as the exit point program for a command, a useful function is performed. If the command was originally qualified with *SYSTEM or *NLVLIBL, the exit program will replace the qualifier with *LIBL. Here's an example of how to add QCARPLCM as the exit program for the RSTOBJ(Restore Object) command in QSYS.
ADDEXITPGM EXITPNT(QIBM_QCA_CHG_COMMAND) +
TEXT('Exit program for RSTOBJ') +
20 'RSTOBJ QSYS')
©2004-2014 Dan Riehl All Rights Reserved
About the Author
Dan performs IBM i security assessments and provides security consulting, remediation, forensic evaluations, and other customized security services for his clients. He also provides training in all aspects of IBM i security and other technical areas through The 400 School, Inc.