transport.c revision c7bef3b16d3d2a0b09ff75fbbd724283ef1ee7e7
0N/A/*
1879N/A * CDDL HEADER START
0N/A *
0N/A * The contents of this file are subject to the terms of the
0N/A * Common Development and Distribution License (the "License").
0N/A * You may not use this file except in compliance with the License.
0N/A *
0N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
0N/A * or http://www.opensolaris.org/os/licensing.
0N/A * See the License for the specific language governing permissions
0N/A * and limitations under the License.
0N/A *
0N/A * When distributing Covered Code, include this CDDL HEADER in each
0N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
0N/A * If applicable, add the following below this CDDL HEADER, with the
0N/A * fields enclosed by brackets "[]" replaced with your own identifying
0N/A * information: Portions Copyright [yyyy] [name of copyright owner]
0N/A *
1472N/A * CDDL HEADER END
1472N/A */
1472N/A/*
0N/A * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
0N/A * Use is subject to license terms.
0N/A *
1879N/A * transport layer for audit_remote (handles connection establishment, gss
1879N/A * context initialization, message encryption and verification)
1879N/A *
1879N/A */
1879N/A
1879N/A#include <assert.h>
1879N/A#include <audit_plugin.h>
1879N/A#include <errno.h>
0N/A#include <fcntl.h>
0N/A#include <gssapi/gssapi.h>
0N/A#include <libintl.h>
0N/A#include <mtmalloc.h>
0N/A#include <netdb.h>
0N/A#include <netinet/in.h>
0N/A#include <netinet/tcp.h>
0N/A#include <stdio.h>
0N/A#include <stdlib.h>
0N/A#include <string.h>
0N/A#include <strings.h>
0N/A#include <syslog.h>
0N/A#include <sys/types.h>
0N/A#include <sys/socket.h>
0N/A#include <unistd.h>
0N/A#include <poll.h>
0N/A#include <pthread.h>
0N/A
0N/A#include "audit_remote.h"
0N/A
0N/A
0N/Astatic int sockfd = -1;
0N/Astatic struct hostent *current_host;
0N/Astatic gss_OID *current_mech_oid;
0N/Astatic in_port_t current_port;
0N/Astatic boolean_t flush_transq;
0N/A
0N/Astatic char *ver_str = "01"; /* supported protocol version */
0N/Astatic char *ver_str_concat; /* concat serv/client version */
0N/A
0N/Astatic gss_ctx_id_t gss_ctx;
0N/Astatic boolean_t gss_ctx_initialized;
0N/A
0N/Apthread_t recv_tid; /* receiving thread */
0N/Astatic pthread_once_t recv_once_control = PTHREAD_ONCE_INIT;
0N/A
0N/Aextern int timeout; /* connection timeout */
0N/A
0N/Aextern pthread_mutex_t plugin_mutex;
0N/Atransq_hdr_t transq_hdr;
0N/A
0N/A/*
0N/A * The three locks synchronize the simultaneous actions on top of transmission
0N/A * queue, socket, gss_context.
0N/A */
0N/Apthread_mutex_t transq_lock = PTHREAD_MUTEX_INITIALIZER;
0N/Apthread_mutex_t sock_lock = PTHREAD_MUTEX_INITIALIZER;
0N/Apthread_mutex_t gss_ctx_lock = PTHREAD_MUTEX_INITIALIZER;
0N/A
0N/A/* reset routine synchronization - required by the sending thread */
0N/Apthread_mutex_t reset_lock = PTHREAD_MUTEX_INITIALIZER;
0N/Astatic boolean_t reset_in_progress; /* reset routine in progress */
0N/A
0N/A#define NP_CLOSE -1 /* notification pipe - close message */
0N/A#define NP_EXIT -2 /* notification pipe - exit message */
0N/Aboolean_t notify_pipe_ready;
0N/Aint notify_pipe[2]; /* notif. pipe - receiving thread */
0N/A
0N/Apthread_cond_t reset_cv = PTHREAD_COND_INITIALIZER;
0N/Astatic close_rsn_t recv_closure_rsn;
0N/A
0N/A#define MAX_TOK_LEN (128 * 1000) /* max token length we accept (B) */
0N/A
0N/A/* transmission queue helpers */
0N/Astatic void transq_dequeue(transq_node_t *);
0N/Astatic boolean_t transq_enqueue(transq_node_t **, gss_buffer_t,
0N/A uint64_t);
0N/Astatic int transq_retransmit(void);
0N/A
0N/Astatic boolean_t init_poll(int);
0N/Astatic void do_reset(int *, struct pollfd *, boolean_t);
0N/Astatic void do_cleanup(int *, struct pollfd *, boolean_t);
0N/A
0N/Astatic void init_recv_record(void);
0N/Astatic void recv_record();
0N/Astatic int connect_timeout(int, struct sockaddr *, int);
0N/Astatic int send_timeout(int, const char *, size_t);
0N/Astatic int recv_timeout(int, char *, size_t);
0N/Astatic int send_token(int *, gss_buffer_t);
0N/Astatic int recv_token(int, gss_buffer_t);
0N/A
0N/A
0N/A/*
0N/A * report_err() - wrapper, mainly due to enhance the code readability - report
0N/A * error to syslog via call to __audit_syslog().
0N/A */
0N/Astatic void
0N/Areport_err(char *msg)
64N/A{
64N/A __audit_syslog("audit_remote.so", LOG_CONS | LOG_NDELAY, LOG_DAEMON,
64N/A LOG_ERR, msg);
64N/A
0N/A}
0N/A
0N/A
0N/A/*
0N/A * report_gss_err() - GSS API error reporting
0N/A */
0N/Astatic void
0N/Areport_gss_err(char *msg, OM_uint32 maj_stat, OM_uint32 min_stat)
0N/A{
0N/A gss_buffer_desc msg_buf;
0N/A OM_uint32 _min, msg_ctx;
0N/A char *err_msg;
0N/A
0N/A /* major stat */
64N/A msg_ctx = 0;
223N/A do {
64N/A (void) gss_display_status(&_min, maj_stat, GSS_C_GSS_CODE,
64N/A *current_mech_oid, &msg_ctx, &msg_buf);
64N/A (void) asprintf(&err_msg,
64N/A gettext("GSS API error - %s(%u): %.*s\n"), msg, maj_stat,
64N/A msg_buf.length, (char *)msg_buf.value);
64N/A if (err_msg != NULL) {
64N/A report_err(err_msg);
64N/A free(err_msg);
0N/A }
0N/A (void) gss_release_buffer(&_min, &msg_buf);
0N/A } while (msg_ctx);
0N/A
0N/A /* minor stat */
0N/A msg_ctx = 0;
0N/A do {
0N/A (void) gss_display_status(&_min, min_stat, GSS_C_MECH_CODE,
0N/A *current_mech_oid, &msg_ctx, &msg_buf);
0N/A (void) asprintf(&err_msg,
74N/A gettext("GSS mech error - %s(%u): %.*s\n"), msg, min_stat,
0N/A msg_buf.length, (char *)msg_buf.value);
0N/A if (err_msg != NULL) {
0N/A report_err(err_msg);
0N/A free(err_msg);
0N/A }
0N/A (void) gss_release_buffer(&_min, &msg_buf);
0N/A } while (msg_ctx);
0N/A}
0N/A
0N/A/*
0N/A * prot_ver_negotiate() - negotiate/acknowledge the protocol version. Currently,
0N/A * there is only one version supported by the plugin - "01".
0N/A * Note: connection must be initiated prior version negotiation
400N/A */
400N/Astatic int
64N/Aprot_ver_negotiate()
64N/A{
64N/A gss_buffer_desc out_buf, in_buf;
64N/A size_t ver_str_concat_sz;
0N/A
0N/A /*
0N/A * Set the version proposal string - once we support more than
0N/A * version "01" this part should be extended to solve the concatenation
0N/A * of supported version identifiers.
0N/A */
0N/A out_buf.value = (void *)ver_str;
0N/A out_buf.length = strlen((char *)out_buf.value);
0N/A DPRINT((dfile, "Protocol version proposal (size=%d): %.*s\n",
64N/A out_buf.length, out_buf.length, (char *)out_buf.value));
64N/A
64N/A if (send_token(&sockfd, &out_buf) < 0) {
64N/A DPRINT((dfile, "Sending protocol version token failed\n"));
64N/A return (-1);
64N/A }
64N/A
64N/A if (recv_token(sockfd, &in_buf) < 0) {
64N/A DPRINT((dfile, "Receiving protocol version token failed\n"));
64N/A return (-1);
64N/A }
64N/A
0N/A /*
0N/A * Verify the sent/received string - memcmp() is sufficient here
0N/A * because we support only one version and it is represented by
0N/A * the "01" string. The received version has to be "01" string as well.
0N/A */
0N/A if (out_buf.length != in_buf.length ||
0N/A memcmp(out_buf.value, in_buf.value, out_buf.length) != 0) {
0N/A DPRINT((dfile, "Verification of the protocol version strings "
0N/A "failed [%d:%s][%d:%s]\n", out_buf.length,
0N/A (char *)out_buf.value, in_buf.length,
0N/A (char *)in_buf.value));
0N/A free(in_buf.value);
0N/A return (-1);
0N/A }
0N/A
0N/A /*
0N/A * Prepare the concatenated client/server version strings later used
0N/A * as an application_data field in the gss_channel_bindings_struct
0N/A * structure.
0N/A */
0N/A ver_str_concat_sz = out_buf.length + in_buf.length + 1;
0N/A ver_str_concat = (char *)calloc(1, ver_str_concat_sz);
0N/A if (ver_str_concat == NULL) {
0N/A report_err(gettext("Memory allocation failed"));
0N/A DPRINT((dfile, "Memory allocation failed: %s\n",
0N/A strerror(errno)));
0N/A free(in_buf.value);
0N/A return (-1);
0N/A }
0N/A (void) memcpy(ver_str_concat, out_buf.value, out_buf.length);
0N/A (void) memcpy(ver_str_concat + out_buf.length, in_buf.value,
0N/A in_buf.length);
0N/A DPRINT((dfile, "Concatenated version strings: %s\n", ver_str_concat));
0N/A
0N/A DPRINT((dfile, "Protocol version agreed.\n"));
0N/A free(in_buf.value);
0N/A return (0);
0N/A}
0N/A
0N/A/*
0N/A * sock_prepare() - creates and connects socket. Function returns
0N/A * B_FALSE/B_TRUE on failure/success and sets the err_rsn accordingly to the
0N/A * reason of failure.
0N/A */
0N/Astatic boolean_t
0N/Asock_prepare(int *sockfdptr, struct hostent *host, close_rsn_t *err_rsn)
0N/A{
0N/A struct sockaddr_storage addr;
0N/A struct sockaddr_in *sin;
0N/A struct sockaddr_in6 *sin6;
0N/A size_t addr_len;
0N/A int sock;
0N/A
0N/A DPRINT((dfile, "Creating socket for %s\n", host->h_name));
0N/A bzero(&addr, sizeof (addr));
0N/A addr.ss_family = host->h_addrtype;
0N/A switch (host->h_addrtype) {
127N/A case AF_INET:
127N/A sin = (struct sockaddr_in *)&addr;
0N/A addr_len = sizeof (struct sockaddr_in);
0N/A bcopy(host->h_addr_list[0],
0N/A &(sin->sin_addr), sizeof (struct in_addr));
0N/A sin->sin_port = current_port;
0N/A break;
0N/A case AF_INET6:
0N/A sin6 = (struct sockaddr_in6 *)&addr;
0N/A addr_len = sizeof (struct sockaddr_in6);
0N/A bcopy(host->h_addr_list[0],
0N/A &(sin6->sin6_addr), sizeof (struct in6_addr));
0N/A sin6->sin6_port = current_port;
0N/A break;
0N/A default:
0N/A /* unknown address family */
0N/A *err_rsn = RSN_UNKNOWN_AF;
0N/A return (B_FALSE);
0N/A }
0N/A if ((sock = socket(addr.ss_family, SOCK_STREAM, 0)) == -1) {
0N/A *err_rsn = RSN_SOCKET_CREATE;
0N/A return (B_FALSE);
0N/A }
0N/A DPRINT((dfile, "Socket created, fd=%d, connecting..\n", sock));
0N/A
0N/A if (connect_timeout(sock, (struct sockaddr *)&addr, addr_len)) {
0N/A (void) close(sock);
0N/A *err_rsn = RSN_CONNECTION_CREATE;
0N/A return (B_FALSE);
0N/A }
0N/A *sockfdptr = sock;
0N/A DPRINT((dfile, "Connected to %s via fd=%d\n", host->h_name,
0N/A *sockfdptr));
0N/A
0N/A return (B_TRUE);
0N/A}
0N/A
0N/A/*
0N/A * establish_context() - establish the client/server GSS context.
0N/A *
0N/A * Note: connection must be established and version negotiated (in plain text)
0N/A * prior to establishing context.
0N/A */
0N/Astatic int
0N/Aestablish_context()
0N/A{
0N/A gss_buffer_desc send_tok, recv_tok, *token_ptr;
0N/A OM_uint32 maj_stat, min_stat;
0N/A OM_uint32 init_sec_min_stat, ret_flags;
0N/A gss_name_t gss_name;
0N/A char *gss_svc_name = "audit";
0N/A char *svc_name;
0N/A struct gss_channel_bindings_struct input_chan_bindings;
0N/A
0N/A /* GSS service name = gss_svc_name + "@" + remote hostname (fqdn) */
0N/A (void) asprintf(&svc_name, "%s@%s", gss_svc_name, current_host->h_name);
0N/A if (svc_name == NULL) {
0N/A report_err(gettext("Cannot allocate service name\n"));
0N/A DPRINT((dfile, "Memory allocation failed: %s\n",
0N/A strerror(errno)));
0N/A return (-1);
0N/A }
0N/A DPRINT((dfile, "Service name: %s\n", svc_name));
0N/A
0N/A send_tok.value = svc_name;
0N/A send_tok.length = strlen(svc_name);
0N/A maj_stat = gss_import_name(&min_stat, &send_tok,
0N/A (gss_OID)GSS_C_NT_HOSTBASED_SERVICE, &gss_name);
0N/A if (maj_stat != GSS_S_COMPLETE) {
0N/A report_gss_err(gettext("initializing context"), maj_stat,
0N/A min_stat);
0N/A free(svc_name);
0N/A return (-1);
0N/A }
0N/A token_ptr = GSS_C_NO_BUFFER;
0N/A gss_ctx = GSS_C_NO_CONTEXT;
0N/A
0N/A /* initialize channel binding */
0N/A bzero(&input_chan_bindings, sizeof (input_chan_bindings));
0N/A input_chan_bindings.initiator_addrtype = GSS_C_AF_NULLADDR;
0N/A input_chan_bindings.acceptor_addrtype = GSS_C_AF_NULLADDR;
0N/A input_chan_bindings.application_data.length = strlen(ver_str_concat);
0N/A input_chan_bindings.application_data.value = ver_str_concat;
0N/A
0N/A (void) pthread_mutex_lock(&gss_ctx_lock);
0N/A do {
0N/A maj_stat = gss_init_sec_context(&init_sec_min_stat,
0N/A GSS_C_NO_CREDENTIAL, &gss_ctx, gss_name, *current_mech_oid,
0N/A GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG
0N/A | GSS_C_CONF_FLAG, 0, &input_chan_bindings, token_ptr,
0N/A NULL, &send_tok, &ret_flags, NULL);
127N/A
0N/A if (token_ptr != GSS_C_NO_BUFFER) {
0N/A (void) gss_release_buffer(&min_stat, &recv_tok);
0N/A }
17N/A
0N/A if (send_tok.length != 0) {
0N/A DPRINT((dfile,
17N/A "Sending init_sec_context token (size=%d)\n",
17N/A send_tok.length));
17N/A if (send_token(&sockfd, &send_tok) < 0) {
17N/A free(svc_name);
17N/A (void) gss_release_name(&min_stat, &gss_name);
0N/A (void) pthread_mutex_unlock(&gss_ctx_lock);
0N/A return (-1);
0N/A }
0N/A }
0N/A if (send_tok.value != NULL) {
0N/A free(send_tok.value); /* freeing svc_name */
0N/A send_tok.value = NULL;
0N/A send_tok.length = 0;
0N/A }
0N/A
0N/A if (maj_stat != GSS_S_COMPLETE &&
0N/A maj_stat != GSS_S_CONTINUE_NEEDED) {
0N/A report_gss_err(gettext("initializing context"),
0N/A maj_stat, init_sec_min_stat);
0N/A if (gss_ctx == GSS_C_NO_CONTEXT) {
0N/A (void) gss_delete_sec_context(&min_stat,
0N/A &gss_ctx, GSS_C_NO_BUFFER);
0N/A }
0N/A (void) gss_release_name(&min_stat, &gss_name);
0N/A (void) pthread_mutex_unlock(&gss_ctx_lock);
0N/A return (-1);
0N/A }
0N/A
0N/A if (maj_stat == GSS_S_CONTINUE_NEEDED) {
0N/A DPRINT((dfile, "continue needed... "));
0N/A if (recv_token(sockfd, &recv_tok) < 0) {
0N/A (void) gss_release_name(&min_stat, &gss_name);
0N/A (void) pthread_mutex_unlock(&gss_ctx_lock);
0N/A return (-1);
0N/A }
0N/A token_ptr = &recv_tok;
0N/A }
0N/A } while (maj_stat == GSS_S_CONTINUE_NEEDED);
0N/A (void) gss_release_name(&min_stat, &gss_name);
0N/A
0N/A DPRINT((dfile, "context established\n"));
0N/A (void) pthread_mutex_unlock(&gss_ctx_lock);
0N/A return (0);
0N/A}
0N/A
0N/A/*
0N/A * delete_context() - release GSS context.
0N/A */
0N/Astatic void
0N/Adelete_context()
0N/A{
0N/A OM_uint32 min_stat;
127N/A
0N/A (void) gss_delete_sec_context(&min_stat, &gss_ctx, GSS_C_NO_BUFFER);
0N/A DPRINT((dfile, "context deleted\n"));
0N/A}
0N/A
0N/A/*
0N/A * send_token() - send GSS token over the wire.
0N/A */
0N/Astatic int
0N/Asend_token(int *fdptr, gss_buffer_t tok)
0N/A{
0N/A uint32_t len;
0N/A uint32_t lensz;
0N/A char *out_buf;
0N/A int fd;
0N/A
0N/A (void) pthread_mutex_lock(&sock_lock);
0N/A if (*fdptr == -1) {
0N/A (void) pthread_mutex_unlock(&sock_lock);
0N/A DPRINT((dfile, "Socket detected as closed.\n"));
0N/A return (-1);
0N/A }
0N/A fd = *fdptr;
0N/A
0N/A len = htonl(tok->length);
0N/A lensz = sizeof (len);
0N/A
0N/A out_buf = (char *)malloc((size_t)(lensz + tok->length));
0N/A if (out_buf == NULL) {
0N/A (void) pthread_mutex_unlock(&sock_lock);
0N/A report_err(gettext("Memory allocation failed"));
0N/A DPRINT((dfile, "Memory allocation failed: %s\n",
0N/A strerror(errno)));
0N/A return (-1);
0N/A }
0N/A (void) memcpy((void *)out_buf, (void *)&len, lensz);
0N/A (void) memcpy((void *)(out_buf + lensz), (void *)tok->value,
0N/A tok->length);
0N/A
0N/A if (send_timeout(fd, out_buf, (lensz + tok->length))) {
0N/A (void) pthread_mutex_unlock(&sock_lock);
0N/A free(out_buf);
0N/A return (-1);
0N/A }
0N/A
0N/A (void) pthread_mutex_unlock(&sock_lock);
0N/A free(out_buf);
0N/A return (0);
0N/A}
0N/A
0N/A
0N/A/*
0N/A * recv_token() - receive GSS token over the wire.
0N/A */
0N/Astatic int
0N/Arecv_token(int fd, gss_buffer_t tok)
0N/A{
0N/A uint32_t len;
0N/A
0N/A if (recv_timeout(fd, (char *)&len, sizeof (len))) {
0N/A return (-1);
0N/A }
0N/A len = ntohl(len);
0N/A
0N/A /* simple DOS prevention mechanism */
0N/A if (len > MAX_TOK_LEN) {
0N/A report_err(gettext("Indicated invalid token length"));
0N/A DPRINT((dfile, "Indicated token length > %dB\n", MAX_TOK_LEN));
0N/A return (-1);
0N/A }
0N/A
0N/A tok->value = (char *)malloc(len);
0N/A if (tok->value == NULL) {
0N/A report_err(gettext("Memory allocation failed"));
0N/A DPRINT((dfile, "Memory allocation failed: %s\n",
0N/A strerror(errno)));
0N/A tok->length = 0;
0N/A return (-1);
0N/A }
0N/A
0N/A if (recv_timeout(fd, tok->value, len)) {
0N/A free(tok->value);
0N/A tok->value = NULL;
0N/A tok->length = 0;
0N/A return (-1);
0N/A }
0N/A
0N/A tok->length = len;
0N/A return (0);
0N/A}
0N/A
0N/A
0N/A/*
0N/A * I/O functions
0N/A */
0N/A
0N/A/*
0N/A * connect_timeout() - sets nonblocking I/O on a socket and timeout-connects
0N/A */
0N/Astatic int
0N/Aconnect_timeout(int sockfd, struct sockaddr *name, int namelen)
0N/A{
0N/A int flags;
0N/A struct pollfd fds;
0N/A int rc;
0N/A struct sockaddr_storage addr;
0N/A socklen_t addr_len = sizeof (addr);
0N/A
0N/A
0N/A flags = fcntl(sockfd, F_GETFL, 0);
0N/A if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1) {
0N/A return (-1);
0N/A }
0N/A if (connect(sockfd, name, namelen)) {
127N/A if (!(errno == EINTR || errno == EINPROGRESS ||
127N/A errno == EWOULDBLOCK)) {
127N/A return (-1);
0N/A }
0N/A }
0N/A fds.fd = sockfd;
0N/A fds.events = POLLOUT;
0N/A for (;;) {
0N/A fds.revents = 0;
1879N/A rc = poll(&fds, 1, timeout * 1000);
1879N/A 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, (void *(*)(void *))recv_record,
(void *)NULL);
}
/*
* recv_record() - the receiver thread routine
*/
static void
recv_record()
{
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);
}