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 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;
}
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;
}
#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
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:
Post a Comment