This article is intended to introduce and acquaint the reader with Farsight Security® Inc.’s AXA C API. It will introduce the libaxa C programming API and showcase a simple working example program.

This information is intended for users of SIE Remote Access (SRA). This article assumes the reader is familiar with AXA and related technologies. To brush up, on AXA, a good starting point is the Advanced Exchange Access document or the three part blog series:

This article, geared towards intermediate-level C programmers, is by no means an exhaustive API reference. For that, the reader is directed to the accompanying Doxygen-based API manual that is available in the AXA GitHub repository. Please see the Additional Information section below for the URL. This article covers libaxa version 1.1.2.

Table of Contents

sratesttool

sratesttool is a Unix command-line utility capable of the following:

Throughout this short series, we’ll examine how all of this done. In this first article, we’ll cover the main driver which contains the initialization code and controls the main program flow.

To wit:

$ sratesttool -s tls:user@sraserver,1021 -c 255 -w ch=255
Farsight Security SRA Test Tool
connecting to tls:user@sraserver,1021...
connected
parsing watch: ch=255...
parse ch=255 OK
setting watch on server...
watch set OK
parsing channel: 255...
parse 255 OK
enabling channel on server...
channel enabled OK
NMSG watch hit, channel: 255 @ 2015-07-29T19:13:21.111788988
NMSG watch hit, channel: 255 @ 2015-07-29T19:13:21.612488985
NMSG watch hit, channel: 255 @ 2015-07-29T19:13:22.113173007
NMSG watch hit, channel: 255 @ 2015-07-29T19:13:22.613782882
NMSG watch hit, channel: 255 @ 2015-07-29T19:13:23.114434957
NMSG watch hit, channel: 255 @ 2015-07-29T19:13:23.615050077
NMSG watch hit, channel: 255 @ 2015-07-29T19:13:24.115665912
NMSG watch hit, channel: 255 @ 2015-07-29T19:13:24.616344928
NMSG watch hit, channel: 255 @ 2015-07-29T19:13:25.117007970
NMSG watch hit, channel: 255 @ 2015-07-29T19:13:25.617640972
^C10 total watch hits

Please note sratesttool is not a full implementation of the AXA protocol. It is meant as a tutorial on how to stand up a simple connection to the SRA server, issue a few commands, and stream data. Sratesttool is only written to work for TLS and many libaxa code paths are not taken nor is robust error checking performed. It is intended as an entry-level program to get the reader familiar with the AXA protocol and the libaxa API.

Sratesttool.c

The code for sratesttool is contained in a single source file that can be compiled into a fully functional program. To build it, you’ll need to link against the libaxa library. You can find the source code in Farsight’s AXA GitHub repository. If you’re a Debian-user, there are packages ready for download as well.

While we will cover the source code across several articles, the full source code is available for download from Farsight Security’s blog-code GitHub page.

Preamble

The first section contains the source code license and the standard C header file include progression:

/*
 * Copyright (c) 2015 by Farsight Security, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <sysexits.h>
#include <unistd.h>
#include <signal.h>
#include <limits.h>

#include <axa/client.h>
#include <axa/axa_endian.h>

Server-Specific Functions

The following server-specific functions wrap libaxa functions. We’ll explore all of them in detail later, for now it is sufficient to understand what they do:

/* connect to server
 *
 * returns true on success, false on failure
 */
static bool srvr_connect(void);

/* send an AXA command to the server and wait for the response
 *
 * returns true on success, false on failure
 */
static bool srvr_cmd(axa_tag_t,             /* AXA tag or AXA_TAG_NONE */
                        axa_p_op_t,         /* AXA opcode (original) */
                        const void *,       /* body of message to send */
                        size_t,             /* length of message */
                        axa_p_op_t);        /* expected response opcode */

/* wait for a response from the server
 *
 * returns true on success, false on failure
 */
static bool srvr_wait_resp(axa_p_op_t,      /* expected response opcode */
                        axa_p_op_t);        /* original opcode*/

