Tuesday, April 18, 2017

Writing a program using the auparse_normalize functions

In the last post we took a look at how an auparse program should be changed to run under the audit event dispatcher. While writing it, we saw a problem where we needed to look at several fields to figure out which field we needed to use. When this changes based on the event type and success vs fail, it can become confusing. There is a solution for that and we will look at what that is.

Auparse Normalizer
The auparse library has gained a new API starting around audit-2.7. It wasn't really ready for people to use until around 2.7.2 and still under active development. Its close enough to done that we can start using it in other programs.

In essence, all that is different in using the normalizer is that you call auparse_normalize() and then use a different field accessor function. You do not need to use auparse_find_field().

The field accessor functions come in 2 types. There are some that return a character string, and there are those that return an integer. The ones that return a string are returning metadata about the event and can be used directly. The ones that return an integer only move the auparse internal cursor to the correct field. Then you use the field just as we have in earlier programs by calling auparse_interpret_field(), or auparse_get_field_str(), or auparse_get_field_type(), etc.

The normalizer API will locate and save the field locations for the event's

  • session
  • subject's primary identity
  • subject's secondary identity
  • object's primary identity
  • object's secondary identity
  • second object
  • results
  • key

It also provides metadata about the

  • kind of event
  • kind of subject (privileged, daemon, user)
  • action being performed by subject
  • kind of object
  • how the event was being performed

To illustrate how to use the API, consider the following program. Notice that we loop at the event level and do not need to iterate across records or fields.

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

int main(int argc, char *argv[])
        auparse_state_t *au = auparse_init(AUSOURCE_DESCRIPTOR, 0);
        if (au == NULL) {
                printf("Error initializing event source\n");
                return 1;

        while (auparse_next_event(au) > 0) {
                // Event level
                const char *item, *evkind, *action, *str, *how, *field;

                // Type:
                printf("event: %s\n", auparse_get_type_name(au));

                // Do normalization
                if (auparse_normalize(au, NORM_OPT_NO_ATTRS) == 1) {
                        printf("error normalizing - skipping\n");

                // Event kind
                evkind = auparse_normalize_get_event_kind(au);
                if (evkind)
                        printf("  event-kind: %s\n", evkind);

                // Session
                if (auparse_normalize_session(au) == 1) {
                        const char *ses = auparse_interpret_field(au);
                        printf("  session: %s\n", ses);

                // Subject
                if (auparse_normalize_subject_primary(au) == 1) {
                        const char *subj = auparse_interpret_field(au);
                        field = auparse_get_field_name(au);
                        if (strcmp(subj, "unset") == 0)
                                subj = "system";
                        printf("  subject.primary: %s=%s\n", field, subj);

                if (auparse_normalize_subject_secondary(au) == 1) {
                        const char *subj = auparse_interpret_field(au);
                        field = auparse_get_field_name(au);
                        printf("  subject.secondary: %s=%s\n", field, subj);

                // Action
                action = auparse_normalize_get_action(au);
                if (action) {
                        printf("  action: %s\n", action);

                // Object
                if (auparse_normalize_object_primary(au) == 1) {
                        field = auparse_get_field_name(au);
                        printf("  object.primary: %s=%s\n", field,

                if (auparse_normalize_object_secondary(au) == 1 ) {
                        field = auparse_get_field_name(au);
                        printf("  object.secondary:%s=%s\n", field,

                // Object kind
                str = auparse_normalize_object_kind(au);
                if (strcmp(str, "unknown"))
                        printf("  object-kind: %s\n", str);

                // How action was performed
                how = auparse_normalize_how(au);
                if (how)
                        printf("  how: %s\n", how ? how : "missing-how");

                if (auparse_normalize_key(au) == 1) {
                        const char *key = auparse_interpret_field(au);
                        printf("  key: %s\n", key);

                // Results
                if (auparse_normalize_get_results(au) == 1) {
                        int i = 0;
                        const char *res[] = { "fail", "success" };
                        item = auparse_interpret_field(au);
                        if (strcmp(item, "yes") == 0)
                                i = 1;
                        else if (strncmp(item, "suc", 3) == 0)
                                i = 1;
                        printf("  results: %s\n", res[i]);
        return 0;

Compile this program using:

# gcc -o normalize  normalize.c -lauparse

To run the program pass log data to it from stdout

ausearch --start today --raw | ./normalize

You should see some output similar to this:

  event-kind: audit-daemon
  session: unset
  subject.primary: auid=system
  subject.secondary: uid=root
  action: started-audit
  object-kind: service
  results: success

You can use this program to look over normalized log data to aid in understanding where each of the parts come from.

OK, so how does this fix up the problem we saw last time? Now all we need to do is call auparse_normalize() and then auparse_normalize_subject_primary(). That's it. No conditional. Rather than present the whole program again, I will just give you the changed code that goes in the handle_event function.  Update: I am going to publish the whole function to emphasize that there is no longer a loop across records when using the auparse_normalize API.

static void handle_event(auparse_state_t *au,
        auparse_cb_event_t cb_event_type, void *user_data)
    if (cb_event_type != AUPARSE_CB_EVENT_READY)

    int type = auparse_get_type(au);
    if (type == AUDIT_USER_LOGIN) {
        char msg[256],  *name = NULL;
        const char *res = NULL;

        /* create a message */
        if (!auparse_normalize(au, NORM_OPT_NO_ATTRS)) {
            if (auparse_normalize_subject_primary(au) == 1)
                name = strdup(auparse_interpret_field(au));
            if (auparse_normalize_get_results(au) == 1)
                res = auparse_get_field_str(au);
        snprintf(msg, sizeof(msg), "%s log in %s",
            name ? name : "someone", res ? res : "unknown");

        /* send a message */
        NotifyNotification *n = notify_notification_new(note,
                msg, NULL);
        notify_notification_set_urgency(n, NOTIFY_URGENCY_NORMAL);
        notify_notification_set_timeout(n, 3000); //3 seconds
        notify_notification_show (n, NULL);


One thing to know about the code above is that you won't get the right answer until you run it with the audit-2.7.6 library which should be released soon.(I found a bug in the library and corrected it while writing this example program.)

We have talked about using all of the various pieces of the auparse library. It should simplify writing analytical programs. Next time we will tie everything together with a short little project to send an email when certain events occur. I was going to do it in this post but decided it was long enough already.

No comments: