Retrieve oracle sysdba audit from table

This post is also available in: Português

Oracle will always generate audit files for some SYSDBA operations in audit_file_dest, no matter what you do. As Doc ID 1528170.1, "Some auditing of SYSDBA is mandatory and cannot be disabled (STARTUP, SHUTDOWN, and CONNECT)." If audit_sys_operations is currently set to TRUE, many files will be created in audit_file_dest by SYSDBA user activity.

In this article, I will explain and show you how to read this information easily within an Oracle table.

I will divide it in 3 steps:

  1. Create a shell script process to generate temporary aud files in "loader" format.
  2. Create a external table and a view to dynamically read this contents.
  3. Create a crontab job to minutely convert new generated audit files.

So let's begin. First of all, create a folder on your OS to keep the shell script and the converted audit files.

In this example, my folder for audit script is /home/oracle/audsys and I will keep the transformed files in /home/oracle/audsys/out. My audit_file_dest is "/u02/admin/orcl/adump".

The script below will convert the audit files from lines to columns with fields separator:

#! /bin/ksh
# Created by Rodrigo Jorge (www.dbarj.com.br) in Oct/2014

audfiles=/u02/admin/orcl/adump/*.aud
outfolder=/home/oracle/audsys/out
outsufix=".ext" # Sufix for temp files

procfile ()
{
  cont=0
  seq=1
  delim="|||"
  ifile=$1
  ofile=$2

  rm -f $ofile

  while read var; do

    if [[ $var == ACTION* ]]
    then
      cont=1
      result="${linha_2ant}${delim}$(echo $linha_ant | sed 's/^[^:]*: //')"
    fi

    if [ $cont -ge 1 -a $cont -le 7 ]
    then
      if [[ $cont == 2 ]] && [[ $var != DATABASE* ]]
      then
         result="${result} ${var}"
      else
         result=${result}${delim}$(echo $var | sed 's/^[^:]*:\[[0-9]*\] //')
         cont=$((cont+1))
      fi
    fi

    if [[ $cont == 8 ]]
    then
      cont=0
      result="${result}${delim}$(basename $ifile)${delim}${seq}"
      seq=$((seq+1))
      echo $result >> $ofile
    fi

    linha_2ant=$linha_ant
    linha_ant=$var

  done <$ifile
}

for f in $audfiles
do
  outfile=$outfolder/$(basename $f)${outsufix}
  if [ ! -f $outfile -o "$outfile" -ot "$f" ]
  then
        procfile $f $outfile
        touch -m -r $f $outfile
  fi
done

shfile=${outfolder}/catfiles.sh
[ ! -f $shfile ] && echo "$(which cat) ${outfolder}/*.aud${outsufix}" > $shfile
[ ! -x $shfile ] && chmod +x $shfile
echo > $outfolder/null.txt

Run the script to generate the initial files. The first run can take a while. After the first, the next executions will be differential.

On the next step, we are creating the directory pointing to our temp folder:

SQL> CREATE DIRECTORY AUDSYSDIR AS '/home/oracle/audsys/out';

Directory created.

SQL>

Now, it's time to create the external table:

CREATE TABLE TB_AUDSYS
(
   LOGDATE VARCHAR2(2000),
   LENGTH NUMBER,
   ACTION CLOB,
   DATABASE_USER VARCHAR2(30),
   PRIVILEGE VARCHAR2(2000),
   CLIENT_USER VARCHAR2(2000),
   CLIENT_TERMINAL VARCHAR2(2000),
   STATUS NUMBER,
   DBID NUMBER,
   FILENAME VARCHAR2(2000),
   SEQ NUMBER
)
ORGANIZATION EXTERNAL
(
   TYPE ORACLE_LOADER
   DEFAULT DIRECTORY AUDSYSDIR
   ACCESS PARAMETERS
   (
      records delimited by newline
      preprocessor audsysdir:'catfiles.sh'
      badfile audsysdir:'query%a_%p.bad'
      logfile audsysdir:'query%a_%p.log'
      fields terminated by '|||'
      missing field values are null
      (
         LOGDATE CHAR(2000),
         LENGTH CHAR(2000) enclosed by X'27',
         ACTION CHAR(100000),
         DATABASE_USER CHAR(2000) enclosed by X'27',
         PRIVILEGE CHAR(2000) enclosed by X'27',
         CLIENT_USER CHAR(2000) enclosed by X'27',
         CLIENT_TERMINAL CHAR(2000) enclosed by X'27',
         STATUS CHAR(2000) enclosed by X'27',
         DBID CHAR(2000) enclosed by X'27',
         FILENAME CHAR(2000),
         SEQ CHAR(2000)
      )
   )
   LOCATION ('null.txt')
)
PARALLEL
REJECT LIMIT 0;

Note here that we use the "fields terminated by '|||'", the same pattern used on the shell script above. Some fileds are also enclosed by X'27', the HEX value for "single quotation". The "location" must be a null file (and it must exist) as we are going to use the preprocessor script, that will "cat" all the files dinamically inside the the temporary folder.

Let's check the output for the table:

SQL> set lines 1000 pages 1000 wrap off
SQL> col logdate format a32
SQL> col length format 9999
SQL> col action format a30
SQL> col database_user format a15
SQL> col privilege format a7
SQL> col client_user format a20
SQL> col client_terminal format a5
SQL> col status format 9999
SQL> col dbid format 9999999999
SQL> col filename format a15
SQL> col seq format 999
SQL> select * from tb_audsys sample (1);

LOGDATE                          LENGTH ACTION                         DATABASE_USER   PRIVILE CLIENT_USER          CLIEN STATUS        DBID FILENAME         SEQ
-------------------------------- ------ ------------------------------ --------------- ------- -------------------- ----- ------ ----------- --------------- ----
Wed Oct 1 16:26:34 2014 -03:00      169 'CONNECT'                      /               NONE    emc12                Not A   1017  2044245708 sirac_ora_15997    2
Sun Oct 12 16:26:48 2014 -03:00     155 'CONNECT'                      /               NONE    emc12                        1017  2044245708 sirac_ora_20302    1
Mon Sep 29 14:29:29 2014 -03:00     166 'CONNECT'                      MDOXDBA         SYSDBA  oracle               pts/0      0  2044245708 sirac_ora_29181    1
Sat Oct 11 16:26:38 2014 -03:00     155 'CONNECT'                      /               NONE    emc12                        1017  2044245708 sirac_ora_45362    1
Mon Sep 29 16:53:30 2014 -03:00     166 'CONNECT'                      MDOXDBA         SYSDBA  oracle               pts/1      0  2044245708 sirac_ora_53002    1
Thu Sep 18 03:00:03 2014 -03:00     165 'CONNECT'                      RMANBACKUP      SYSDBA  oracle                          0  2044245708 sirac_ora_57015    1
Thu Oct 2 16:26:33 2014 -03:00      169 'CONNECT'                      /               NONE    emc12                Not A   1017  2044245708 sirac_ora_62200    2
Fri Oct 10 16:26:37 2014 -03:00     155 'CONNECT'                      /               NONE    emc12                        1017  2044245708 sirac_ora_63673    1

8 rows selected.

SQL>

Good, everything is working. I can create now a view to convert the LOGDATE column from varchar2 to timestamp:

CREATE VIEW VW_AUDSYS AS
SELECT TO_TIMESTAMP_TZ(SUBSTR(LOGDATE, 5), 'Mon fmDDfm hh24:mi:ss YYYY TZH:TZM') LOG_TZ,
       LENGTH,
       TO_CHAR(SUBSTR(ACTION,1,4000)) ACTION,
       DATABASE_USER,
       PRIVILEGE,
       CLIENT_USER,
       CLIENT_TERMINAL,
       STATUS,
       DBID,
       FILENAME,
       SEQ
FROM   TB_AUDSYS;

The last step is to schedule the shell to run every X minutes. So we will have our table always updated table with the latest SYSDBA actions:

[oracle@serveraudsys]$ crontab -l
*/5 * * * * /home/oracle/audsys/procaud.sh
Have you enjoyed? Please leave a comment or give a 👍!

27 comments

Skip to comment form

  1. Very Good !

    • rajkishore on October 9, 2014 at 09:57
    • Reply

    Awesome, It worked perfectly in linux.

    But solaris i am getting error at below line.

    if [[ $var == ACTION* ]] . Seems this command doesn't works in solaris. I ll check for similar command in solaris.

    Its a great script.

    Rajkishore

    1. I'm happy you enjoyed! =]

      Regards,
      RJ

        • rajkishore on October 10, 2014 at 08:41
        • Reply

        Hi Jorge,

        Sorry , i don't want to disturb you. I implemented this one all linux server and it worked perfect.

        While doing the same in solaris box. i few problems. I did some changes to solve some of them.

        But still getting error at if [[ $var == ACTION* ]]

        linha_2ant=Thu Oct 9 10:14:31 2014 -04:00
        linha_ant=LENGTH : '158'
        + read var
        + [[ ACTION :[6] 'COMMIT' == ACTION* ]]
        procaud.sh: [[: not found
        + [ 0 -ge 1 -a 0 -le 7 ]
        + [ 0 = 8 ]
        linha_2ant=LENGTH : '158'
        linha_ant=ACTION :[6] 'COMMIT'

        I tried lot of alternatives to validate this condition, but not working .

        It would be kind of you, if you look into this one.

        usa0300uz1151-tcsdba-NDR1>uname -a
        SunOS usa0300uz1151 5.10 Generic_150400-13 sun4u sparc SUNW,SPARC-Enterprise
        usa0300uz1151-tcsdba-NDR1>echo $SHELL
        /bin/ksh

        1. First of all, you are not disturbing me asking a question! =]

          Try to just change the first line from:
          #! /bin/sh
          To:
          #! /bin/ksh

          If it doesn't work, try to force the ksh execution with: "ksh procaud.sh"

          Regards,
          Rodrigo

    • rajkishore on October 13, 2014 at 12:35
    • Reply

    Hi Rodrigo,

    after running with ksh issue has been solved. But i observed one more thing. some of the records are getting rejected . ( fyi, i have put reject limit unlimited in external table)

    It seems if the action part contain single code (') , then it is putting them in bad file.

    My logfile is like :

    FILENAME CHAR (2000)
    Terminated by "|||"
    Enclosed by "27" and "27"
    Trim whitespace same as SQL Loader
    KUP-04021: field formatting error for field ACTION
    KUP-04037: terminator not found
    KUP-04101: record 4 rejected in file /uv1044/u321/home/tcsdba/audsys/out/null.txt
    KUP-04021: field formatting error for field ACTION
    KUP-04037: terminator not found
    KUP-04101: record 5 rejected in file /uv1044/u321/home/tcsdba/audsys/out/null.txt
    KUP-04021: field formatting error for field ACTION
    KUP-04037: terminator not found
    KUP-04101: record 9 rejected in file /uv1044/u321/home/tcsdba/audsys/out/null.txt
    KUP-04021: field formatting error for field ACTION

    my bad file is like:

    'Thu Oct 9 10:16:45 2014 -04:00'|||'188'|||'insert into tcsdba.t1 values('RAJ')'|||'/'|||'SYSDBA'|||'tcsdba'|||'pts/12'|||'0'|||'845124382'|||'ndr1_ora_9731_1.aud'
    'Thu Oct 9 10:16:46 2014 -04:00'|||'188'|||'insert into tcsdba.t1 values('RAJ')'|||'/'|||'SYSDBA'|||'tcsdba'|||'pts/12'|||'0'|||'845124382'|||'ndr1_ora_9731_1.aud'
    'Thu Oct 9 10:16:46 2014 -04:00'|||'188'|||'insert into tcsdba.t1 values('RAJ')'|||'/'|||'SYSDBA'|||'tcsdba'|||'pts/12'|||'0'|||'845124382'|||'ndr1_ora_9731_1.aud'
    'Thu Oct 9 10:16:46 2014 -04:00'|||'188'|||'insert into tcsdba.t1 values('RAJ')'|||'/'|||'SYSDBA'|||'tcsdba'|||'pts/12'|||'0'|||'845124382'|||'ndr1_ora_9731_1.aud'

    So some of the records are getting skipped.

    May we have to use some kind of delimiter, But i m not sure where to put that.

    Regards
    Rajkishore

    1. I will improve the script to deal with that situation and I will update you when it's done!

    2. Hi Rajkishore,
      I improved the script, the table and the view code. Please try again and check if it works!

      Regards,
      Rodrigo

    • rajkishore on October 14, 2014 at 03:45
    • Reply

    Hi Rodrigo,

    Its awesome. its perfect now. No action is getting rejected now. : ) 🙂 🙂

    I am grateful to you for this help. I have learnt a lot of things from you.

    Its a pleasure discussing with you. I will stay in touch with you . :))

    Regards
    Rajkishore

    1. I'm glad you enjoyed! =]
      See you!

      Regards,
      Rodrigo

    • Abdoali J on June 4, 2015 at 07:11
    • Reply

    Hi Rodrigo,

    This is awesome stuff, worked like charm for me... great work on the script... 🙂

    Thanks,
    Abdo.

    • Raja on July 19, 2016 at 07:36
    • Reply

    Hi,

    excellent script and work perfect in 11G, but does not do in 10G.

    1. Hi Raja,
      Yes, it use some features that didn't exist in 10G.
      Regards,
      RJ

    • Rudy on January 13, 2017 at 19:14
    • Reply

    RJ hello thanks for this script it wonderful I have one small issue im trying to resolve

    if sql statement is not one complete line

    ex: user does

    select name
    from v$database

    user hit enter after select name

    then aud file looks like this

    Fri Jan 13 14:52:29 2017 -06:00
    LENGTH : '181'
    ACTION :[27] 'select name
    from v$database'
    DATABASE USER:[1] '/'
    PRIVILEGE :[6] 'SYSDBA'
    CLIENT USER:[6] 'oracle'
    CLIENT TERMINAL:[5] 'pts/1'
    STATUS:[1] '0'
    DBID:[10] '12344556666'

    and you get |||select name|||from v$database rather than |||'select name from v$database'|||/

    this caused record to go in bad file

    Thanks

    1. Hi Rudy,

      What's happening to you is very strange, as the shell that I've written should be appending the whole SQL statement removing line breaks until the DATABASE USER line appears. Please ensure your shell is running with ksh or provide some output for debug.

      Tks,
      RJ

    • Rudy on January 16, 2017 at 14:25
    • Reply

    RJ

    Thanks I believe that was my issue, was not using ksh did some tests with logon as sysdba and queries with multiple line breaks and all is good
    when using ksh

    One thing I had to do not related to line breaks was I had to do set -f I had some sql being captured that contained *
    ex: select * from v$datafiles and when processed the * caused output to be a ls -l of what ever directory I was in when running shell
    so I would get select (ls output) from v$datafile as action

    set -f took care of issue

    I added it like this

    for f in $audfiles
    do
    set -f
    outfile=$outfolder/$(basename $f)${outsufix}
    if [ ! -f $outfile -o "$outfile" -ot "$f" ]
    then
    procfile $f $outfile
    touch -m -r $f $outfile
    fi
    done

    Thanks again, a wonderful script Ive been looking for something like this for awhile and was struggling developing myself

    • Carin Webb on November 28, 2017 at 10:59
    • Reply

    Hi RJ,

    Firstly thank you for the awesome script, it has save me a lot of time in converting the files.

    Just one question, the client wants the number that is next to the ACTION (ACTION :[147] 'select /*+ opt_param('parallel_execution_enabled')
    to be included in the converted data. As I am not well versed in shell scripting can you please give me some advise on how to include it.

    It will be greatly appreciated.

    Thank You
    Carin Webb

    1. Hi Carin, there is a sed command in my script that remove the numbers on all those fields: sed 's/^[^:]*:\[[0-9]*\] //'
      Replace it by sed 's/^[^:]*://' to keep the numbers in all fields. If you want to keep this number only on ACTION field, you would need to add an extra if clause.
      Regards,
      RJ

        • Carin Webb on November 30, 2017 at 04:26
        • Reply

        Hi RJ,

        Thank you for you response and assistance, I had to get the numbers and have it as a seperate column, which I have achieved, here is the code I added for others to also use and reference:

        if [ $cont -ge 1 -a $cont -le 7 ]
        then
        if [[ $cont == 2 ]] && [[ $var != DATABASE* ]]
        then
        result="${result} ${var}"

        else
        if [[ $var == ACTION* ]]
        then
        result=${result}${delim}${concat}$(echo ${var:9:3} | sed 's/^[^:]*: //')${concat}
        result=${result}${delim}$(echo $var | sed 's/^[^:]*:\[[0-9]*\] //')
        cont=$((cont+1))
        else
        result=${result}${delim}$(echo $var | sed 's/^[^:]*:\[[0-9]*\] //')
        cont=$((cont+1))
        fi

        fi

  2. I'm running the script on AIX server. It is giving me error " Segmentation Fault".
    Please help me.

    1. You are probably facing some bug.. it shouldn't happen. I'd advise to open a SR in MOS..

    • Radhika on January 8, 2018 at 10:57
    • Reply

    Hi RJ

    Awesome script I tried it on my 12c database and was able to create all following above setps. But when I select from table its not getting rows and it gives no row selected I can see log files generating in temp directory. What else can I check ?

    Regards
    Radhika

    1. Hi Radhika,
      You need to check all the steps and see if you have pointed correctly the folder of audit files and the output one. Maybe Oracle is not being able to read/write files from the directory path that you have created. In that case, I would advise you to test an expdp to this directory to check if any file is being generated there.

      Regards,
      RJ

        • Radhika on February 12, 2018 at 11:18
        • Reply

        Thank You RJ i was able to fetch data for my 12,11G databases but I have a 10G database which needs this to be placed as well but preprocessor clause is not working for the same can you let me know what can be an alternative for it.

        Regards
        Radhika.

    • H DBA on October 15, 2018 at 08:43
    • Reply

    Hi RJ,

    Unfortunately, I am not able to understand the shell script 🙁
    It would be very helpful, if you can give an explaination about the shell script in detail, like how it's working & what it's doing exactly ?

    Appreciate your response & time.

    • Elisha on July 6, 2020 at 05:15
    • Reply

    ERROR at line 1:
    ORA-29913: error in executing ODCIEXTTABLEOPEN callout
    ORA-29400: data cartridge error
    KUP-00554: error encountered while parsing access parameters
    KUP-01005: syntax error: found "identifier": expecting one of: "badfile, byteordermark, characterset, column, data, delimited, discardfile, disable_directory_link_check, exit, fields, fixed, load, logfile, language, nodiscardfile, nobadfile, nologfile, date_cache, processing, readsize, string, skip, territory, varia"
    KUP-01008: the bad identifier was: preprocessor
    KUP-01007: at line 2 column 7
    ORA-06512: at "SYS.ORACLE_LOADER", line 19

    • Mahesh on June 6, 2021 at 23:53
    • Reply

    Hi Team,

    IF we dont want sysdba user details and need to fetch application users list means where we need to make changes in this script .

    Could you please give your inputs.

    Regards,
    Mahesh S

Leave a Reply

Your email address will not be published.