/* process a message from the server */
static void srvr_process(void);

/* disconnect from server and print a message */
static void srvr_disconnect(const char *,   /* message to print */
                        ...)                /* optional varargs */
                        AXA_PF(1,2);

Miscellaneous Functions

Miscellaneous functions are declared next:

/* stop everything and shutdown */
static void stop(int                        /* exitcode */
                        );
/* signal handler */
static void sigterm(int                     /* signal number of caught sig */
                        );
/* print usage */
static void usage(const char *              /* program name */
                        );

Global Variables

There is a small collection of global data:

static axa_client_t client;
static const char *server_str;
static const char *watch_str;
static const char *channel_str;
static int terminated;
static uint64_t hits;

Main Function Argument Processing

The initial stanza of main function is standard and uninteresting command-line argument processing:

int
main(int argc, char **argv)
{
    int opt;
    axa_emsg_t emsg;
    const char *cp;

    while ((opt = getopt(argc, argv, "c:hs:w:")) >= 0)
    {
        switch (opt)
        {
            case 'c':
                channel_str = optarg;
                break;
            case 'h':
                usage(argv[0]);
                return (EXIT_SUCCESS);
            case 's':
                server_str = optarg;
                break;
            case 'w':
                watch_str = optarg;
                break;
            default:
                usage(argv[0]);
                return (EX_USAGE);
        }
    }
    if (server_str == NULL || channel_str == NULL || watch_str == NULL)
    {
        fprintf(stderr, "-s, -c, -w are all required\n");
        return (EX_USAGE);
    }

Axa Initialization

Next, we encounter the first libaxa API function calls. The first stanza sets up the AXA logging substructure. One of the conveniences libaxa offers is an opaque “three stream” logging / syslog interface. With libaxa, you have the following logging streams:

Sratesttool does not use the syslog interface and as such initializes the loggers to emit messages only to stderr. For more information, see the Doxygen manual for axa_parse_log_opt().

After the logging initialization, we initialize the AXA client context. This is an opaque structure that contains all of the state required to open and maintain an SRA server connection.

Next comes the signal processing. In order to provide an orderly exit from signals that cause the termination of the sratesttool process with garbage collection and a statistics report, we catch and hand them off to our simple signal handler.

/* set the global program name (for logging) */
axa_set_me(argv[0]);
/* set the tracing stream to emit to stderr only */
AXA_ASSERT(axa_parse_log_opt(&emsg, "trace,off,stderr"));
/* set the error stream to emit to stderr only */
AXA_ASSERT(axa_parse_log_opt(&emsg, "error,off,stderr"));
/* set the accounting stream to emit to stderr only */
AXA_ASSERT(axa_parse_log_opt(&emsg, "acct,off,stderr"));

/* initialize the AXA syslog interface */
axa_syslog_init();

/* ensure all stdio FDs are open and ready for business */
axa_clean_stdio();

/* initialize the client context (including AXA IO engine) */
axa_client_init(&client);

/* catch and handle these signals for graceful exit */
signal(SIGPIPE, SIG_IGN);
signal(SIGHUP, sigterm);
signal(SIGTERM, sigterm);
signal(SIGINT, sigterm);

axa_trace_msg("Farsight Security SRA Test Tool\n");

Server Connection

At this point, our AXA client is initialized and sratesttool is ready for business. It then reaches out and tries to make a connection to the server. If something goes wrong, sratesttool will bail. More robust implementations such as {sra,rad}tool, {sra,rad}tunnel and axaclientd will attempt to reconnect using an exponential back off and retry algorithm.

A lot of core libaxa code is contained in the srvr_connect() function including:

We will explore this in more detail later.

/* try (once) to connect to the SRA server and bail on any error */
if (!srvr_connect())
{
    exit(EXIT_FAILURE);
}

Process Data

We now descend into the infinite event loop. The first check will always be to see if sratesttool needs to exit (as the result of an asynchronous event such as the user hitting ctrl-c at the console).

The call to axa_io_wait() tells libaxa to simply wait up to 10ms for input from the server. Internally, libaxa polls its list of file descriptors to look for input from the server. If the timer expires and no data is available, axa_io_wait() will return AXA_IO_BUSY which just returns control to the top of the loop. If data is waiting to be read, AXA_IO_OK will be returned and srvr_process() will be called to handle it (covered in detail later). For sratesttool, most of the time, this should be watch hits. If something goes wrong when polling, AXA_IO_ERR is returned and sratesttool will disconnect and quit.

Also of note, axa_io_wait() is called without keepalive functionality (not needed for this example) and without tunnel debugging (only used for SSH-based connections).

    /* event loop where we continuously wait/send data/wait */
    for (;;)
    {
        if (terminated != 0)
        {
            stop(terminated);
        }
        /* wait up to 10ms for data from the server */
        switch (axa_io_wait(&emsg,
                    &client.io,         /* address of AXA IO engine */
                    10,                 /* wait up to this many ms */
                    false,              /* wake up to send a keepalive */
                    false))             /* pay attention to tunnel msgs */
        {
            /* AXA IO error */
            case AXA_IO_ERR:
                srvr_disconnect("%s", emsg.c);
                break;
            /* incomplete response, poll() and try again */
            case AXA_IO_BUSY:
                break;
            /* operation has finished, time to process the result */
            case AXA_IO_OK:
                srvr_process();
                break;
            default:
                AXA_FAIL("impossible axa_io_wait() result");
        }
    }
}

Connecting To The Server And Setting State

The first srvr_* function we’ll dissect is a big one, responsible for connecting to the SRA server, setting the watch and enabling the channel. First, we explain important AXA-specific data types / variables:

static bool
srvr_connect(void)
{
    axa_p_watch_t watch;
    size_t watch_len;
    axa_p_channel_t channel;
    axa_emsg_t emsg;
    bool res;
    axa_tag_t cur_tag;

Connect To The Server

The axa_client_open() function attempts to open a server connection and establish state for our client’s connection. The first two arguments, passed by reference, will be filled in by libaxa(emsg) if something goes wrong, client if something doesn’t). The next argument, server_str is a specially crafted text string of the following format: transport:user@server[,port].

The input socket buffer size is set to be 256 * 1024, a larger than normal value due to the potentially high-volume of SIE data transited by SRA. Internally, this will be requested to be set by setsockopt(..SO_RCVBUF..).

The return value from axa_client_open() is evaluated in a small switch table which checks for errors or non sequitur as per the following:

axa_trace_msg("connecting to %s...", server_str);
switch (axa_client_open(&emsg,      /* if func fails, will contain error */
                        &client,    /* client context */
                        server_str, /* server address string */
                        false,      /* true to enable RAD mode */
                        false,      /* true when SSH tunnel debugging */
                        256 * 1024, /* socket buffer size */
                        false))     /* true for non-blocking */
{
    /* permanent failure, connection has been closed, check emsg */
    case AXA_CONNECT_ERR:
        axa_error_msg("%s", emsg.c);
        return (false);
    /* A temporary failure, connection has been closed, check emsg.
     * In more robust implementations, we would try to reconnect to the
     * server.
     */
    case AXA_CONNECT_TEMP:
        axa_error_msg("%s", emsg.c);
        srvr_disconnect("%s", emsg.c);
        return (false);
    /* connect is complete */
    case AXA_CONNECT_DONE:
        break;V
    /* non-blocking connection waiting for TCP SYN/ACK or TLS handshake */
    case AXA_CONNECT_INCOM:
        AXA_FAIL("impossible result from axa_client_open");
    /* connect is complete (incl xmit of AXA_P_OP_NOP) */
    case AXA_CONNECT_NOP:
        break;
    /* connect is complete (incl xmit of AXA_P_OP_USER) */
    case AXA_CONNECT_USER:
        srvr_disconnect("%s", emsg.c);
        return (false);
}

Pause The Output

