root/usr/src/lib/auditd_plugins/remote/transport.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * transport layer for audit_remote (handles connection establishment, gss
 * context initialization, message encryption and verification)
 *
 */

#include <assert.h>
#include <audit_plugin.h>
#include <errno.h>
#include <fcntl.h>
#include <gssapi/gssapi.h>
#include <libintl.h>
#include <mtmalloc.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <poll.h>
#include <pthread.h>

#include "audit_remote.h"


static int              sockfd = -1;
static struct hostent   *current_host;
static gss_OID          *current_mech_oid;
static in_port_t        current_port;
static boolean_t        flush_transq;

static char             *ver_str = "01";        /* supported protocol version */
static char             *ver_str_concat;        /* concat serv/client version */

static gss_ctx_id_t     gss_ctx;
static boolean_t        gss_ctx_initialized;

pthread_t               recv_tid;               /* receiving thread */
static pthread_once_t   recv_once_control = PTHREAD_ONCE_INIT;

extern int              timeout;                /* connection timeout */

extern pthread_mutex_t  plugin_mutex;
transq_hdr_t            transq_hdr;

/*
 * The three locks synchronize the simultaneous actions on top of transmission
 * queue, socket, gss_context.
 */
pthread_mutex_t         transq_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t         sock_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t         gss_ctx_lock = PTHREAD_MUTEX_INITIALIZER;

/* reset routine synchronization - required by the sending thread */
pthread_mutex_t         reset_lock = PTHREAD_MUTEX_INITIALIZER;
static boolean_t        reset_in_progress;      /* reset routine in progress */

#define NP_CLOSE        -1              /* notification pipe - close message */
#define NP_EXIT         -2              /* notification pipe - exit message */
boolean_t               notify_pipe_ready;
int                     notify_pipe[2]; /* notif. pipe - receiving thread */

pthread_cond_t          reset_cv = PTHREAD_COND_INITIALIZER;
static close_rsn_t      recv_closure_rsn;

#define MAX_TOK_LEN     (128 * 1000)    /* max token length we accept (B) */

/* transmission queue helpers */
static void             transq_dequeue(transq_node_t *);
static boolean_t        transq_enqueue(transq_node_t **, gss_buffer_t,
    uint64_t);
static int              transq_retransmit(void);

static boolean_t        init_poll(int);
static void             do_reset(int *, struct pollfd *, boolean_t);
static void             do_cleanup(int *, struct pollfd *, boolean_t);

static void             init_recv_record(void);
static void             *recv_record(void *);
static int              connect_timeout(int, struct sockaddr *, int);
static int              send_timeout(int, const char *, size_t);
static int              recv_timeout(int, char *, size_t);
static int              send_token(int *, gss_buffer_t);
static int              recv_token(int, gss_buffer_t);


/*
 * report_err() - wrapper, mainly due to enhance the code readability - report
 * error to syslog via call to __audit_syslog().
 */
static void
report_err(char *msg)
{
        __audit_syslog("audit_remote.so", LOG_CONS | LOG_NDELAY, LOG_DAEMON,
            LOG_ERR, msg);

}


/*
 * report_gss_err() - GSS API error reporting
 */
static void
report_gss_err(char *msg, OM_uint32 maj_stat, OM_uint32 min_stat)
{
        gss_buffer_desc msg_buf;
        OM_uint32       _min, msg_ctx;
        char            *err_msg;

        /* major stat */
        msg_ctx = 0;
        do {
                (void) gss_display_status(&_min, maj_stat, GSS_C_GSS_CODE,
                    *current_mech_oid, &msg_ctx, &msg_buf);
                (void) asprintf(&err_msg,
                    gettext("GSS API error - %s(%u): %.*s\n"), msg, maj_stat,
                    msg_buf.length, (char *)msg_buf.value);
                if (err_msg != NULL) {
                        report_err(err_msg);
                        free(err_msg);
                }
                (void) gss_release_buffer(&_min, &msg_buf);
        } while (msg_ctx);

        /* minor stat */
        msg_ctx = 0;
        do {
                (void) gss_display_status(&_min, min_stat, GSS_C_MECH_CODE,
                    *current_mech_oid, &msg_ctx, &msg_buf);
                (void) asprintf(&err_msg,
                    gettext("GSS mech error - %s(%u): %.*s\n"), msg, min_stat,
                    msg_buf.length, (char *)msg_buf.value);
                if (err_msg != NULL) {
                        report_err(err_msg);
                        free(err_msg);
                }
                (void) gss_release_buffer(&_min, &msg_buf);
        } while (msg_ctx);
}

/*
 * prot_ver_negotiate() - negotiate/acknowledge the protocol version. Currently,
 * there is only one version supported by the plugin - "01".
 * Note: connection must be initiated prior version negotiation
 */
static int
prot_ver_negotiate()
{
        gss_buffer_desc out_buf, in_buf;
        size_t          ver_str_concat_sz;

        /*
         * Set the version proposal string - once we support more than
         * version "01" this part should be extended to solve the concatenation
         * of supported version identifiers.
         */
        out_buf.value = (void *)ver_str;
        out_buf.length = strlen((char *)out_buf.value);
        DPRINT((dfile, "Protocol version proposal (size=%d): %.*s\n",
            out_buf.length, out_buf.length, (char *)out_buf.value));

        if (send_token(&sockfd, &out_buf) < 0) {
                DPRINT((dfile, "Sending protocol version token failed\n"));
                return (-1);
        }

        if (recv_token(sockfd, &in_buf) < 0) {
                DPRINT((dfile, "Receiving protocol version token failed\n"));
                return (-1);
        }

        /*
         * Verify the sent/received string - memcmp() is sufficient here
         * because we support only one version and it is represented by
         * the "01" string. The received version has to be "01" string as well.
         */
        if (out_buf.length != in_buf.length ||
            memcmp(out_buf.value, in_buf.value, out_buf.length) != 0) {
                DPRINT((dfile, "Verification of the protocol version strings "
                    "failed [%d:%s][%d:%s]\n", out_buf.length,
                    (char *)out_buf.value, in_buf.length,
                    (char *)in_buf.value));
                free(in_buf.value);
                return (-1);
        }

        /*
         * Prepare the concatenated client/server version strings later used
         * as an application_data field in the gss_channel_bindings_struct
         * structure.
         */
        ver_str_concat_sz = out_buf.length + in_buf.length + 1;
        ver_str_concat = (char *)calloc(1, ver_str_concat_sz);
        if (ver_str_concat == NULL) {
                report_err(gettext("Memory allocation failed"));
                DPRINT((dfile, "Memory allocation failed: %s\n",
                    strerror(errno)));
                free(in_buf.value);
                return (-1);
        }
        (void) memcpy(ver_str_concat, out_buf.value, out_buf.length);
        (void) memcpy(ver_str_concat + out_buf.length, in_buf.value,
            in_buf.length);
        DPRINT((dfile, "Concatenated version strings: %s\n", ver_str_concat));

        DPRINT((dfile, "Protocol version agreed.\n"));
        free(in_buf.value);
        return (0);
}

