//TSGMXZ1R JOB (AD00),'#04D42 ZELDEN', // NOTIFY=&SYSUID, // CLASS=M,MSGCLASS=H,MSGLEVEL=(1,1) //* //********************************************************** //* COPY REXX PROGRAM TO A TEMPORARY PDS //********************************************************** //GENER EXEC PGM=IEBGENER //SYSPRINT DD SYSOUT=* //SYSIN DD DUMMY //SYSUT1 DD DATA,DLM=@# /* rexx */ /* */ /* AUTHOR: Mark Zelden */ /* */ /********************************************************************/ /* This program reads the output from the RACF database unload */ /* utility IRRDBU00. It creates two output files and a report. The */ /* report lists all of the USERIDs that are have not been accessed */ /* a specified number of days taken from an input parm to this */ /* program (the default is 180 days). The first output file */ /* contains RACF "DELUSER" commands. The second output file */ /* contains "DELETE" commands for user datasets (if the dataset is */ /* migrated the command is changed to "HDELETE") and aliases, and */ /* also contains "DELDSD" commands for any dataset profiles for */ /* the user. */ /* */ /* The record layout for the utility can be found in SYS1.SAMPLIB */ /* member RACDBULD. This program has been tested successfully with */ /* RACF V1.9.2 and RACF V2.1. */ /********************************************************************/ /* SAMPLE EXECUTION JCL: */ /* (This program must be run under the TMP because of TSO commands */ /* that are issued from within the program). */ /* */ /* //MYJOB JOB (ACCT),CLASS=M,MSGCLASS=H */ /* //TMP EXEC PGM=IKJEFT01,REGION=2M */ /* //SYSEXEC DD DSN=REXX.EXEC.LIBRARY,DISP=SHR */ /* //RACFIN DD DSN=RACF.UNLOADED.DATABASE,DISP=SHR */ /* //DELUSER DD DSN=USERID.RACF.DELUSER,DISP=(NEW,CATLG,DELETE), */ /* // UNIT=SYSALLDA,SPACE=(TRK,(15,5),RLSE), */ /* // DCB=(RECFM=FB,LRECL=80,BLKSIZE=3120) */ /* //DELTSO DD DSN=USERID.RACF.DELTSO,DISP=(NEW,CATLG,DELETE), */ /* // UNIT=SYSALLDA,SPACE=(TRK,(15,5),RLSE), */ /* // DCB=(RECFM=FB,LRECL=80,BLKSIZE=3120) */ /* //DELRPT DD SYSOUT=*,DCB=(LRECL=133,RECFM=FBA,BLKSIZE=3990) */ /* //SYSTSPRT DD SYSOUT=* */ /* //SYSTSIN DD * */ /* %RACFUDEL 180 */ /* /* */*/ /* // 180 = THE NUMBER OF DAYS SINCE THE USERID WAS LAST ACCESSED */ /********************************************************************/ arg NUMDAYS if NUMDAYS = '' then NUMDAYS = 180 /* number of days since accessed */ CAT1 ='MCAT.ICF.VACAT43' CAT2 ='MCAT.ICF.VBCAT43' CURDATE = Translate(Date('N')) /* DD MMM YYYY */ CALL RDATE(TODAY) /* Get number of days */ CURNDATE = Substr(RESULT,16,5) /* NNNNN date from RDATE */ RECNUM = 1 /* current record NUMBER */ TOTAL = 0 /* total number of records */ PAGEC = 1 /* page counter */ LINEC = 0 /* line couter */ call NEWPAGE do forever "EXECIO 1 DISKR RACFIN "RECNUM if rc<>0 then leave /* no more records - exit loop */ RECNUM = RECNUM+1 parse pull INREC TYPE = Substr(inrec,1,4) /* record type */ if TYPE <> '0200' then iterate /* not user record */ TEMPY = Substr(inrec,114,4) TEMPM = Substr(inrec,119,2) TEMPD = Substr(inrec,122,2) if TEMPY <> '' then do Call RDATE TEMPM TEMPD TEMPY DATEACC = Substr(RESULT,16,5) /* century date from RDATE */ NEVERUSED = 'N' end else do DATEACC = 00000 NEVERUSED = 'Y' end if CURNDATE - NUMDAYS ^> DATEACC then iterate /* not enough time */ /******************************/ /* USERID met age criteria */ /******************************/ If LINEC > 55 then call NEWPAGE /* do we need a new page? */ LASTDATE = Substr(inrec,114,10) USERID = substr(inrec,6,8) USERIDX = Strip(USERID) NAME = substr(inrec,75,20) if Substr(inrec,50,1) = 'Y' then REVOKMSG = ' REVOKED=Y ' else REVOKMSG = ' REVOKED=N ' if NEVERUSED = 'Y' then ACCMSG = 'LAST ACCESS UNKNOWN (ID NEVER USED)' else ACCMSG = 'LAST ACCESSED ON' LASTDATE queue ' ' USERID NAME REVOKMSG ACCMSG "EXECIO 1 DISKW DELRPT" /******************************************************************/ /* Check to see if user has a TSO segment */ /******************************************************************/ /* "EXECIO 1 DISKR RACFIN "RECNUM */ /* if rc<>0 then leave /* no more records - exit loop */ */ /* RECNUM = RECNUM+1 */ /* parse pull INREC2 */ /* TYPE2 = Substr(inrec2,1,4) /* record type */ */ /* if TYPE2 = '0220' then call DELTSO_USER /* tso segment ? */ */ /******************************************************************/ Call DELTSO_USER /* check for dsns, aliases and dsn profiles */ queue ' DELUSER ' USERID ' /*' NAME '*/' "EXECIO 1 DISKW DELUSER" TOTAL = TOTAL+1 LINEC = LINEC+1 End /* do forever */ /******************************************/ /* write totals */ /******************************************/ If LINEC > 52 then call NEWPAGE /* do we need a new page? */ queue ' ' queue ' ' queue ' THE TOTAL NUMBER OF REVOKED USERIDS THAT HAVE', 'NOT BEEN USED IN' numdays 'DAYS WAS' total "EXECIO 3 DISKW DELRPT" /******************************************/ /* close files and exit */ /******************************************/ "EXECIO 0 DISKR RACFIN (FINIS" "EXECIO 0 DISKW DELUSER (FINIS" "EXECIO 0 DISKW DELTSO (FINIS" "EXECIO 0 DISKW DELRPT (FINIS" Exit 0 /******************************************/ /* SUB ROUTINES */ /******************************************/ DELTSO_USER: /*********************************************************************/ /* check if user has any datasets cataloged */ /*********************************************************************/ junk=outtrap(LISTC.) "LISTCAT LEVEL('" || USERIDX || "') ALL" RETCODE = RC junk=outtrap('off') If RETCODE = 0 then do /* user has datasets */ do SCAN1 = 1 to LISTC.0 if Pos('--- ' || USERIDX,LISTC.SCAN1) = 0 then iterate DSN = Strip(Substr(LISTC.SCAN1,17,44)) line# = SCAN1 do SCAN2 = line# to LISTC.0 if Pos('VOLSER---',LISTC.SCAN2) = 0 then iterate VOL = Substr(LISTC.SCAN2,26,6) leave end /* do SCAN2 */ if Datatype(VOL,number) = 1 then iterate /* tape vol - skip */ if VOL = 'MIGRAT' then CMD = 'HDELETE' else CMD = 'DELETE' queue ' ' || CMD || ' ''' || DSN || '''' "EXECIO 1 DISKW DELTSO" end /* do SCAN1 */ end /*********************************************************************/ /* check for aliases */ /*********************************************************************/ junk=outtrap(dummy.) "LISTCAT ENT('" || USERIDX || "') CAT('" || CAT1 || "')" if rc = 0 then do queue ' DELETE ''' || USERIDX || ''' CATALOG(''' || CAT1 || ''')' "EXECIO 1 DISKW DELTSO" end "LISTCAT ENT('" || USERIDX || "') CAT('" || CAT2 || "')" if rc = 0 then do queue ' DELETE ''' || USERIDX || ''' CATALOG(''' || CAT2 || ''')' "EXECIO 1 DISKW DELTSO" end junk=outtrap('off') /*********************************************************************/ /* Check for RACF dataset profiles */ /*********************************************************************/ junk=outtrap(SEARCH.) "SEARCH MASK(" || USERIDX || ".)" RETCODE = RC junk=outtrap('off') If RETCODE = 0 then do /* user has datasets */ do PROF = 1 to SEARCH.0 queue ' DELDSD ''' || Word(SEARCH.PROF,1) || '''' "EXECIO 1 DISKW DELUSER" end end return NEWPAGE: queue '1 USERIDS THAT HAVE NOT BEEN', 'USED IN' numdays 'DAYS ' curdate ' PAGE' pagec queue ' ' queue ' ' "EXECIO 3 DISKW DELRPT" LINEC = 3 PAGEC = PAGEC+1 return /* rexx */ RDATE: /* */ /* AUTHOR: Mark Zelden */ /* */ /************************************************/ /* Convert MM DD YYYY , YYYY DDD, or NNNNN to */ /* standard date output that includes the day */ /* of the week and the number of days (NNNNN) */ /* from January 1, 1900. This is not the same */ /* as the Century date! Valid input dates range */ /* from 01/01/1900 through 12/31/2172. */ /* */ /* A parm of "TODAY" can also be passed to */ /* the date conversion routine. */ /* MM DD YYYY can also be specifed as */ /* MM/DD/YYYY or MM-DD-YYYY. */ /* */ /* The output format is always as follows: */ /* MM/DD/YYYY.JJJ NNNNN WEEKDAY */ /* */ /* The above value will be put in the special */ /* REXX variable "RESULT" */ /* example: CALL RDATE TODAY */ /* example: CALL RDATE 1996 300 */ /* example: CALL RDATE 10 26 1996 */ /* example: CALL RDATE 10/26/1996 */ /* example: CALL RDATE 10-26-1996 */ /* example: CALL RDATE 35363 */ /* result: 10/26/1996.300 35363 Saturday */ /************************************************/ arg P1 P2 P3 If Pos('/',P1) <> 0 | Pos('-',P1) <> 0 then do PX = Translate(P1,' ','/-') Parse var PX P1 P2 P3 End JULTBL = '000031059090120151181212243273304334' DAY.0 = 'Sunday' DAY.1 = 'Monday' DAY.2 = 'Tuesday' DAY.3 = 'Wednesday' DAY.4 = 'Thursday' DAY.5 = 'Friday' DAY.6 = 'Saturday' Select When P1 = 'TODAY' then do P1 = Substr(date('s'),5,2) P2 = Substr(date('s'),7,2) P3 = Substr(date('s'),1,4) call CONVERT_MDY call THE_END end When P2 = '' & P3 = '' then do call CONVERT_NNNNN call THE_END end When P3 = '' then do call CONVERT_JDATE call DOUBLE_CHECK call THE_END end otherwise do call CONVERT_MDY call DOUBLE_CHECK call THE_END end end /* end select */ /* say RDATE_VAL; exit 0 */ return RDATE_VAL /**********************************************/ /* E N D O F M A I N L I N E C O D E */ /**********************************************/ CONVERT_MDY: if P1<1 | P1>12 then do say 'Invalid month passed to date routine' exit 12 end if P2<1 | P2>31 then do say 'Invalid day passed to date routine' exit 12 end if (P1=4 | P1=6 | P1=9 | P1=11) & P2>30 then do say 'Invalid day passed to date routine' exit 12 end if P3<1900 | P3>2172 then do say 'Invalid year passed to date routine. Must be be 1900-2172' exit 12 end BASE = Substr(JULTBL,((P1-1)*3)+1,3) if (P3//4=0 & P3<>1900 & P3<>2100) then LEAP= 1 else LEAP = 0 if P1 > 2 then BASE = BASE+LEAP JJJ = BASE + P2 MM = P1 DD = P2 YYYY = P3 return CONVERT_NNNNN: if P1<1 | P1>99712 then do say 'Invalid date passed to date routine. NNNNN must be 1-99712' exit 12 end /* Determine YYYY and JJJ */ if P1>365 then P1=P1+1 YEARS_X4=(P1-1)%1461 JJJ=P1-YEARS_X4*1461 if P1 > 73415 then JJJ = JJJ +1 EXTRA_YEARS=(JJJ*3-3)%1096 JJJ=JJJ-(EXTRA_YEARS*1096+2)%3 YYYY=YEARS_X4*4+EXTRA_YEARS+1900 P1 = YYYY ; P2 = JJJ ; call CONVERT_JDATE CONVERT_JDATE: if P1<1900 | P1>2172 then do say 'Invalid year passed to date routine. Must be be 1900-2172' exit 12 end if P2<1 | P2>366 then do say 'Invalid Julian date passed to date routine' exit 12 end if (P1//4=0 & P1<>1900 & P1<>2100) then LEAP= 1 else LEAP = 0 ADJ1 = 0 ADJ2 = 0 Do MM = 1 to 11 VAL1 = Substr(JULTBL,((MM-1)*3)+1,3) VAL2 = Substr(JULTBL,((MM-1)*3)+4,3) if MM >=2 then ADJ2 = LEAP if MM >=3 then ADJ1 = LEAP if P2 > VAL1+ADJ1 & P2 <= VAL2+ADJ2 then do DD = P2-VAL1-ADJ1 MATCH = 'Y' leave end end if MATCH <> 'Y' then do MM = 12 DD = P2-334-LEAP end YYYY = P1 JJJ = P2 return DOUBLE_CHECK: if MM = 2 then do if DD > 28 & LEAP = 0 then do say 'Invalid day passed to date routine' exit 12 end if DD > 29 & LEAP = 1 then do say 'Invalid day passed to date routine' exit 12 end end if LEAP = 0 & JJJ > 365 then do say 'Invalid Julian date passed to date routine' exit 12 end return THE_END: YR_1900 = YYYY-1900 NNNNN = (YR_1900*365) +(YR_1900+3)%4 + JJJ if YYYY > 1900 then NNNNN = NNNNN-1 if YYYY > 2100 then NNNNN = NNNNN-1 INDEX = NNNNN//7 /* index to DAY stem */ WEEKDAY = DAY.INDEX DD = Right(DD,2,'0') MM = Right(MM,2,'0') YYYY = Strip(YYYY) NNNNN = Right(NNNNN,5,'0') JJJ = Right(JJJ,3,'0') RDATE_VAL = MM||'/'||DD||'/'||YYYY||'.'||JJJ||' '||NNNNN||' '||WEEKDAY return @# //SYSUT2 DD DSN=&&PDS(RACFUDEL),UNIT=SYSDA, // DISP=(NEW,PASS,DELETE), // SPACE=(TRK,(1,1,1)), // DCB=(LRECL=80,BLKSIZE=3120,RECFM=FB,DSORG=PO) //********************************************************** //* UNLOAD RACF DATABASE TO A FLAT FILE //********************************************************** //UNLOAD EXEC PGM=IRRDBU00, // PARM='NOLOCKINPUT' //SYSPRINT DD SYSOUT=* //SYSUDUMP DD SYSOUT=* //INDD1 DD DSN=SYS1.RACF,DISP=SHR //OUTDD DD DSN=&&FLAT,DISP=(,PASS,DELETE), // SPACE=(CYL,(10,10),RLSE), // UNIT=SYSDA, //* DCB=(LRECL=2048,BLKSIZE=27998,RECFM=VB) /* RACF 1.9 */ // DCB=(LRECL=4098,BLKSIZE=27998,RECFM=VB) /* RACF 2.1 */ //********************************************************** //* DELETE "DELUSER" AND "DELTSO" FILES IF THEY EXIST //********************************************************** //BR14 EXEC PGM=IEFBR14 //DELUSER DD DSN=TSGMXZ1.RACF.DELUSER,DISP=(MOD,DELETE), // UNIT=SYSALLDA,SPACE=(TRK,1) //DELTSO DD DSN=TSGMXZ1.RACF.DELTSO,DISP=(MOD,DELETE), // UNIT=SYSALLDA,SPACE=(TRK,1) //********************************************************** //* EXEC "RACFUDEL" REXX CLIST IN BATCH //* PARM OF 180 = NUMBER OF DAYS SINCE LAST USED //********************************************************* //TMP EXEC PGM=IKJEFT01,REGION=2M //********************************************************** //SYSEXEC DD DSN=&&PDS,DISP=(OLD,DELETE,DELETE) //RACFIN DD DSN=&&FLAT,DISP=(OLD,DELETE,DELETE) //DELUSER DD DSN=TSGMXZ1.RACF.DELUSER,DISP=(NEW,CATLG,DELETE), // UNIT=SYSALLDA,SPACE=(TRK,(15,5),RLSE), // DCB=(RECFM=FB,LRECL=80,BLKSIZE=3120) //DELTSO DD DSN=TSGMXZ1.RACF.DELTSO,DISP=(NEW,CATLG,DELETE), // UNIT=SYSALLDA,SPACE=(TRK,(15,5),RLSE), // DCB=(RECFM=FB,LRECL=80,BLKSIZE=3120) //DELRPT DD SYSOUT=*,DCB=(LRECL=133,RECFM=FBA,BLKSIZE=3990) //SYSTSPRT DD SYSOUT=* //SYSTSIN DD * %RACFUDEL 180 /* //* 180 = THE NUMBER OF DAYS SINCE THE USERID WAS LAST ACCESSED