After successfully connecting to the server but before any watches are set or channels are enabled, an AXA_P_OP_PAUSE command is sent. This “pause” command tells the server to stop sending data and to actually discard it, server side. This is a global switch the pauses data output for all channels for the current user.

This is the first time we encounter the srvr_cmd() function which is used to send commands and data to the SRA server. We’ll cover it in detail later but for now it’s important to note that it accepts an AXA tag argument:

And two AXA opcode arguments:

/* block any watch hits until we are ready */
if (!srvr_cmd(AXA_TAG_MIN, AXA_P_OP_PAUSE, NULL, 0, AXA_P_OP_OK))
{
    return (false);
}

Parse and Set the Watch

Next, the watch string is parsed with a call to axa_parse_watch(). If there is a problem parsing it, libaxa will do its best to emit a helpful error message in emsg. However, if the watch string makes no sense, the function will fail with an empty emsg.

For a verbose treatment of what SRA watches can look like, see sratool(1).

After successfully parsing the watch, it is set on the server with a call to srvr_cmd() with an AXA opcode of AXA_P_OP_WATCH and an expected server response of AXA_P_OP_OK.

cur_tag = 1;
/* parse the watch string */
axa_trace_msg("parsing watch: %s...\n", watch_str);
res = axa_parse_watch(&emsg, &watch, &watch_len, watch_str);
if (!res)
{
    if (emsg.c[0] == '\0')
    {
        axa_error_msg("unrecognized \"-w %s\"", watch_str);
    }
    else
    {
        axa_error_msg("\"-w %s\": %s", watch_str, emsg.c);
    }
    return (false);
}
else
{
    axa_trace_msg("parse %s OK\n", watch_str);
}

/* set the watch on the server */
axa_trace_msg("setting watch on server...\n");
if (!srvr_cmd(cur_tag, AXA_P_OP_WATCH, &watch, watch_len, AXA_P_OP_OK))
{
    return (false);
}
axa_trace_msg("watch set OK\n");

Parse And Enable The Channel

Next, in the same manner as above, the channel string is parsed and enabled on the server.

memset(&channel, 0, sizeof(channel));
/* parse the channel string */
axa_trace_msg("parsing channel: %s...\n", channel_str);
if (!axa_parse_ch(&emsg, &channel.ch, channel_str,
            strlen(channel_str), true, true))
{
    axa_error_msg("\"-c %s\": %s", channel_str, emsg.c);
    return (false);
}
else
{
    axa_trace_msg("parse %s OK\n", channel_str);
}

channel.on = 1;
axa_trace_msg("enabling channel on server...\n");
if (!srvr_cmd(AXA_TAG_MIN, AXA_P_OP_CHANNEL, &channel, sizeof(channel),
    AXA_P_OP_OK))
{
    return (false);
}

Un-Pause The Output

After the channel is enabled, sratesttool is ready to start streaming watch hits. It lets the server know by sending the opcode AXA_P_OP_GO which tells the server its time to un-pause the output.

Now the server connection process is complete and the function returns successfully.

    if (!srvr_cmd(AXA_TAG_MIN, AXA_P_OP_GO, NULL, 0, AXA_P_OP_OK))
    {
        return (false);
    }
    axa_trace_msg("channel enabled OK\n");
    return (true);
}

Run a Command on The Server

The next function, as we’ve already seen, srvr_cmd(), is used to send opcodes and data to the SRA server. It is a wrapper to axa_client_send() with additional code to disconnect via srvr_disconnect() if a problem occurs.

We’ve already covered the tag and opcode arguments, but the srvr_cmd() function also accepts data in the form of an opaque pointer and its length. The call to axa_client_send() fires off the request to the server and, if it doesn’t fail, a call to srvr_wait_resp() is made to process the response.

static bool
srvr_cmd(axa_tag_t tag, axa_p_op_t op, const void *b, size_t b_len,
     axa_p_op_t resp_op)
{
    axa_p_hdr_t hdr;
    axa_emsg_t emsg;
    char pbuf[AXA_P_STRLEN];

    if (!axa_client_send(&emsg, &client, tag, op, &hdr, b, b_len))
    {
        srvr_disconnect("sending %s failed: %s",
                axa_p_to_str(pbuf, sizeof(pbuf), true, &hdr, b),
                emsg.c);
        return (false);
    }

    return (srvr_wait_resp(resp_op, op));
}

Wait for a Response From the Server

The main event loop where sratesttool receives input from the SRA server and decides what to do with it, called srvr_wait_resp() is covered next. The function can be broken down into three parts:

  1. Check to see if it’s time to quit
  2. Receive input from the server
  3. Determine what to do with the input

Check To See If It’s Time To Quit

The first thing srvr_wait_resp() does is check to see if the global sentry value terminated is set to true. For this to be the case, one of the signals sratesttool was initialized to catch, has been caught. Most of the time, this will be SIGINT from the user pressing “ctrl-c” at the console. In this case, stop() (covered below) is called which begins the shutdown process.

static bool
srvr_wait_resp(axa_p_op_t resp_op, axa_p_op_t orig_op)
{
    bool result, done;
    axa_emsg_t emsg;

    result = false;
    done = false;
    do
    {
        if (terminated != 0)
        {
            stop(terminated);
        }

Receive Input From The Server

If it’s not time to quit, control is passed to axa_input(). This function accepts the standard emsg argument we’ve already seen, a pointer to a client.io context, and the number of milliseconds to block and wait for input which in this case is INT_MAX (this can be considered an indefinite block while waiting for data).

The result is switched and the following cases are evaluated:

        switch (axa_input(&emsg, &client.io, INT_MAX))
        {
            case AXA_IO_ERR:
                axa_error_msg("%s", emsg.c);
                goto out;
            case AXA_IO_BUSY:
                continue;
            case AXA_IO_OK:
                break;
            default:
                AXA_FAIL("impossible axa_input() result");
        }

Determine What To Do With The Input

Next, sratesttool evaluates the received AXA protocol message header’s opcode inside of a large switch table. For posterity, all of the possible opcodes are listed and in a more robust implementation, each code path would likely be populated. In the case of sratesttool only two server responses are interesting:

After a processing the response, axa_recv_flush() is called to purge (free()) the AXA protocol message from the IO context.

If something went wrong, *axa_client_backoff()8 is called which will result in the client closing the connection to the server and the shutting down of the IO context.

        switch ((axa_p_op_t)client.io.recv_hdr.op)
        {
            case AXA_P_OP_NOP:
                break;
            case AXA_P_OP_HELLO:
                if (!axa_client_hello(&emsg, &client, NULL))
                {
                    axa_error_msg("%s", emsg.c);
                }
                else
                {
                    axa_trace_msg("connected OK");
                }
                break;
            case AXA_P_OP_OPT:
                done = true;
                break;
            case AXA_P_OP_OK:
                if (resp_op == client.io.recv_hdr.op &&
                        orig_op == client.io.recv_body->result.orig_op)
                {
                    result = true;
                    done = true;
                }
                break;
            case AXA_P_OP_ERROR:
                axa_error_msg("server returned error");
                done = true;
                break;
            case AXA_P_OP_MISSED:
            case AXA_P_OP_MISSED_RAD:
            case AXA_P_OP_WHIT:
            case AXA_P_OP_AHIT:
            case AXA_P_OP_WLIST:
            case AXA_P_OP_ALIST:
            case AXA_P_OP_CLIST:
                /* in this function, these are all unexpected op codes */
                break;
            case AXA_P_OP_USER:
            case AXA_P_OP_JOIN:
            case AXA_P_OP_PAUSE:
            case AXA_P_OP_GO:
            case AXA_P_OP_WATCH:
            case AXA_P_OP_WGET:
            case AXA_P_OP_ANOM:
            case AXA_P_OP_AGET:
            case AXA_P_OP_STOP:
            case AXA_P_OP_ALL_STOP:
            case AXA_P_OP_CHANNEL:
            case AXA_P_OP_CGET:
            case AXA_P_OP_ACCT:
            case AXA_P_OP_RADU:
            default:
                AXA_FAIL("impossible AXA op of %d from %s",
                     client.io.recv_hdr.op, client.io.label);
        }
        axa_recv_flush(&client.io);
    }
    while (!done);

out:
    if (!result)
    {
        /* disconnect for now if we failed to get the right response */
        axa_client_backoff(&client);
    }
    return (result);
}

Disconnect From the Server

If something egregious went wrong, sratesttool will emit an error message via axa_verror_msg() and shutdown the server connection via axa_client_backoff(). As has been mentioned before, a more robust implementation may make use of the internal backoff timers and attempt a reconnect.

static void AXA_PF(1,2)
srvr_disconnect(const char *p, ...)
{
    va_list args;

    va_start(args, p);
    axa_verror_msg(p, args);
    va_end(args);

    axa_client_backoff(&client);
}

Stop and Shutdown

The stop() function is called any time the interrupted sentinel evaluates to not 0. It performs an orderly shutdown, reports the number of watch hits, and then exits the program.

static void AXA_NORETURN
stop(int s)
{
    axa_client_close(&client);
    axa_io_cleanup();

    axa_trace_msg("%"PRIu64" total watch hits\n", hits);

    exit(s);
}

Read SIE Data From The Server

The function that is called from the main event loop everytime SIE data is ready is called srvr_process(). First, axa_recv_buf() is called and the result is evaluated. Important to note, the axa_recv_buf() can block so if this is undesirable, use of the other server read functions covered earlier such as axa_io_wait() or axa_input(). Once an AXA_IO_OK is returned, control passes to another switch table where the response opcode is evaluated.

static void
srvr_process(void)
{
    int n;
    char buf[BUFSIZ];
    axa_emsg_t emsg;
    struct timespec ts;
    struct tm *tm_info;

    switch (axa_recv_buf(&emsg, &client.io))
    {
        case AXA_IO_OK:
            break;
        case AXA_IO_ERR:
            srvr_disconnect("%s", emsg.c);
            return;
        case AXA_IO_BUSY:
            return;             /* wait for the rest */
        case AXA_IO_KEEPALIVE:  /* NA for this example */
        case AXA_IO_TUNERR:     /* NA for this example */
            break;
    }

Process the Watch Hit

The only case sratesttool is interested in is AXA_P_OP_WHIT. When the received header opcode is a “watch hit”, sratesttool next figures out the type of watch hit being reported to the client, NMSG or IP. While sratesttool is not interested in the contents of the watch hit, it does need to extract the timestamp, which is stored in different places depending on the type. Next, a human readable string is constructed using the type of watch hit, the SIE channel it occurred on, and the timestamp of the watch hit (including nanoseconds). The hits counter is incremented and control proceeds to the end of the switch table where, as seen above, axa_recv_flush() is called to purge the AXA protocol message from the IO context.

    n = 0;
    switch ((axa_p_op_t)client.io.recv_hdr.op)
    {
        case AXA_P_OP_NOP:
            break;
        case AXA_P_OP_ERROR:
            srvr_disconnect(" ");
            return;
        case AXA_P_OP_MISSED:
        case AXA_P_OP_MISSED_RAD:
            break;
        case AXA_P_OP_WHIT:
            switch (client.io.recv_body->whit.hdr.type)
            {
                case AXA_P_WHIT_NMSG:
                    ts.tv_sec = client.io.recv_body->whit.nmsg.hdr.ts.tv_sec;
                    ts.tv_nsec = client.io.recv_body->whit.nmsg.hdr.ts.tv_nsec;
                    break;
                case AXA_P_WHIT_IP:
                    ts.tv_sec =
                        AXA_P2H32(client.io.recv_body->whit.ip.hdr.tv.tv_sec);
                    ts.tv_nsec = 1000 *
                        AXA_P2H32(client.io.recv_body->whit.ip.hdr.tv.tv_usec);
                    break;
            }
            tm_info = localtime((time_t *)&ts.tv_sec);
            n = strftime(buf + n, 26, "%Y-%m-%dT%H:%M:%S", tm_info);
            snprintf(buf + n, BUFSIZ - n, ".%ld", ts.tv_nsec);
            axa_trace_msg("%s watch hit, channel: %3d @ %s\n",
                    client.io.recv_body->whit.hdr.type == AXA_P_WHIT_NMSG ?
                    "NMSG" : "IP",
                    client.io.recv_body->whit.hdr.ch,
                    buf);
            hits++;
            break;
        case AXA_P_OP_AHIT:
        case AXA_P_OP_OK:
        case AXA_P_OP_HELLO:
        case AXA_P_OP_WLIST:
        case AXA_P_OP_ALIST:
        case AXA_P_OP_OPT:
        case AXA_P_OP_CLIST:
            break;
        case AXA_P_OP_USER:
        case AXA_P_OP_JOIN:
        case AXA_P_OP_PAUSE:
        case AXA_P_OP_GO:
        case AXA_P_OP_WATCH:
        case AXA_P_OP_WGET:
        case AXA_P_OP_ANOM:
        case AXA_P_OP_AGET:
        case AXA_P_OP_STOP:
        case AXA_P_OP_ALL_STOP:
        case AXA_P_OP_CHANNEL:
        case AXA_P_OP_CGET:
        case AXA_P_OP_ACCT:
        case AXA_P_OP_RADU:
        default:
            AXA_FAIL("impossible AXA op of %d from %s",
                 client.io.recv_hdr.op, client.io.label);
    }

    axa_recv_flush(&client.io);
}