/*
 * sock_prepare() - creates and connects socket. Function returns
 * B_FALSE/B_TRUE on failure/success and sets the err_rsn accordingly to the
 * reason of failure.
 */
static boolean_t
sock_prepare(int *sockfdptr, struct hostent *host, close_rsn_t *err_rsn)
{
        struct sockaddr_storage addr;
        struct sockaddr_in      *sin;
        struct sockaddr_in6     *sin6;
        size_t                  addr_len;
        int                     sock;

        DPRINT((dfile, "Creating socket for %s\n", host->h_name));
        bzero(&addr, sizeof (addr));
        addr.ss_family = host->h_addrtype;
        switch (host->h_addrtype) {
        case AF_INET:
                sin = (struct sockaddr_in *)&addr;
                addr_len = sizeof (struct sockaddr_in);
                bcopy(host->h_addr_list[0],
                    &(sin->sin_addr), sizeof (struct in_addr));
                sin->sin_port = current_port;
                break;
        case AF_INET6:
                sin6 = (struct sockaddr_in6 *)&addr;
                addr_len = sizeof (struct sockaddr_in6);
                bcopy(host->h_addr_list[0],
                    &(sin6->sin6_addr), sizeof (struct in6_addr));
                sin6->sin6_port = current_port;
                break;
        default:
                /* unknown address family */
                *err_rsn = RSN_UNKNOWN_AF;
                return (B_FALSE);
        }
        if ((sock = socket(addr.ss_family, SOCK_STREAM, 0)) == -1) {
                *err_rsn = RSN_SOCKET_CREATE;
                return (B_FALSE);
        }
        DPRINT((dfile, "Socket created, fd=%d, connecting..\n", sock));

        if (connect_timeout(sock, (struct sockaddr *)&addr, addr_len)) {
                (void) close(sock);
                *err_rsn = RSN_CONNECTION_CREATE;
                return (B_FALSE);
        }
        *sockfdptr = sock;
        DPRINT((dfile, "Connected to %s via fd=%d\n", host->h_name,
            *sockfdptr));

        return (B_TRUE);
}

/*
 * establish_context() - establish the client/server GSS context.
 *
 * Note: connection must be established and version negotiated (in plain text)
 * prior to establishing context.
 */
static int
establish_context()
{
        gss_buffer_desc                         send_tok, recv_tok, *token_ptr;
        OM_uint32                               maj_stat, min_stat;
        OM_uint32                               init_sec_min_stat, ret_flags;
        gss_name_t                              gss_name;
        char                                    *gss_svc_name = "audit";
        char                                    *svc_name;
        struct gss_channel_bindings_struct      input_chan_bindings;

        /* GSS service name = gss_svc_name + "@" + remote hostname (fqdn) */
        (void) asprintf(&svc_name, "%s@%s", gss_svc_name, current_host->h_name);
        if (svc_name == NULL) {
                report_err(gettext("Cannot allocate service name\n"));
                DPRINT((dfile, "Memory allocation failed: %s\n",
                    strerror(errno)));
                return (-1);
        }
        DPRINT((dfile, "Service name: %s\n", svc_name));

        send_tok.value = svc_name;
        send_tok.length = strlen(svc_name);
        maj_stat = gss_import_name(&min_stat, &send_tok,
            (gss_OID)GSS_C_NT_HOSTBASED_SERVICE, &gss_name);
        if (maj_stat != GSS_S_COMPLETE) {
                report_gss_err(gettext("initializing context"), maj_stat,
                    min_stat);
                free(svc_name);
                return (-1);
        }
        token_ptr = GSS_C_NO_BUFFER;
        gss_ctx = GSS_C_NO_CONTEXT;

        /* initialize channel binding */
        bzero(&input_chan_bindings, sizeof (input_chan_bindings));
        input_chan_bindings.initiator_addrtype = GSS_C_AF_NULLADDR;
        input_chan_bindings.acceptor_addrtype = GSS_C_AF_NULLADDR;
        input_chan_bindings.application_data.length = strlen(ver_str_concat);
        input_chan_bindings.application_data.value = ver_str_concat;

        (void) pthread_mutex_lock(&gss_ctx_lock);
        do {
                maj_stat = gss_init_sec_context(&init_sec_min_stat,
                    GSS_C_NO_CREDENTIAL, &gss_ctx, gss_name, *current_mech_oid,
                    GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG
                    | GSS_C_CONF_FLAG, 0, &input_chan_bindings, token_ptr,
                    NULL, &send_tok, &ret_flags, NULL);

                if (token_ptr != GSS_C_NO_BUFFER) {
                        (void) gss_release_buffer(&min_stat, &recv_tok);
                }

                if (send_tok.length != 0) {
                        DPRINT((dfile,
                            "Sending init_sec_context token (size=%d)\n",
                            send_tok.length));
                        if (send_token(&sockfd, &send_tok) < 0) {
                                free(svc_name);
                                (void) gss_release_name(&min_stat, &gss_name);
                                (void) pthread_mutex_unlock(&gss_ctx_lock);
                                return (-1);
                        }
                }
                if (send_tok.value != NULL) {
                        free(send_tok.value);   /* freeing svc_name */
                        send_tok.value = NULL;
                        send_tok.length = 0;
                }

                if (maj_stat != GSS_S_COMPLETE &&
                    maj_stat != GSS_S_CONTINUE_NEEDED) {
                        report_gss_err(gettext("initializing context"),
                            maj_stat, init_sec_min_stat);
                        if (gss_ctx == GSS_C_NO_CONTEXT) {
                                (void) gss_delete_sec_context(&min_stat,
                                    &gss_ctx, GSS_C_NO_BUFFER);
                        }
                        (void) gss_release_name(&min_stat, &gss_name);
                        (void) pthread_mutex_unlock(&gss_ctx_lock);
                        return (-1);
                }

                if (maj_stat == GSS_S_CONTINUE_NEEDED) {
                        DPRINT((dfile, "continue needed... "));
                        if (recv_token(sockfd, &recv_tok) < 0) {
                                (void) gss_release_name(&min_stat, &gss_name);
                                (void) pthread_mutex_unlock(&gss_ctx_lock);
                                return (-1);
                        }
                        token_ptr = &recv_tok;
                }
        } while (maj_stat == GSS_S_CONTINUE_NEEDED);
        (void) gss_release_name(&min_stat, &gss_name);

        DPRINT((dfile, "context established\n"));
        (void) pthread_mutex_unlock(&gss_ctx_lock);
        return (0);
}

/*
 * delete_context() - release GSS context.
 */
static void
delete_context()
{
        OM_uint32       min_stat;

        (void) gss_delete_sec_context(&min_stat, &gss_ctx, GSS_C_NO_BUFFER);
        DPRINT((dfile, "context deleted\n"));
}

/*
 * send_token() - send GSS token over the wire.
 */
static int
send_token(int *fdptr, gss_buffer_t tok)
{
        uint32_t        len;
        uint32_t        lensz;
        char            *out_buf;
        int             fd;

        (void) pthread_mutex_lock(&sock_lock);
        if (*fdptr == -1) {
                (void) pthread_mutex_unlock(&sock_lock);
                DPRINT((dfile, "Socket detected as closed.\n"));
                return (-1);
        }
        fd = *fdptr;

        len = htonl(tok->length);
        lensz = sizeof (len);

        out_buf = (char *)malloc((size_t)(lensz + tok->length));
        if (out_buf == NULL) {
                (void) pthread_mutex_unlock(&sock_lock);
                report_err(gettext("Memory allocation failed"));
                DPRINT((dfile, "Memory allocation failed: %s\n",
                    strerror(errno)));
                return (-1);
        }
        (void) memcpy((void *)out_buf, (void *)&len, lensz);
        (void) memcpy((void *)(out_buf + lensz), (void *)tok->value,
            tok->length);

        if (send_timeout(fd, out_buf, (lensz + tok->length))) {
                (void) pthread_mutex_unlock(&sock_lock);
                free(out_buf);
                return (-1);
        }

        (void) pthread_mutex_unlock(&sock_lock);
        free(out_buf);
        return (0);
}


/*
 * recv_token() - receive GSS token over the wire.
 */
static int
recv_token(int fd, gss_buffer_t tok)
{
        uint32_t        len;

        if (recv_timeout(fd, (char *)&len, sizeof (len))) {
                return (-1);
        }
        len = ntohl(len);

        /* simple DOS prevention mechanism */
        if (len > MAX_TOK_LEN) {
                report_err(gettext("Indicated invalid token length"));
                DPRINT((dfile, "Indicated token length > %dB\n", MAX_TOK_LEN));
                return (-1);
        }

        tok->value = (char *)malloc(len);
        if (tok->value == NULL) {
                report_err(gettext("Memory allocation failed"));
                DPRINT((dfile, "Memory allocation failed: %s\n",
                    strerror(errno)));
                tok->length = 0;
                return (-1);
        }

        if (recv_timeout(fd, tok->value, len)) {
                free(tok->value);
                tok->value = NULL;
                tok->length = 0;
                return (-1);
        }

        tok->length = len;
        return (0);
}


/*
 * I/O functions
 */

/*
 * connect_timeout() - sets nonblocking I/O on a socket and timeout-connects
 */
static int
connect_timeout(int sockfd, struct sockaddr *name, int namelen)
{
        int                     flags;
        struct pollfd           fds;
        int                     rc;
        struct sockaddr_storage addr;
        socklen_t               addr_len = sizeof (addr);


        flags = fcntl(sockfd, F_GETFL, 0);
        if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1) {
                return (-1);
        }
        if (connect(sockfd, name, namelen)) {
                if (!(errno == EINTR || errno == EINPROGRESS ||
                    errno == EWOULDBLOCK)) {
                        return (-1);
                }
        }
        fds.fd = sockfd;
        fds.events = POLLOUT;
        for (;;) {
                fds.revents = 0;
                rc = poll(&fds, 1, timeout * 1000);
                if (rc == 0) {  /* timeout */
                        return (-1);
                } else if (rc < 0) {
                        if (errno == EINTR || errno == EAGAIN) {
                                continue;
                        } else {
                                return (-1);
                        }
                }
                if (fds.revents) {
                        if (getpeername(sockfd, (struct sockaddr *)&addr,
                            &addr_len))
                                return (-1);
                } else {
                        return (-1);
                }
                return (0);
        }
}

/*
 * send_timeout() - send data (in chunks if needed, each chunk in timeout secs).
 */
static int
send_timeout(int fd, const char *buf, size_t len)
{
        int             bytes;
        struct pollfd   fds;
        int             rc;

        fds.fd = fd;
        fds.events = POLLOUT;

        while (len) {
                fds.revents = 0;
                rc = poll(&fds, 1, timeout * 1000);
                if (rc == 0) {  /* timeout */
                        return (-1);
                } else if (rc < 0) {
                        if (errno == EINTR || errno == EAGAIN) {
                                continue;
                        } else {
                                return (-1);
                        }
                }
                if (!fds.revents) {
                        return (-1);
                }

                bytes = write(fd, buf, len);
                if (bytes < 0) {
                        if (errno == EINTR) {
                                continue;
                        } else {
                                return (-1);
                        }
                } else if (bytes == 0) {        /* eof */
                        return (-1);
                }

                len -= bytes;
                buf += bytes;
        }

        return (0);
}

/*
 * recv_timeout() - receive data (in chunks if needed, each chunk in timeout
 * secs). In case the function is called from receiving thread, the function
 * cycles the poll() call in timeout seconds (waits for input from server).
 */
static int
recv_timeout(int fd, char *buf, size_t len)
{
        int             bytes;
        struct pollfd   fds;
        int             rc;

        fds.fd = fd;
        fds.events = POLLIN;

        while (len) {
                fds.revents = 0;
                rc = poll(&fds, 1, timeout * 1000);
                if (rc == 0) {                  /* timeout */
                        return (-1);
                } else if (rc < 0) {
                        if (errno == EINTR || errno == EAGAIN) {
                                continue;
                        } else {
                                return (-1);
                        }
                }

                if (!fds.revents) {
                        return (-1);
                }

                bytes = read(fd, buf, len);
                if (bytes < 0) {
                        if (errno == EINTR) {
                                continue;
                        } else {
                                return (-1);
                        }
                } else if (bytes == 0) {        /* eof */
                        return (-1);
                }

                len -= bytes;
                buf += bytes;
        }

        return (0);
}

/*
 * read_fd() - reads data of length len from the given file descriptor fd to the
 * buffer buf, in chunks if needed. Function returns B_FALSE on failure,
 * otherwise B_TRUE. Function preserves errno, if it was set by the read(2).
 */
static boolean_t
read_fd(int fd, char *buf, size_t len)
{
        int             bytes;
#ifdef DEBUG
        size_t          len_o = len;
#endif

        while (len) {
                bytes = read(fd, buf, len);
                if (bytes < 0) {                /* err */
                        if (errno == EINTR || errno == EAGAIN) {
                                continue;
                        } else {
                                return (B_FALSE);
                        }
                } else if (bytes == 0) {        /* eof */
                        return (B_FALSE);
                }

                len -= bytes;
                buf += bytes;
        }

        DPRINT((dfile, "read_fd: Read %d bytes.\n", len_o - len));
        return (B_TRUE);
}

/*
 * write_fd() - writes buf of length len to the opened file descriptor fd, in
 * chunks if needed. The data from the pipe are processed in the receiving
 * thread. Function returns B_FALSE on failure, otherwise B_TRUE. Function
 * preserves errno, if it was set by the write(2).
 */
static boolean_t
write_fd(int fd, char *buf, size_t len)
{
        int             bytes;
#ifdef DEBUG
        size_t          len_o = len;
#endif

        while (len) {
                bytes = write(fd, buf, len);
                if (bytes == -1) {              /* err */
                        if (errno == EINTR || errno == EAGAIN) {
                                continue;
                        } else {
                                return (B_FALSE);
                        }
                }

                len -= bytes;
                buf += bytes;
        }

        DPRINT((dfile, "write_fd: Wrote %d bytes.\n", len_o - len));
        return (B_TRUE);
}

/*
 * Plug-in entry point
 */

/*
 * send_record() - send an audit record to a host opening a connection,
 * negotiate version and establish context if necessary.
 */
send_record_rc_t
send_record(struct hostlist_s *hostlptr, const char *input, size_t in_len,
    uint64_t sequence, close_rsn_t *err_rsn)
{
        gss_buffer_desc         in_buf, out_buf;
        OM_uint32               maj_stat, min_stat;
        int                     conf_state;
        int                     rc;
        transq_node_t           *node_ptr;
        uint64_t                seq_n;  /* sequence in the network byte order */
        boolean_t               init_sock_poll = B_FALSE;

        /*
         * We need to grab the reset_lock here, to prevent eventual
         * unsynchronized cleanup calls within the reset routine (reset caused
         * by the receiving thread) and the initialization calls in the
         * send_record() code path.
         */
        (void) pthread_mutex_lock(&reset_lock);

        /*
         * Check whether the socket was closed by the recv thread prior to call
         * send_record() and behave accordingly to the reason of the closure.
         */
        if (recv_closure_rsn != RSN_UNDEFINED) {
                *err_rsn = recv_closure_rsn;
                if (recv_closure_rsn == RSN_GSS_CTX_EXP) {
                        rc = SEND_RECORD_RETRY;
                } else {
                        rc = SEND_RECORD_NEXT;
                }
                recv_closure_rsn = RSN_UNDEFINED;
                (void) pthread_mutex_unlock(&reset_lock);
                return (rc);
        }

        /*
         * Send request to other then previously used host.
         */
        if (current_host != hostlptr->host) {
                DPRINT((dfile, "Set new host: %s\n", hostlptr->host->h_name));
                if (sockfd != -1) {
                        (void) pthread_mutex_unlock(&reset_lock);
                        reset_transport(DO_CLOSE, DO_SYNC);
                        return (SEND_RECORD_RETRY);
                }
                current_host = (struct hostent *)hostlptr->host;
                current_mech_oid = &hostlptr->mech;
                current_port = hostlptr->port;
        }

        /* initiate the receiving thread */
        (void) pthread_once(&recv_once_control, init_recv_record);

        /* create and connect() socket, negotiate the protocol version */
        if (sockfd == -1) {
                /* socket operations */
                DPRINT((dfile, "Socket creation and connect\n"));
                if (!sock_prepare(&sockfd, current_host, err_rsn)) {
                        /* we believe the err_rsn set by sock_prepare() */
                        (void) pthread_mutex_unlock(&reset_lock);
                        return (SEND_RECORD_NEXT);
                }

                /* protocol version negotiation */
                DPRINT((dfile, "Protocol version negotiation\n"));
                if (prot_ver_negotiate() != 0) {
                        DPRINT((dfile,
                            "Protocol version negotiation failed\n"));
                        (void) pthread_mutex_unlock(&reset_lock);
                        reset_transport(DO_CLOSE, DO_SYNC);
                        *err_rsn = RSN_PROTOCOL_NEGOTIATE;
                        return (SEND_RECORD_NEXT);
                }

                /* let the socket be initiated for poll() */
                init_sock_poll = B_TRUE;
        }

        if (!gss_ctx_initialized) {
                DPRINT((dfile, "Establishing context..\n"));
                if (establish_context() != 0) {
                        (void) pthread_mutex_unlock(&reset_lock);
                        reset_transport(DO_CLOSE, DO_SYNC);
                        *err_rsn = RSN_GSS_CTX_ESTABLISH;
                        return (SEND_RECORD_NEXT);
                }
                gss_ctx_initialized = B_TRUE;
        }

        /* let the recv thread poll() on the sockfd */
        if (init_sock_poll) {
                init_sock_poll = B_FALSE;
                if (!init_poll(sockfd)) {
                        *err_rsn = RSN_INIT_POLL;
                        (void) pthread_mutex_unlock(&reset_lock);
                        return (SEND_RECORD_RETRY);
                }
        }

        (void) pthread_mutex_unlock(&reset_lock);

        /* if not empty, retransmit contents of the transmission queue */
        if (flush_transq) {
                DPRINT((dfile, "Retransmitting remaining (%ld) tokens from "
                    "the transmission queue\n", transq_hdr.count));
                if ((rc = transq_retransmit()) == 2) { /* gss context exp */
                        reset_transport(DO_CLOSE, DO_SYNC);
                        *err_rsn = RSN_GSS_CTX_EXP;
                        return (SEND_RECORD_RETRY);
                } else if (rc == 1) {
                        reset_transport(DO_CLOSE, DO_SYNC);
                        *err_rsn = RSN_OTHER_ERR;
                        return (SEND_RECORD_NEXT);
                }
                flush_transq = B_FALSE;
        }

        /*
         * Concatenate sequence number and the new record. Note, that the
         * pointer to the chunk of memory allocated for the concatenated values
         * is later passed to the transq_enqueu() function which stores the
         * pointer in the transmission queue; subsequently called
         * transq_dequeue() frees the allocated memory once the MIC is verified
         * by the recv_record() function.
         *
         * If we return earlier than the transq_enqueue() is called, it's
         * necessary to free the in_buf.value explicitly prior to return.
         *
         */
        in_buf.length = in_len + sizeof (sequence);
        in_buf.value = malloc(in_buf.length);
        if (in_buf.value == NULL) {
                        report_err(gettext("Memory allocation failed"));
                        DPRINT((dfile, "Memory allocation failed: %s\n",
                            strerror(errno)));
                        reset_transport(DO_CLOSE, DO_SYNC);
                        *err_rsn = RSN_MEMORY_ALLOCATE;
                        return (SEND_RECORD_FAIL);
        }
        seq_n = htonll(sequence);
        (void) memcpy(in_buf.value, &seq_n, sizeof (seq_n));
        (void) memcpy((char *)in_buf.value + sizeof (seq_n), input, in_len);

        /* wrap sequence number and the new record to the per-message token */
        (void) pthread_mutex_lock(&gss_ctx_lock);
        if (gss_ctx != NULL) {
                maj_stat = gss_wrap(&min_stat, gss_ctx, 1, GSS_C_QOP_DEFAULT,
                    &in_buf, &conf_state, &out_buf);
                (void) pthread_mutex_unlock(&gss_ctx_lock);
                switch (maj_stat) {
                case GSS_S_COMPLETE:
                        break;
                case GSS_S_CONTEXT_EXPIRED:
                        reset_transport(DO_CLOSE, DO_SYNC);
                        free(in_buf.value);
                        *err_rsn = RSN_GSS_CTX_EXP;
                        return (SEND_RECORD_RETRY);
                default:
                        report_gss_err(gettext("gss_wrap message"), maj_stat,
                            min_stat);
                        reset_transport(DO_CLOSE, DO_SYNC);
                        free(in_buf.value);
                        *err_rsn = RSN_OTHER_ERR;
                        return (SEND_RECORD_NEXT);
                }
        } else {        /* GSS context deleted by the recv thread */
                (void) pthread_mutex_unlock(&gss_ctx_lock);
                reset_transport(DO_CLOSE, DO_SYNC);
                free(in_buf.value);
                *err_rsn = RSN_OTHER_ERR;
                return (SEND_RECORD_NEXT);
        }


        /* enqueue the to-be-sent token into transmission queue */
        (void) pthread_mutex_lock(&transq_lock);
        if (!transq_enqueue(&node_ptr, &in_buf, sequence)) {
                (void) pthread_mutex_unlock(&transq_lock);
                reset_transport(DO_CLOSE, DO_SYNC);
                free(in_buf.value);
                (void) gss_release_buffer(&min_stat, &out_buf);
                *err_rsn = RSN_OTHER_ERR;
                return (SEND_RECORD_RETRY);
        }
        DPRINT((dfile, "Token enqueued for later verification\n"));
        (void) pthread_mutex_unlock(&transq_lock);

        /* send token */
        if (send_token(&sockfd, &out_buf) < 0) {
                DPRINT((dfile, "Token sending failed\n"));
                reset_transport(DO_CLOSE, DO_SYNC);
                (void) gss_release_buffer(&min_stat, &out_buf);

                (void) pthread_mutex_lock(&transq_lock);
                transq_dequeue(node_ptr);
                (void) pthread_mutex_unlock(&transq_lock);

                *err_rsn = RSN_OTHER_ERR;
                return (SEND_RECORD_NEXT);
        }
        DPRINT((dfile, "Token sent (transq size = %ld)\n", transq_hdr.count));

        (void) gss_release_buffer(&min_stat, &out_buf);

        return (SEND_RECORD_SUCCESS);
}

/*
 * init_recv_record() - initialize the receiver thread
 */
static void
init_recv_record()
{
        DPRINT((dfile, "Initiating the recv thread\n"));
        (void) pthread_create(&recv_tid, NULL, recv_record, NULL);

}


/*
 * recv_record() - the receiver thread routine
 */
static void *
recv_record(void *arg __unused)
{
        OM_uint32               maj_stat, min_stat;
        gss_qop_t               qop_state;
        gss_buffer_desc         in_buf = GSS_C_EMPTY_BUFFER;
        gss_buffer_desc         in_buf_mic = GSS_C_EMPTY_BUFFER;
        transq_node_t           *cur_node;
        uint64_t                r_seq_num;      /* received sequence number */
        boolean_t               token_verified;
        boolean_t               break_flag;
        struct pollfd           fds[2];
        int                     fds_cnt;
        struct pollfd           *pipe_fd = &fds[0];
        struct pollfd           *recv_fd = &fds[1];
        uint32_t                len;
        int                     rc;
        pipe_msg_t              np_data;

        DPRINT((dfile, "Receiver thread initiated\n"));

        /*
         * Fill in the information in the vector of file descriptors passed
         * later on to the poll() function. In the initial state, there is only
         * one struct pollfd in the vector which contains file descriptor of the
         * notification pipe - notify_pipe[1]. There might be up to two file
         * descriptors (struct pollfd) in the vector - notify_pipe[1] which
         * resides in the vector during the entire life of the receiving thread,
         * and the own file descriptor from which we read data sent by the
         * remote server application.
         */
        pipe_fd->fd = notify_pipe[1];
        pipe_fd->events = POLLIN;
        recv_fd->fd = -1;
        recv_fd->events = POLLIN;
        fds_cnt = 1;

        /*
         * In the endless loop, try to grab some data from the socket or
         * notify_pipe[1].
         */
        for (;;) {

                pipe_fd->revents = 0;
                recv_fd->revents = 0;
                recv_closure_rsn = RSN_UNDEFINED;

                /* block on poll, thus rc != 0 */
                rc = poll(fds, fds_cnt, -1);
                if (rc == -1) {
                        if (errno == EAGAIN || errno == EINTR) {
                                /* silently continue on EAGAIN || EINTR */
                                continue;
                        } else {
                                /* log the debug message in any other case */
                                DPRINT((dfile, "poll() failed: %s\n",
                                    strerror(errno)));
                                report_err(gettext("poll() failed.\n"));
                                continue;
                        }
                }

                /*
                 * Receive a message from the notification pipe. Information
                 * from the notification pipe takes precedence over the received
                 * data from the remote server application.
                 *
                 * Notification pipe message format - message accepted
                 * from the notify pipe comprises of two parts (int ||
                 * boolean_t), where if the first part (sizeof (int)) equals
                 * NP_CLOSE, then the second part (sizeof (boolean_t)) signals
                 * the necessity of broadcasting (DO_SYNC/DO_NOT_SYNC) the end
                 * of the reset routine.
                 */
                if (pipe_fd->revents & POLLIN) {
                        DPRINT((dfile, "An event on notify pipe detected\n"));
                        if (!read_fd(pipe_fd->fd, (char *)&np_data,
                            sizeof (np_data))) {
                                DPRINT((dfile, "Reading notify pipe failed: "
                                    "%s\n", strerror(errno)));
                                report_err(gettext("Reading notify pipe "
                                    "failed"));
                        } else {
                                switch (np_data.sock_num) {
                                case NP_EXIT:   /* exit receiving thread */
                                        do_cleanup(&fds_cnt, recv_fd,
                                            np_data.sync);
                                        pthread_exit((void *)NULL);
                                        break;
                                case NP_CLOSE:  /* close and remove recv_fd */
                                        do_reset(&fds_cnt, recv_fd,
                                            np_data.sync);
                                        continue;
                                default:        /* add rc_pipe to the fds */
                                        recv_fd->fd = np_data.sock_num;
                                        fds_cnt = 2;
                                        continue;
                                }
                        }
                }
                /* Receive a token from the remote server application */
                if (recv_fd->revents & POLLIN) {
                        DPRINT((dfile, "An event on fd detected\n"));
                        if (!read_fd(recv_fd->fd, (char *)&len, sizeof (len))) {
                                DPRINT((dfile, "Token length recv failed\n"));
                                recv_closure_rsn = RSN_TOK_RECV_FAILED;
                                reset_transport(DO_CLOSE, DO_NOT_SYNC);
                                continue;
                        }
                        len = ntohl(len);

                        /* simple DOS prevention mechanism */
                        if (len > MAX_TOK_LEN) {
                                report_err(gettext("Indicated invalid token "
                                    "length"));
                                DPRINT((dfile, "Indicated token length > %dB\n",
                                    MAX_TOK_LEN));
                                recv_closure_rsn = RSN_TOK_TOO_BIG;
                                reset_transport(DO_CLOSE, DO_NOT_SYNC);
                                continue;
                        }

                        in_buf.value = (char *)malloc(len);
                        if (in_buf.value == NULL) {
                                report_err(gettext("Memory allocation failed"));
                                DPRINT((dfile, "Memory allocation failed: %s\n",
                                    strerror(errno)));
                                recv_closure_rsn = RSN_MEMORY_ALLOCATE;
                                reset_transport(DO_CLOSE, DO_NOT_SYNC);
                                continue;
                        }
                        if (!read_fd(recv_fd->fd, (char *)in_buf.value, len)) {
                                DPRINT((dfile, "Token value recv failed\n"));
                                free(in_buf.value);
                                recv_closure_rsn = RSN_TOK_RECV_FAILED;
                                reset_transport(DO_CLOSE, DO_NOT_SYNC);
                                continue;
                        }

                        in_buf.length = len;
                }

                /*
                 * Extract the sequence number and the MIC from
                 * the per-message token
                 */
                (void) memcpy(&r_seq_num, in_buf.value, sizeof (r_seq_num));
                r_seq_num = ntohll(r_seq_num);
                in_buf_mic.length = in_buf.length - sizeof (r_seq_num);
                in_buf_mic.value = (char *)in_buf.value + sizeof (r_seq_num);

                /*
                 * seq_num/r_seq_num - the sequence number does not need to
                 * be unique in the transmission queue. Any token in the
                 * transmission queue with the same seq_num as the acknowledge
                 * token received from the server is tested. This is due to the
                 * fact that the plugin cannot influence (in the current
                 * implementation) sequence numbers generated by the kernel (we
                 * are reusing record sequence numbers as a transmission queue
                 * sequence numbers). The probability of having two or more
                 * tokens in the transmission queue is low and at the same time
                 * the performance gain due to using sequence numbers is quite
                 * high.
                 *
                 * In case a harder condition with regard to duplicate sequence
                 * numbers in the transmission queue will be desired over time,
                 * the break_flag behavior used below should be
                 * removed/changed_accordingly.
                 */
                break_flag = B_FALSE;
                token_verified = B_FALSE;
                (void) pthread_mutex_lock(&transq_lock);
                cur_node = transq_hdr.head;
                while (cur_node != NULL && !break_flag) {
                        if (cur_node->seq_num != r_seq_num) {
                                cur_node = cur_node->next;
                                continue;
                        }

                        (void) pthread_mutex_lock(&gss_ctx_lock);
                        maj_stat = gss_verify_mic(&min_stat, gss_ctx,
                            &(cur_node->seq_token), &in_buf_mic,
                            &qop_state);
                        (void) pthread_mutex_unlock(&gss_ctx_lock);

                        if (!GSS_ERROR(maj_stat)) { /* the success case */
                                switch (maj_stat) {
                                /*
                                 * All the GSS_S_OLD_TOKEN, GSS_S_UNSEQ_TOKEN,
                                 * GSS_S_GAP_TOKEN are perceived as correct
                                 * behavior of the server side. The plugin
                                 * implementation is resistant to any of the
                                 * above mention cases of returned status codes.
                                 */
                                /*FALLTHRU*/
                                case GSS_S_OLD_TOKEN:
                                case GSS_S_UNSEQ_TOKEN:
                                case GSS_S_GAP_TOKEN:
                                case GSS_S_COMPLETE:
                                        /*
                                         * remove the verified record/node from
                                         * the transmission queue
                                         */
                                        transq_dequeue(cur_node);
                                        DPRINT((dfile, "Recv thread verified "
                                            "the token (transq len = %ld)\n",
                                            transq_hdr.count));

                                        token_verified = B_TRUE;
                                        break_flag = B_TRUE;
                                        break;

                                /*
                                 * Both the default case as well as
                                 * GSS_S_DUPLICATE_TOKEN case should never
                                 * occur. It's been left here for the sake of
                                 * completeness.
                                 * If any of the two cases occur, it is
                                 * subsequently cought because we don't set
                                 * the token_verified flag.
                                 */
                                /*FALLTHRU*/
                                case GSS_S_DUPLICATE_TOKEN:
                                default:
                                        break_flag = B_TRUE;
                                        break;
                                } /* switch (maj_stat) */

                        } else {        /* the failure case */
                                report_gss_err(
                                    gettext("signature verification of the "
                                    "received token failed"),
                                    maj_stat, min_stat);

                                switch (maj_stat) {
                                case GSS_S_CONTEXT_EXPIRED:
                                        /* retransmission necessary */
                                        recv_closure_rsn = RSN_GSS_CTX_EXP;
                                        break_flag = B_TRUE;
                                        DPRINT((dfile, "Recv thread detected "
                                            "the GSS context expiration\n"));
                                        break;
                                case GSS_S_BAD_SIG:
                                        DPRINT((dfile, "Bad signature "
                                            "detected (seq_num = %lld)\n",
                                            cur_node->seq_num));
                                        cur_node = cur_node->next;
                                        break;
                                default:
                                        report_gss_err(
                                            gettext("signature verification"),
                                            maj_stat, min_stat);
                                        break_flag = B_TRUE;
                                        break;
                                }
                        }

                } /* while */
                (void) pthread_mutex_unlock(&transq_lock);

                if (in_buf.value != NULL) {
                        free(in_buf.value);
                        in_buf.value = NULL;
                        in_buf.length = 0;
                }

                if (!token_verified) {
                        /*
                         * Received, but unverifiable token is perceived as
                         * the protocol flow corruption with the penalty of
                         * reinitializing the client/server connection.
                         */
                        DPRINT((dfile, "received unverifiable token\n"));
                        report_err(gettext("received unverifiable token\n"));
                        if (recv_closure_rsn == RSN_UNDEFINED) {
                                recv_closure_rsn = RSN_TOK_UNVERIFIABLE;
                        }
                        reset_transport(DO_CLOSE, DO_NOT_SYNC);
                }

        } /* for (;;) */


}


/*
 * init_poll() - initiates the polling in the receiving thread via sending the
 * appropriate message over the notify pipe. Message format = (int ||
 * booleant_t), where the first part (sizeof (int)) contains the
 * newly_opened/to_be_polled socket file descriptor. The contents of the second
 * part (sizeof (boolean_t)) of the message works only as a padding here and no
 * action (no recv/send thread synchronisation) is made in the receiving thread
 * based on its value.
 */
static boolean_t
init_poll(int fd)
{
        pipe_msg_t      np_data;
        int             pipe_in = notify_pipe[0];

        np_data.sock_num = fd;
        np_data.sync = B_FALSE; /* padding only */

        if (!write_fd(pipe_in, (char *)&np_data, sizeof (np_data))) {
                DPRINT((dfile, "Cannot write to the notify pipe\n"));
                report_err(gettext("writing to the notify pipe failed"));
                return (B_FALSE);
        }

        return (B_TRUE);
}


/*
 * reset_transport() - locked by the reset_lock initiates the reset of socket,
 * GSS security context and (possibly) flags the transq for retransmission; for
 * more detailed information see do_reset(). The reset_transport() also allows
 * the synchronization - waiting for the reset to be finished.
 *
 * do_close: DO_SYNC, DO_NOT_SYNC
 * sync_on_return: DO_EXIT (DO_NOT_CLOSE), DO_CLOSE (DO_NOT_EXIT)
 *
 */
