Wednesday, April 12, 2017

Writing a basic auparse program

Auparse is the library distributed with the audit package to make accessing the logs easier. The main issue is that if you wanted to do anything with audit logs, the first thing that you would need to do is to parse the audit logs. That means learning rules about how events are structured and knowing quirks like interlaced events. In reality, its better to use a library meant for the task and just get on with whatever your project is.

Events
Events are denoted by a unit of text that have the same time stamp and serial number. Each record as logged by the audit daemon ends with a carriage return. A record has information about one or more aspects of something happening in the system.

Events can be simple or compound. A simple event has only one record. It is a complete thought that answers who did something to what and how did it turn out. A compound event is composed of multiple records that form a complete thought.

Each record is composed of multiple fields that are recorded as name=value. Anything that does not have an equal sign is superfluous information that can be discarded.

Navigating events
So, if we want to write a utility that processes audit events, the basic scheme is to iterate across each event, then iterate across each record, and then iterate across each field.

The auparse library has functions that allow you to do:
  • Basic initializations/cleanup
  • Iterate across events
  • Access event level informatio (timestamp, serial number, node)
  • Iterate across records
  • Access record level information (record type)
  • Iterate across each field
  • Access field level information (value, interpreted value, field type)
The best source to see the functions available at each level is to open the auparse.h file and look through it. The functions are grouped with a comment explaining the grouping. For details about a function, see its man page.

The most basic program that walks though logs or a file is as follows:

#include <auparse.h>

int main(int argc, char *argv[])
{
        auparse_state_t *au;

        if (argc == 2)
                au = auparse_init(AUSOURCE_FILE, argv[1]);
        else
                au = auparse_init(AUSOURCE_LOGS, NULL);

        auparse_first_record(au);
        do {
                do {
                        do {
                                const char *name = auparse_get_field_name(au);
                        } while (auparse_next_field(au) > 0);
                } while (auparse_next_record(au) > 0);
        } while (auparse_next_event(au) > 0);

        auparse_destroy(au);

        return 0;
}


Compile using:

# gcc -o basic  basic.c -lauparse


This doesn't do anything terribly useful, but it illustrates the basic anatomy. You call auparse_init to create a parsing state variable and to tell auparse where to expect the events to come from. This variable has to be passed to every function that the auparse library has. This is because auparse has been designed so that you can actually have 2 or more instances of the library working on logs simultaneously.

Next the program positions the internal cursor to the first record and then iterates over each event using auparse_next_event, then iterates across each record using auparse_next_record, and then each field using auparse_next_field.


A slightly more complicated example
The following program modifies the first program by using record and field level accessor functions to output the record name and the field name.

#include <stdio.h>
#include <auparse.h>


int main(int argc, char *argv[])
{
        auparse_state_t *au;

        if (argc == 2)
                au = auparse_init(AUSOURCE_FILE, argv[1]);
        else
                au = auparse_init(AUSOURCE_LOGS, NULL);
        if (au == NULL) {
                printf("You need to be root\n");
                return 1;
        }
        auparse_first_record(au);
        do {
                do {
                        char buf[32];
                        const char *type = auparse_get_type_name(au);
                        if (type == NULL) {
                                snprintf(buf, sizeof(buf), "%d",
                                        auparse_get_type(au));
                                type = buf;
                        }
                        printf("Record type: %s - ", type);
                        do {
                                const char *name = auparse_get_field_name(au);
                                printf("%s,", name);
                        } while (auparse_next_field(au) > 0);
                        printf("\b \n");
                } while (auparse_next_record(au) > 0);
        } while (auparse_next_event(au) > 0);

        auparse_destroy(au);

        return 0;
}


Save as audit-fields.c and compile using:

# gcc -o audit-fields audit-fields.c -lauparse


Running the program yields results like this:

# ./audit-fields /var/log/audit/audit.log | head
Record type: CWD - type,cwd
Record type: PATH - type,item,name,inode,dev,mode,ouid,ogid,rdev,obj,nametype
Record type: PROCTITLE - type,proctitle
Record type: SYSCALL - type,arch,syscall,success,exit,a0,a1,a2,a3,items,ppid,pid,auid,uid,gid,euid,suid,fsuid,egid,sgid,fsgid,tty,ses,comm,exe,subj,key
Record type: PROCTITLE - type,proctitle
Record type: SYSCALL - type,arch,syscall,success,exit,a0,a1,a2,a3,items,ppid,pid,auid,uid,gid,euid,suid,fsuid,egid,sgid,fsgid,tty,ses,comm,exe,subj,key
Record type: CWD - type,cwd
Record type: PATH - type,item,name,inode,dev,mode,ouid,ogid,rdev,obj,nametype
Record type: PROCTITLE - type,proctitle
Record type: SYSCALL - type,arch,syscall,success,exit,a0,a1,a2,a3,items,ppid,pid,auid,uid,gid,euid,suid,fsuid,egid,sgid,fsgid,tty,ses,comm,exe,subj,key
Record type: CWD - type,cwd
type,item,name,inode,dev,mode,ouid,ogid,rdev,obj,nametype
type,proctitle
Record type: SYSCALL - type,arch,syscall,success,exit,a0,a1,a2,a3,items,ppid,pid,auid,uid,gid,euid,suid,fsuid,egid,sgid,fsgid,tty,ses,comm,exe,subj,key
type,proctitle
Record type: SYSCALL - type,arch,syscall,success,exit,a0,a1,a2,a3,items,ppid,pid,auid,uid,gid,euid,suid,fsuid,egid,sgid,fsgid,tty,ses,comm,exe,subj,key
type,cwd
type,item,name,inode,dev,mode,ouid,ogid,rdev,obj,nametype
type,proctitle




Conclusion
Its really simple to create a basic program that walks the events in you logs or a file you specify. Any time you want to create a special purpose utility, you can use the source code given here to get your project off to a quick start.

There is another aspect to examining events. Sometimes you don't want to do historical retrieval, but would rather look at things in real time. This requires changing the basic program somewhat. We will look at how to write a simple program suitable to get events from audispd in the next post.

No comments: