Switch to and back from different user profiles within the same job with ease.
In UNIX or Linux, users switch to the super user (aka root) or another user via the su shell command (in a new shell, actually). With proper options, a su command can even preserve the original environment in the newly started shell session—for example, the -m option (do not reset environment variables) of the su command of GNU Linux. For an IBM i developer or operator, a CL command with function similar to the UNIX/Linux su shell command would be very handy.
In this article, I'll show you a practical utility, the SU CL command, which allows you to switch to and back from different user profiles within the same job. The SU command changes the USRPRF under which the current thread/job runs so that the new USRPRF (replacing the previous USRPRF) becomes one of the sources of authority of the current thread/job. While no other job resources are changed due to a SU command, you can for example still retrieve previously entered commands with function key F
How Does the Su Command Work?
The SU command allows an operator or a program to switch to another user profile in a single job. When switching to another user profile, you should pass a user profile (*USRPRF) name and the password of the USRPRF (or optionally one of the following accepted special values: *NOPWD, *NOPWDCHK, or *NOPWDSTS). For example: SU USER(USER_A) PASSWORD(******). When switching back to the previous USRPRF, you can simply issue SU USER(*EXIT).
The SU command allows an operator or a program to switch to multiple users one by one and then switch back in the reverse order. To preserve the USRPRF handles, the SU command uses data queue (*DTAQ) object QTEMP/@SUDTAQ of type *LIFO (queue entries are received in a last-in first-out order). In other words, @SUDTAQ works as a "USRPRF handle stack." Figure 1 illustrates the working mechanism of the SU command in brief.
Figure 1: This is how the SU command works.
[1], [2] When an operator or a program issues a SU USER(USER_NAME) PASSWORD(******) command, SU saves the current USRPRF handle by first retrieving a USRPRF handle (PH_A) of the current user (USER_A) via the Get Profile Handle (QSYGETPH) API and then pushing PH_A on the USRPRF handle stack), @SUDTAQ, via the Send Data Queue (QSNDDTAQ) API.
[3] Get the USRPRF handle (PH_B) of the target user (USER_B) by invoking the QSYGETPH API with the input user name and password parameters.
[4] Switch to the USER_B by invoking the Set Profile (QWTSETP) API, passing the USRPRF handle PH_B. After QWTSEPT returns successfully, the current job is running under the user profile USER_B -- PH_B.
[5] Release PH_B via the Release Profile Handle (QSYRLSPH) API.
[6] Work under user profile USER_B.
[7] When a SU USER(*EXIT) command is issued, SU pops the user profile handle of USER_A (PH_A) out of the "USRPRF handle stack" via the Receive Data Queue (QRCVDTAQ) API and then invokes QWTSETP to switch back USER_A.
[8] After QWTSEPT returns successfully, the current thread runs under the user profile USER_A again. SU then releases PH_A.
Note that the SU command accepts either a password or one of the following special values:
- *NOPWD—The user requesting the profile handle must have *USE authority to the user profile. A profile handle does not get created for a disabled user profile. A profile handle does not get created for a user profile with an expired password.
- *NOPWDCHK—The user requesting the profile handle must have *USE authority to the user profile. If the profile is disabled, the user requesting the profile handle must have *ALLOBJ and *SECADM special authorities to get a handle. If the password is expired, the user requesting the profile handle must have *ALLOBJ and *SECADM special authorities to get a handle.
- *NOPWDSTS—The user requesting the profile handle must have *USE authority to the user profile. A profile handle does not get created for a disabled user profile. If the password is expired, the user requesting the profile handle must have *ALLOBJ and *SECADM special authorities to get a handle.
Source Code of the SU Command
The following is the source of command definition of the SU command su.cl-cmd.
CMD PROMPT('su') PARM KWD(USER) TYPE(*NAME) LEN(10) SPCVAL((*EXIT + *EXIT)) MIN(1) CASE(*MONO) PROMPT('User + name') PARM KWD(PASSWORD) TYPE(*CHAR) LEN(128) + DFT(*NOPWDCHK) SPCVAL((*NOPWD *NOPWD) + (*NOPWDCHK *NOPWDCHK) (*NOPWDSTS + *NOPWDSTS)) MIN(0) CASE(*MIXED) + DSPINPUT(*NO) INLPMTLEN(10) + PROMPT('Password') |
The CL version of the CPP of the SU command su.clle is shown as follows:
/********************************************************************/ /* @file su.clle */ /* CL version of the CPP of the SU command. */ /********************************************************************/
PGM PARM(&TGTUSR &PWD)
DCL VAR(&TGTUSR) TYPE(*CHAR) LEN(10) DCL VAR(&PWD) TYPE(*CHAR) LEN(128) DCL VAR(&ORG_PH) TYPE(*CHAR) LEN(12) DCL VAR(&TGT_PH) TYPE(*CHAR) LEN(12) DCL VAR(&QNAM) TYPE(*CHAR) LEN(10) VALUE(@SUDTAQ) DCL VAR(&QLIB) TYPE(*CHAR) LEN(10) VALUE(QTEMP) DCL VAR(&ENT_LEN) TYPE(*DEC) LEN(5 0) VALUE(12) DCL VAR(&TIMEOUT) TYPE(*DEC) LEN(5 0) VALUE(0) DCL VAR(&PWD_LEN) TYPE(*INT) LEN(4) VALUE(128) DCL VAR(&OFF1) TYPE(*INT) LEN(4) DCL VAR(&OFF2) TYPE(*INT) LEN(4) DCL VAR(&CCSID) TYPE(*INT) LEN(4) VALUE(0) DCL VAR(&WHERE) TYPE(*PTR) DCL VAR(&PWD_PTR) TYPE(*PTR) DCL VAR(&NULL) TYPE(*PTR) DCL VAR(&EC) TYPE(*CHAR) LEN(16) + VALUE(X'00000000000000000000000000000000')
IF COND(&TGTUSR *NE '*EXIT') THEN(GOTO + CMDLBL(SWAP_TO)) ELSE CMD(GOTO CMDLBL(SWAP_BACK))
SWAP_TO: CHKOBJ OBJ(&QLIB/&QNAM) OBJTYPE(*DTAQ) MONMSG MSGID(CPF9801) EXEC(DO) CRTDTAQ DTAQ(QTEMP/@SUDTAQ) MAXLEN(12) SEQ(*LIFO) + AUT(*CHANGE) ENDDO /* Create the PH stack in case it isn't + already exists */
CALL PGM(QSYGETPH) PARM('*CURRENT ' ' ' &ORG_PH) CALL PGM(QSNDDTAQ) PARM(&QNAM &QLIB &ENT_LEN + &ORG_PH) /* Save current PH */
/* Get the PH of target user */ IF COND(%SST(&PWD 1 6) *EQ '*NOPWD') THEN(CALL + PGM(QSYGETPH) PARM(&TGTUSR &PWD &TGT_PH)) ELSE CMD(DO) CALLPRC PRC('_MEMCHR') PARM((&PWD *BYREF) (' ' + *BYVAL) (&PWD_LEN *BYVAL)) RTNVAL(&WHERE) IF COND(&WHERE *NE &NULL) THEN(DO) CHGVAR VAR(&PWD_PTR) VALUE(%ADDR(&PWD)) CHGVAR VAR(&OFF2) VALUE(%OFS(&WHERE)) CHGVAR VAR(&OFF1) VALUE(%OFS(&PWD_PTR)) CHGVAR VAR(&PWD_LEN) VALUE(&OFF2 - &OFF1) ENDDO /* If &where == *NULL */ CALL PGM(QSYGETPH) PARM(&TGTUSR &PWD &TGT_PH &EC + &PWD_LEN &CCSID) ENDDO /* Else block */
CALL PGM(QWTSETP) PARM(&TGT_PH) /* Switch to + target user */ CALL PGM(QSYRLSPH) PARM(&TGT_PH) /* Release PH */ GOTO CMDLBL(BYE)
SWAP_BACK: CALL PGM(QRCVDTAQ) PARM(&QNAM &QLIB &ENT_LEN + &ORG_PH &TIMEOUT) /* Pop previous PH out + of the PH stack */ IF COND(&ENT_LEN *EQ 0) THEN(SNDPGMMSG + MSGID(CPF9898) MSGF(QSYS/QCPFMSG) + MSGDTA('Exit to where? :p') MSGTYPE(*ESCAPE)) ELSE CMD(DO) CALL PGM(QWTSETP) PARM(&ORG_PH) /* Switch back to + original user */ CALL PGM(QSYRLSPH) PARM(&ORG_PH) /* Release PH */ ENDDO
BYE: ENDPGM |
The following is the OPM MI version of the CPP of the SU command su.emi (needs to be compiled via mic).
/** * @file su.emi * * CPP of the SU command. */
dcl spcptr @tgt-user parm ; dcl dd tgt-user char(10) bas(@tgt-user) ; dcl spcptr @pwd parm ; dcl dd pwd char(128) bas(@pwd) ;
dcl ol pl-main( @tgt-user, @pwd ) parm ext ; entry *(pl-main) ext ; brk "MORNING" ; /* Check target USRPRF parameter */ cmpbla(b) tgt-user, *EXIT / eq(=+3) ; calli swap-to, *, @swap-to ; b =+2 ; : calli swap-back, *, @swap-back ; /* SU USER(*EXIT) */ : brk "SEEU" ; rtx * ;
/* Routine: swap-to */ dcl insptr @swap-to auto ; entry swap-to int ; dcl sysptr @sudtaq auto ; dcl dd rt char(34) auto ; dcl dd * char(2) def(rt) pos(1) init(x" dcl dd * char(30) def(rt) pos(3) init(" ") ; dcl dd * char(2) def(rt) pos(33) init(x"0000") ;
/* Resolve QTEMP/@SUDTAQ */ cpybla rt(3:30), qrcvdtaq?q ; setip @on-2201, crt-su-dtaq ; rslvsp @sudtaq, rt, @pco?qtemp, * ; b so-boring ; crt-su-dtaq: cpyblap cl-cmd, "CRTDTAQ DTAQ(QTEMP/@SUDTAQ) MAXLEN(12) SEQ(*LIFO) AUT(*CHANGE)", " " ; triml pkd-cmd-len, cl-cmd, " " ; callx pco?sept(qcmdexc-entry), al-qcmdexc, * ; so-boring: /* Save current profile handle for swapping back */ cpybla user, cur-user ; setspp @ph, org-ph ; callx pco?sept(qsygetph-entry), al-qsygetph-short, * ; /* When specify *CURRENT for parm user, */ /* QSYGETPH expects 3-4 parms */ cpynv qrcvdtaq?len, 12 ; setspp @qrcvdtaq?msg, org-ph ; callx pco?sept(qsnddtaq-entry), al-qsnddtaq, * ; brk "MEMO" ; /* Get the profile handle of the target USRPRF */ cpybla user, tgt-user ; setspp @ph, ph ; cmpbla(b) pwd, "*NOPWD" / eq(spec-pwd-value) ; triml pwd-len, pwd, " " ; callx pco?sept(qsygetph-entry), al-qsygetph, * ; b =+2 ; spec-pwd-value: callx pco?sept(qsygetph-entry), al-qsygetph-short, * ; /* When specify a special value for parm */ /* PASSWORD, QSYGETPH expects 3-4 parms */ : /* Swap to target USRPRF */ callx pco?sept(qwtsetp-entry), al-qwtsetp, * ; callx pco?sept(qsyrlsph-entry), al-qsyrlsph, * ; /* Release profile handle of tgt-user */ end-swap-to: brk "TO" ; b @swap-to ;
/* Routine: swap-back */ dcl insptr @swap-back auto ; entry swap-back int ; /* Resolve QTEMP/@SUDTAQ */ cpybla rt(3:10), qrcvdtaq?q ; setip @on-2201, end-swap-back ; rslvsp @sudtaq, rt, @pco?qtemp, * ;
/* Dequeue previous profile handle from @SUDTAQ */ setspp @qrcvdtaq?msg, org-ph ; callx pco?sept(qrcvdtaq-entry), al-qrcvdtaq, * ; cmpnv(b) qrcvdtaq?len, 0 / neq(end-deq) ; cpybla msg, "Exit to where? :p" ; setspp @sndimdmsg?text, msg ; triml sndimdmsg?textl, msg, " " ; cpybla sndimdmsg?msgtype, "*ESCAPE" ; calli sndimdmsg, *, @sndimdmsg ; b end-swap-back ; end-deq: /* Set profile handle to previous PH */ setspp @ph, org-ph ; callx pco?sept(qwtsetp-entry), al-qwtsetp, * ; callx pco?sept(qsyrlsph-entry), al-qsyrlsph, * ;
end-swap-back: b @swap-back ;
/* Exception handlers */ dcl excm excd-2201 excid(h'2201') bp(on-2201) imd ; dcl insptr @on-2201 auto ; on-2201: cpybla msg, "Data queue @sudtaq does not exist" ; cpybla msg(12:7), qrcvdtaq?q ; setspp @sndimdmsg?text, msg ; triml sndimdmsg?textl, msg, " " ; cpybla sndimdmsg?msgtype, "*DIAG" ; calli sndimdmsg, *, @sndimdmsg ; brk "2201" ; b @on-2201 ;
/* QCMDEXC */ dcl con qcmdexc-entry bin(2) unsgnd init(h' dcl dd pkd-cmd-len pkd(15,5) auto ; dcl spcptr @pkd-cmd-len auto init(pkd-cmd-len) ; dcl dd cl-cmd char(64) auto ; dcl spcptr @cmd-str auto init(cl-cmd) ; dcl ol al-qcmdexc (@cmd-str, @pkd-cmd-len) arg ;
/* QSYGETPH */ dcl con qsygetph-entry bin(2) unsgnd init(h'1305') ; dcl dd user char(10) auto ; dcl spcptr @user auto init(user) ; dcl dd ph char(12) auto ; dcl dd org-ph char(12) auto ; /* Profile handle of original USRPRF */ dcl spcptr @ph auto ; dcl dd ec-size bin(4) auto init(0) ; dcl spcptr @ec auto init(ec-size) ; dcl dd pwd-len bin(4) auto ; dcl spcptr @pwd-len auto init(pwd-len) ; dcl dd ccsid bin(4) auto init(-1) ; /* determine CCSID according to the current password level (QPWDLVL) */ dcl spcptr @ccsid auto init(ccsid) ; dcl ol al-qsygetph ( @user, @pwd, @ph, @ec, @pwd-len, @ccsid ) arg ; dcl ol al-qsygetph-short ( @user, @pwd, @ph ) arg ; dcl con cur-user char(10) init("*CURRENT") ;
/* QSYRLSPH */ dcl con qsyrlsph-entry bin(2) unsgnd init(h'130B') ; dcl ol al-qsyrlsph (@ph) arg ;
/* QWTSETP */ dcl con qwtsetp-entry bin(2) unsgnd init(h'1350') ; dcl ol al-qwtsetp (@ph) arg ;
/* QRCVDTAQ */ dcl con qrcvdtaq-entry bin(2) unsgnd init(h'B51') ; dcl dd qrcvdtaq?q char(10) auto init("@SUDTAQ") ; dcl spcptr @qrcvdtaq?q auto init(qrcvdtaq?q) ; dcl dd qrcvdtaq?lib char(10) auto init("QTEMP") ; dcl spcptr @qrcvdtaq?lib auto init(qrcvdtaq?lib) ; dcl dd qrcvdtaq?len pkd(5,0) auto init(p'12') ; dcl spcptr @qrcvdtaq?len auto init(qrcvdtaq?len) ; dcl spcptr @qrcvdtaq?msg auto ; dcl dd qrcvdtaq?waittime pkd(5,0) auto init(p'0') ; /* Dequeue @SUDTAQ without waiting */ dcl spcptr @qrcvdtaq?waittime auto init(qrcvdtaq?waittime) ; dcl ol al-qrcvdtaq( @qrcvdtaq?q, @qrcvdtaq?lib, @qrcvdtaq?len, @qrcvdtaq?msg, @qrcvdtaq?waittime ) arg ;
/* QSNDDTAQ */ dcl con qsnddtaq-entry bin(2) unsgnd init(h'B52') ; dcl ol al-qsnddtaq( @qrcvdtaq?q, @qrcvdtaq?lib, @qrcvdtaq?len, @qrcvdtaq?msg ) arg ;
dcl con *EXIT char(10) init("*EXIT") ; dcl dd msg char(64) auto init(" ") ; dcl dd flag bin(2) auto init(0) ;
/* Includes */ /include "sept.emi" ; /include "sndimdmsg.emi" ;
pend ; |
Source files included by su.emi are available here: sept.emi, sndimdmsg.emi.
For your convenience, the C version of the CPP of SU is available here: su.c.
Let's Try It!
Imagine that you're developing a defect-tracking application. A physical file called BUGS is expected to store all reported defects. Three kinds of users of this defect-tracking application have different authorities to PF BUGS:
- The administrator (identified by USRPRF ADMIN)—User profile ADMIN owns the PF BUGS and hence has full authorities to BUGS.
- Testers whose group profile is TESTER—Testers are responsible for reporting defects to managers and programmers, who are expected to solve all detected defects. USRPRF TESTER has add and read authorities to PF BUGS but is prohibited from modifying or deleting BUGS records.
- Programmers whose group profile is PGMR—USRPRF PGM has read and update authorities to PF BUGS.
The public and private authorities to different users are set by ADMIN as shown:
GRTOBJAUT OBJ(BUGS) OBJTYPE(*FILE) USER(*PUBLIC) AUT(*EXCLUDE) /* Revoke *PUBLIC authorities to BUGS */
GRTOBJAUT OBJ(BUGS) OBJTYPE(*FILE) USER(TESTER) AUT(*USE) GRTOBJAUT OBJ(BUGS) OBJTYPE(*FILE) USER(TESTER) AUT(*ADD) /* Allow TESETER to read BUGS or add records to it */
GRTOBJAUT OBJ(BUGS) OBJTYPE(*FILE) USER(PGMR) AUT(*CHANGE) RVKOBJAUT OBJ(BUGS) OBJTYPE(*FILE) USER(PGMR) AUT(*DLT) /* Allow PGMR to read BUGS or update records in it */ |
The following example steps demonstrate operations on PF BUGS under three different USRPRFs within a single job:
CLRPFM BUGS /* Under USRPRF ADMIN */ SU TESTER. STRQSH CMD('db2 "INSERT INTO BUGS (DID, DDATE, DDESC) VALUES(''A01'', '' /* Under USRPRF TESTER */ SU PGMR STRQSH CMD('db2 "UPDATE BUGS SET DDESC=TRIM(DDESC) CONCAT ' - Solved'"') /* Under USRPRF PGMR */ SU *EXIT RUNQRY *N BUGS /* Under USRPRF TESTER */ SU *EXIT |
Note that a DSPJOB OPTION(*STSA) can be used to check the current user profile of the job.
LATEST COMMENTS
MC Press Online