void
reset_transport(boolean_t do_close, boolean_t sync_on_return)
{
        int             pipe_in = notify_pipe[0];
        pipe_msg_t      np_data;

        /*
         * Check if the reset routine is in progress or whether it was already
         * executed by some other thread.
         */
        (void) pthread_mutex_lock(&reset_lock);
        if (reset_in_progress) {
                (void) pthread_mutex_unlock(&reset_lock);
                return;
        }
        reset_in_progress = B_TRUE;

        np_data.sock_num = (do_close ? NP_CLOSE : NP_EXIT);
        np_data.sync = sync_on_return;
        (void) write_fd(pipe_in, (char *)&np_data, sizeof (np_data));

        if (sync_on_return) {
                while (reset_in_progress) {
                        (void) pthread_cond_wait(&reset_cv, &reset_lock);
                        DPRINT((dfile, "Wait for sync\n"));
                }
                DPRINT((dfile, "Synced\n"));
        }
        (void) pthread_mutex_unlock(&reset_lock);

}


/*
 * do_reset() - the own reseting routine called from the recv thread. If the
 * synchronization was requested, signal the finish via conditional variable.
 */
static void
do_reset(int *fds_cnt, struct pollfd *recv_fd, boolean_t do_signal)
{

        (void) pthread_mutex_lock(&reset_lock);

        /* socket */
        (void) pthread_mutex_lock(&sock_lock);
        if (sockfd == -1) {
                DPRINT((dfile, "socket already closed\n"));
                (void) pthread_mutex_unlock(&sock_lock);
                goto out;
        } else {
                (void) close(sockfd);
                sockfd = -1;
                recv_fd->fd = -1;
                (void) pthread_mutex_unlock(&sock_lock);
        }
        *fds_cnt = 1;

        /* context */
        if (gss_ctx_initialized) {
                delete_context();
        }
        gss_ctx_initialized = B_FALSE;
        gss_ctx = NULL;

        /* mark transq to be flushed */
        (void) pthread_mutex_lock(&transq_lock);
        if (transq_hdr.count > 0) {
                flush_transq = B_TRUE;
        }
        (void) pthread_mutex_unlock(&transq_lock);

out:
        reset_in_progress = B_FALSE;
        if (do_signal) {
                (void) pthread_cond_broadcast(&reset_cv);
        }

        (void) pthread_mutex_unlock(&reset_lock);
}

/*
 * do_cleanup() - removes all the preallocated space by the plugin; prepares the
 * plugin/application to be gracefully finished. Even thought the function
 * allows execution without signalling the successful finish, it's recommended
 * to use it (we usually want to wait for cleanup before exiting).
 */
static void
do_cleanup(int *fds_cnt, struct pollfd *recv_fd, boolean_t do_signal)
{

        (void) pthread_mutex_lock(&reset_lock);

        /*
         * socket
         * note: keeping locking for safety, thought it shouldn't be necessary
         * in current implementation - we get here only in case the sending code
         * path calls auditd_plugin_close() (thus no socket manipulation) and
         * the recv thread is doing the own socket closure.
         */
        (void) pthread_mutex_lock(&sock_lock);
        if (sockfd != -1) {
                DPRINT((dfile, "Closing socket: %d\n", sockfd));
                (void) close(sockfd);
                sockfd = -1;
                recv_fd->fd = -1;
        }
        *fds_cnt = 1;
        (void) pthread_mutex_unlock(&sock_lock);

        /* context */
        if (gss_ctx_initialized) {
                DPRINT((dfile, "Deleting context: "));
                delete_context();
        }
        gss_ctx_initialized = B_FALSE;
        gss_ctx = NULL;

        /* transmission queue */
        (void) pthread_mutex_lock(&transq_lock);
        if (transq_hdr.count > 0) {
                DPRINT((dfile, "Deallocating the transmission queue "
                    "(len = %ld)\n", transq_hdr.count));
                while (transq_hdr.count > 0) {
                        transq_dequeue(transq_hdr.head);
                }
        }
        (void) pthread_mutex_unlock(&transq_lock);

        /* notification pipe */
        if (notify_pipe_ready) {
                (void) close(notify_pipe[0]);
                (void) close(notify_pipe[1]);
                notify_pipe_ready = B_FALSE;
        }

        reset_in_progress = B_FALSE;
        if (do_signal) {
                (void) pthread_cond_broadcast(&reset_cv);
        }
        (void) pthread_mutex_unlock(&reset_lock);
}


/*
 * transq_dequeue() - dequeues given node pointed by the node_ptr from the
 * transmission queue. Transmission queue should be locked prior to use of this
 * function.
 */
static void
transq_dequeue(transq_node_t *node_ptr)
{

        if (node_ptr == NULL) {
                DPRINT((dfile, "transq_dequeue(): called with NULL pointer\n"));
                return;
        }

        free(node_ptr->seq_token.value);

        if (node_ptr->prev != NULL) {
                node_ptr->prev->next = node_ptr->next;
        }
        if (node_ptr->next != NULL) {
                node_ptr->next->prev = node_ptr->prev;
        }


        /* update the transq_hdr */
        if (node_ptr->next == NULL) {
                transq_hdr.end = node_ptr->prev;
        }
        if (node_ptr->prev == NULL) {
                transq_hdr.head = node_ptr->next;
        }

        transq_hdr.count--;

        free(node_ptr);
}


/*
 * transq_enqueue() - creates new node in (at the end of) the transmission
 * queue. in_ptoken_ptr is a pointer to the plain token in a form of
 * gss_buffer_desc. Function returns 0 on success and updates the *node_ptr to
 * point to a newly added transmission queue node. In case of any failure
 * function returns 1 and sets the *node_ptr to NULL.
 * Transmission queue should be locked prior to use of this function.
 */
static boolean_t
transq_enqueue(transq_node_t **node_ptr, gss_buffer_t in_seqtoken_ptr,
    uint64_t sequence)
{

        *node_ptr = calloc(1, sizeof (transq_node_t));
        if (*node_ptr == NULL) {
                report_err(gettext("Memory allocation failed"));
                DPRINT((dfile, "Memory allocation failed: %s\n",
                    strerror(errno)));
                goto errout;
        }

        /* value of the seq_token.value = (sequence number || plain token) */
        (*node_ptr)->seq_num = sequence;
        (*node_ptr)->seq_token.length = in_seqtoken_ptr->length;
        (*node_ptr)->seq_token.value = in_seqtoken_ptr->value;

        /* update the transq_hdr */
        if (transq_hdr.head == NULL) {
                transq_hdr.head = *node_ptr;
        }
        if (transq_hdr.end != NULL) {
                (transq_hdr.end)->next = *node_ptr;
                (*node_ptr)->prev = transq_hdr.end;
        }
        transq_hdr.end = *node_ptr;

        transq_hdr.count++;

        return (B_TRUE);

errout:
        if (*node_ptr != NULL) {
                if ((*node_ptr)->seq_token.value != NULL) {
                        free((*node_ptr)->seq_token.value);
                }
                free(*node_ptr);
                *node_ptr = NULL;
        }
        return (B_FALSE);
}


/*
 * transq_retransmit() - traverse the transmission queue and try to, 1 by 1,
 * re-wrap the tokens with the recent context information and retransmit the
 * tokens from the transmission queue.
 * Function returns 2 on GSS context expiration, 1 on any other error, 0 on
 * successfully resent transmission queue.
 */
static int
transq_retransmit()
{

        OM_uint32       maj_stat, min_stat;
        transq_node_t   *cur_node = transq_hdr.head;
        gss_buffer_desc out_buf;
        int             conf_state;

        DPRINT((dfile, "Retransmission of the remainder in the transqueue\n"));

        while (cur_node != NULL) {

                (void) pthread_mutex_lock(&transq_lock);
                (void) pthread_mutex_lock(&gss_ctx_lock);
                maj_stat = gss_wrap(&min_stat, gss_ctx, 1, GSS_C_QOP_DEFAULT,
                    &(cur_node->seq_token), &conf_state, &out_buf);
                (void) pthread_mutex_unlock(&gss_ctx_lock);

                switch (maj_stat) {
                case GSS_S_COMPLETE:
                        break;
                case GSS_S_CONTEXT_EXPIRED:
                        DPRINT((dfile, "Context expired.\n"));
                        report_gss_err(gettext("gss_wrap message"), maj_stat,
                            min_stat);
                        (void) pthread_mutex_unlock(&transq_lock);
                        return (2);
                default:
                        report_gss_err(gettext("gss_wrap message"), maj_stat,
                            min_stat);
                        (void) pthread_mutex_unlock(&transq_lock);
                        return (1);
                }

                DPRINT((dfile, "Sending transmission queue token (seq=%lld, "
                    "size=%d, transq len=%ld)\n", cur_node->seq_num,
                    out_buf.length, transq_hdr.count));
                if (send_token(&sockfd, &out_buf) < 0) {
                        (void) gss_release_buffer(&min_stat, &out_buf);
                        (void) pthread_mutex_unlock(&transq_lock);
                        return (1);
                }
                (void) gss_release_buffer(&min_stat, &out_buf);

                cur_node = cur_node->next;
                (void) pthread_mutex_unlock(&transq_lock);

        } /* while */

        return (0);
}