Signal Handler

The signal handler simply sets the global sentinel terminated to the integral value of the signal. This allows the uppers layers to:

  1. Know when something asynchronous has happened and it’s time to quit
  2. Know which signal was sent to sratesttool

If a signal is sent repeatedly, it is considered urgent and sratesttool will reset the default signal handler via signal(sig, SIG_DFL) which will immediately terminate the program.

void
sigterm(int sig)
{
    terminated = sig;

    signal(sig, SIG_DFL);       /* quit early on repeated signals */
}

Usage

Finally, we conclude with a simple usage function instructing the user how to invoke sratesttool.

static void
usage(const char *name)
{
    printf("SRA Test Tool (c) 2015 Farsight Security, Inc.\n");
    printf("\nUsage: %s [options]\n", name);
    printf("[-s tls:user@server,port]\n");
    printf("\t\tuser:      SRA username\n");
    printf("\t\tserver:    SRA server\n");
    printf("\t\tport:      TCP port (typically 1021)\n");
    printf("[-h]");
    printf("\t\tthis prose\n");
    printf("[-c channel]");
    printf("\tenable channel (default: \"255\", SIE Heartbeat)\n");
    printf("[-w watch]");
    printf("\tset watch (default: \"ch=255\", SIE Heartbeat channel)\n");
}

Conclusion

We’ve covered the internals of sratesttool, a very simple “hello world” SRA client. If you are interested in learning more, please check out the AXA distribution repository which contains sratool and radtool which are more robust implementations of SRA and RAD clients built on top of libaxa.

Additional Information

About Farsight Security

Farsight Security, Inc. is the world’s largest provider of historical and real-time DNS intelligence solutions. We enable security teams to qualify, enrich and correlate all sources of threat data and ultimately save time when it is most critical - during an attack or investigation. Our solutions provide enterprise, government and security industry personnel and platforms with unmatched global visibility, context and response. Farsight Security is headquartered in San Mateo, California, USA. Learn more about how we can empower your threat platform and security team with Farsight Security passive DNS solutions at www.farsightsecurity.com or follow us on Twitter: @FarsightSecInc.

This document was originally published on Farsight’s blog as Advanced Exchange Access: The C Programming API by Mike Schiffman https://www.farsightsecurity.com/txt-record/2015/07/30/mschiffm-axa-api-c-1/