/*
 * lib/krb5/pcache.c
 *
 * An implementation of the "pipe" credential cache
 *
 * The "pipe" credential cache uses a shared descriptor to communicate
 * with a credential cache server.  The details of this follow.
 *
 * To set up the pipe credential cache, krb5_cc_resolve() is called with
 * a type of "PIPE:NewCache".  This creates a pair of descriptors (using
 * socketpair()), and the credential cache server is forked at that point.
 * Communication between the Kerberos client applications and the credential
 * cache server is over the pair of descriptors created by socketpair()
 * (which are now inherited to all child processes).
 *
 * To help prevent chld processes from closing this descriptor, it
 * is dup2()'d to the maximum descriptor number allowed indicated by
 * getrlimit(), and then setrlimit() is called to lower the maximum number
 * of descriptors below this number.  E.g, if the maximum descriptor number
 * permitted is 255, the communication descriptor is dup2()'d to 255 and
 * setrlimit() is called to limit the maximum number of descriptors to 254.
 *
 * Decentants of the process that initializes the credential cache should
 * set KRB5CCNAME to "PIPE:nn" where "nn" is the value of the shared
 * descriptor.  When Kerberos client programs call krb5_cc_resolve()
 * (generally from krb5_cc_default()), they will communicate with the
 * credential cache server via the shared descriptor.  The CCserver will
 * create a new pair of sockets and send the descriptor to the client
 * via the Unix descriptor passing mechanism.  The client program will
 * then use this connection to communicate with the credential cache
 * server (the master connection cannot be used for this; since it is
 * shared by all processes, it's impossible to distinguish between replies
 * to multiple requests).
 *
 * 2008-05-22
 *
 * This file has been changed significantly to build with Heimdal.
 *
 * To build, place this file in lib/krb5/pcache.c and add:
 *
 *   krb5_cc_register(p, &krb5_pcc_ops, TRUE);
 *
 * to lib/krb5/context.c:~252 and run configure like:
 *
 *   ./configure CPPFLAGS="-DENABLE_PIPE_CCACHE -DHAVE_STRUCT_MSGHDR_MSG_CONTROL"
 *
 * but substitute macros accordingly (e.g. I think BSD would need
 * HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS instead?).
 *
 */

#ifdef ENABLE_PIPE_CCACHE

#include "krb5_locl.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <strings.h>
#include <stdarg.h>
#include <signal.h>
#include <memory.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/resource.h>

extern const krb5_cc_ops krb5_pcc_ops;

#define PCC_OP_INITIALIZE	0
#define PCC_OP_DESTROY		1
#define PCC_OP_STORE		2
#define PCC_OP_GET_PRINCIPAL	3
#define PCC_OP_START_SEQ	4
#define PCC_OP_NEXT_CRED	5
#define PCC_OP_END_SEQ		6

#define PCC_NEW_DESCRIPTOR '\254'
#define PCC_KILL_SERVER '\253'

#define PCC_SUCCESS	'\000'
#define PCC_FAILURE	'\001'

#define PCC_TIMEOUT	10

#define PCC_INTERNAL_MEMCACHE "MEMORY:CCServer Internal Cache"

typedef struct _krb5_pcc_data {
	char *name;
	int fd_master;
	int fd_comm;
} krb5_pcc_data;

typedef struct _krb5_pcc_connection {
	int fd_comm;
	krb5_cc_cursor **carray;
	int max_cursor;
	struct _krb5_pcc_connection *next;
} krb5_pcc_connection;

static krb5_pcc_connection *pcc_conn_head;
static int pcc_maxfd;
static int pcc_master_fd;
static fd_set readfds;

#define PCACHE(X) ((krb5_pcc_data *)(X)->data.data)

/*
 * Internal credential cache free function
 */

static void
krb5_pcc_free(krb5_context context, krb5_ccache id)
{

	if (PCACHE(id)) {
		if (PCACHE(id)->name)
			free(PCACHE(id)->name);
		if (PCACHE(id)->fd_master)
			close(PCACHE(id)->fd_master);
		if (PCACHE(id)->fd_comm)
			close(PCACHE(id)->fd_comm);
		krb5_data_free(&id->data);
	}
}

/*
 * Connection state free function
 */

static void
krb5_pcc_conn_free(krb5_context context, krb5_ccache ccache,
		   krb5_pcc_connection *conn)
{
	int i;

	if (conn->carray) {
		for (i = 0; i < conn->max_cursor; i++) {
			if (conn->carray[i]) {
				krb5_cc_end_seq_get(context, ccache,
						    conn->carray[i]);
				free(conn->carray[i]);
			}
		}
		free(conn->carray);
	}

	free(conn);
}

/*
 * Allocate a new pair of communication sockets and send it to the
 * requesting process
 */

static void
krb5_pcc_send_new_fd(krb5_context context, int reply_fd)
{
	int fds[2], cc;
	struct msghdr msg;
	struct iovec iov[1];
	static char success = PCC_SUCCESS, failure = PCC_FAILURE;
	krb5_pcc_connection *newconn;

#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL) && !defined(HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS)
	union {
		struct cmsghdr cm;
		char control[CMSG_SPACE(sizeof(int))];
	} control_un;
	struct cmsghdr *cmptr;
#endif /* HAVE_STRUCT_MSGHDR_MSG_CONTROL && !HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS */

	if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) {
		com_err("CCserver", errno, "while allocating communication "
			"socket pair");
		write(reply_fd, &failure, sizeof(failure));
		return;
	}

#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL) && !defined(HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS)
	msg.msg_control = control_un.control;
	msg.msg_controllen = sizeof(control_un.control);

	cmptr = CMSG_FIRSTHDR(&msg);
	cmptr->cmsg_len = CMSG_LEN(sizeof(int));
	cmptr->cmsg_level = SOL_SOCKET;
	cmptr->cmsg_type = SCM_RIGHTS;
	*((int *) CMSG_DATA(cmptr)) = fds[0];
#else /* HAVE_STRUCT_MSGHDR_MSG_CONTROL && !HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS */
	msg.msg_accrights = (caddr_t) &fds[0];
	msg.msg_accrightslen = sizeof(int);
#endif /* HAVE_STRUCT_MSGHDR_MSG_CONTROL  && !HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS */

	msg.msg_name = NULL;
	msg.msg_namelen = 0;

	iov[0].iov_base = &success;
	iov[0].iov_len = sizeof(success);
	msg.msg_iov = iov;
	msg.msg_iovlen = 1;

	cc = sendmsg(reply_fd, &msg, 0);

	if (cc < 0) {
		com_err("CCserver", errno, "while sending file descriptor");
		close(fds[0]);
		close(fds[1]);
		return;
	}

	close(fds[0]);

	newconn = (krb5_pcc_connection *) malloc(sizeof(krb5_pcc_connection));

	if (! newconn) {
		close(fds[1]);
		com_err("CCserver", ENOMEM, "while allocating the connection "
			"handle");
		return;
	}

	memset((void *) newconn, 0, sizeof(krb5_pcc_connection));

	newconn->fd_comm = fds[1];
	newconn->next = pcc_conn_head; 
	pcc_conn_head = newconn;

	if (newconn->fd_comm > pcc_maxfd)
		pcc_maxfd = newconn->fd_comm;
	FD_SET(newconn->fd_comm, &readfds);
}

/*
 * The function which writes data to the credential cache client or server.
 */

static krb5_error_code
krb5_pcc_write_cccomm(krb5_context context, int fd, int32_t opcode,
		      int clientflag, int numargs, ...)
{
	va_list ap;
	struct iovec *iov;
	int cc, totallen, i;
	uint32_t *lenarray = NULL;
	int32_t response;
	krb5_error_code retval;
#ifdef POSIX_SIGNALS
	struct sigaction osigint, sa;
#else /* POSIX_SIGNALS */
	RETSIGTYPE (*osigint)();
#endif /* POSIX_SIGNALS */

	/*
	 * Assemble an iov structure so we can write out the request
	 * in one operation
	 */

	iov = (struct iovec *) malloc(sizeof(struct iovec) *
				      (numargs * 2 + 1));
	if (!iov)
		return ENOMEM;

	if (numargs) {
		lenarray = (uint32_t *) malloc(sizeof(uint32_t) * numargs);
		if (! lenarray) {
			free(iov);
			return ENOMEM;
		}
	}

	iov[0].iov_base = (caddr_t) &opcode;
	totallen = iov[0].iov_len = sizeof(opcode);

	va_start(ap, numargs);

	for (i = 0; i < numargs; i++) {
		iov[i*2 + 2].iov_base = (caddr_t) va_arg(ap, void *);
		lenarray[i] = va_arg(ap, int);
		iov[i*2 + 2].iov_len = lenarray[i];
		iov[i*2 + 1].iov_base = (caddr_t) &lenarray[i];
		iov[i*2 + 1].iov_len = sizeof(uint32_t);
		totallen += lenarray[i] + sizeof(uint32_t);
	}

	va_end(ap);

	/*
	 * Make sure we block SIGPIPE ... _if_ we're doing the write from
	 * the client (the server already blocks SIGPIPE for everything)
	 */

	if (clientflag) {
#ifdef POSIX_SIGNALS
		sigemptyset(&sa.sa_mask);
		sa.sa_flags = 0;
		sa.sa_handler = SIG_IGN;
		sigaction(SIGPIPE, &sa, &osigint);
#else /* POSIX_SIGNALS */
		osigint = signal(SIGPIPE, SIG_IGN);
#endif /* POSIX_SIGNALS */
	}

	cc = writev(fd, iov, numargs * 2 + 1);
	retval = errno;

	if (clientflag) {
#ifdef POSIX_SIGNALS
		sigaction(SIGPIPE, &osigint, NULL);
#else /* POSIX_SIGNALS */
		signal(SIGPIPE, osigint);
#endif /* POSIX_SIGNALS */
	}

	if (lenarray)
		free(lenarray);
	free(iov);

	if (cc < 0) {
		return retval;
	}

	if (cc != totallen) {
		return KRB5_CC_IO;
	}

	/*
	 * Read back in the response word, if we're a client.
	 */

	if (clientflag) {
		fd_set readfds;
		struct timeval tv;

		tv.tv_sec = PCC_TIMEOUT;
		tv.tv_usec = 0;
		FD_ZERO(&readfds);
		FD_SET(fd, &readfds);

		cc = select(fd + 1, &readfds, NULL, NULL, &tv);

		if (cc == 0)
			return ETIMEDOUT;

		if (cc < 0)
			return errno;

		cc = read(fd, &response, sizeof(response));

		if (cc < 0) {
			return errno;
		}

		if (cc != sizeof(response)) {
			return KRB5_CC_IO;
		}
	}

	return response;
}

/*
 * Our internal socket read function (note: does timeout).
 */

static krb5_error_code
krb5_pcc_read_cccomm(krb5_context context, int fd, void **data,
		     unsigned int *len)
{
	uint32_t writelen;
	fd_set readfds;
	struct timeval tv;
	int cc;

	FD_ZERO(&readfds);
	FD_SET(fd, &readfds);
	tv.tv_sec = PCC_TIMEOUT;
	tv.tv_usec = 0;

	cc = select(fd + 1, &readfds, NULL, NULL, &tv);

	if (cc <= 0)
		return cc == 0 ? ETIMEDOUT : errno;

	cc = read(fd, &writelen, sizeof(writelen));

	if (cc < sizeof(writelen)) {
		return cc < 0 ? errno : KRB5_CC_IO;
	}

	if (writelen > 0) {
		/*
		 * Pad the length in case we want to NUL-terminate
		 */

		*data = malloc(writelen + 1);

		if (! *data) {
			return ENOMEM;
		}

		FD_SET(fd, &readfds);
		tv.tv_sec = PCC_TIMEOUT;
		tv.tv_usec = 0;

		cc = select(fd + 1, &readfds, NULL, NULL, &tv);

		if (cc <= 0) {
			free(*data);
			return cc == 0 ? ETIMEDOUT : errno;
		}

		cc = read(fd, *data, writelen);

		if (cc < writelen) {
			free(*data);
			return cc < 0 ? errno : KRB5_CC_IO;
		}
	} else {
		*data = NULL;
	}

	*len = writelen;

	return 0;
}

/*
 * A function which writes out a credential structure to the peer on
 * the other side
 */

static krb5_error_code
krb5_pcc_write_creds(krb5_context context, int fd, int32_t opcode,
		     int clientflag, krb5_creds *creds)
{
	EncKrbCredPart cr_enc;
	KrbCredInfo cr_info;
	krb5_error_code retval;
	krb5_data cr_info_data, auth_data;
	unsigned char *buf;
	size_t buf_size, len;
	krb5_boolean is_skey = 0;

	memset(&cr_info_data, 0, sizeof cr_info_data);
	memset(&auth_data, 0, sizeof auth_data);

	memset((void *) &cr_enc, 0, sizeof(cr_enc));
	memset((void *) &cr_info, 0, sizeof(cr_info));

	if (clientflag == 0 && opcode != 0)
		goto skipcreds;

	cr_info.prealm = &creds->client->realm;
	cr_info.pname = &creds->client->name;

	cr_info.srealm = &creds->server->realm;
	cr_info.sname = &creds->server->name;

	cr_info.key = creds->session;

	cr_info.authtime = &creds->times.authtime;
	cr_info.starttime = &creds->times.starttime;
	cr_info.endtime = &creds->times.endtime;
	cr_info.renew_till = &creds->times.renew_till;

	cr_info.flags = &creds->flags.b;

	cr_info.caddr = &creds->addresses;

	cr_enc.ticket_info.len = 1;
	cr_enc.ticket_info.val = &cr_info;

	ASN1_MALLOC_ENCODE(EncKrbCredPart, buf, buf_size, &cr_enc, &len, retval);
	if (retval) {
		goto exitout;
	}
	if (buf_size != len) {
		free(buf);
		retval = KRB5KRB_ERR_GENERIC;
		goto exitout;
	}
	cr_info_data.length = len;
	cr_info_data.data = buf;

	ASN1_MALLOC_ENCODE(AuthorizationData, buf, buf_size, &creds->authdata, &len, retval);
	if (retval) {
		goto exitout;
	}
	if (buf_size != len) {
		free(buf);
		retval = KRB5KRB_ERR_GENERIC;
		goto exitout;
	}
	auth_data.length = len;
	auth_data.data = buf;

skipcreds:
	retval = krb5_pcc_write_cccomm(context, fd, opcode, clientflag,
				       clientflag == 0 && opcode ? 0 : 5,

				       (void *) (cr_info_data.data),
				       cr_info_data.length,

				       (void *) creds->ticket.data,
				       creds->ticket.length,

				       (void *) creds->second_ticket.data,
				       creds->second_ticket.length,

				       (void *) &is_skey,
				       sizeof(krb5_boolean),

				       (void *) (auth_data.data),
				       auth_data.length);
	if (retval)
		goto exitout;

	retval = 0;
exitout:
	krb5_data_free(&cr_info_data);
	krb5_data_free(&auth_data);

	return retval;
}

/*
 * Function to read encoded credentials from a socket
 */

static krb5_error_code
krb5_pcc_read_creds(krb5_context context, int fd, krb5_creds *creds)
{
	EncKrbCredPart cr_enc;
	KrbCredInfo *cr_info;
	Principal princ;
	size_t len;
	krb5_data data;
	krb5_error_code retval;

	retval = krb5_pcc_read_cccomm(context, fd, (void **) &data.data,
				      &data.length);

	if (retval)
		return retval;

	if ((retval = decode_EncKrbCredPart(data.data, data.length, &cr_enc, &len))) {
		krb5_clear_error_string (context);
		free(data.data);
		return retval;
	}

	free(data.data);

	if (cr_enc.ticket_info.len != 1) {
		retval = KRB5KRB_ERR_GENERIC;
		goto errout;
	}

	cr_info = cr_enc.ticket_info.val;

	princ.realm = *(cr_info->prealm);
	princ.name = *(cr_info->pname);

	if ((retval = krb5_copy_principal(context,
					  &princ,
					  &creds->client))) {
		goto errout;
	}

	princ.realm = *(cr_info->srealm);
	princ.name = *(cr_info->sname);

	if ((retval = krb5_copy_principal(context,
					  &princ,
					  &creds->server))) {
		goto errout;
	}

	if ((retval = krb5_copy_keyblock_contents(context,
					 &cr_info->key,
					 &creds->session))) {
		goto errout;
	}

	creds->times.authtime = *(cr_info->authtime);
	creds->times.starttime = *(cr_info->starttime);
	creds->times.endtime = *(cr_info->endtime);
	creds->times.renew_till = *(cr_info->renew_till);

	creds->flags.b = *(cr_info->flags);

	if ((retval = copy_HostAddresses(cr_info->caddr, &creds->addresses))) {
		goto errout;
	}

	retval = krb5_pcc_read_cccomm(context, fd,
				      (void **) &creds->ticket.data,
				      &creds->ticket.length);

	if (retval)
		goto errout;

	retval = krb5_pcc_read_cccomm(context, fd,
				      (void **) &creds->second_ticket.data,
				      &creds->second_ticket.length);

	if (retval)
		goto errout;

	retval = krb5_pcc_read_cccomm(context, fd, (void **) &data.data,
				      &data.length);

	if (retval || data.length != sizeof(krb5_boolean))
		goto errout;

	/* creds->is_skey is not present in Heimdal
	 */
	free(data.data);

	retval = krb5_pcc_read_cccomm(context, fd, (void **) &data.data,
				      &data.length);

	if (retval)
		goto errout;

	if (data.length) {
		retval = decode_AuthorizationData(data.data, data.length, &creds->authdata, &len);
		free(data.data);
		if (retval) {
			krb5_clear_error_string (context);
			memset(&creds->authdata, 0, sizeof(creds->authdata));
			goto errout;
		}
	} else {
		memset(&creds->authdata, 0, sizeof(creds->authdata));
	}

errout:
	free_EncKrbCredPart(&cr_enc);

	return retval;
}
/*
 * Handle the "INITIALIZE" request.
 */

static void
krb5_pcc_s_initialize(krb5_context context, krb5_ccache cc, int fd)
{
	krb5_principal princ;
	krb5_error_code retval;
	char *namebuf;
	unsigned int namelen;

	retval = krb5_pcc_read_cccomm(context, fd, (void **) &namebuf,
				      &namelen);
	
	if (retval)
		goto fail;
	
	namebuf[namelen] = '\0';

	retval = krb5_parse_name(context, namebuf, &princ);
	free(namebuf);

	if (retval)
		goto fail;
	
	retval = krb5_cc_initialize(context, cc, princ);
	krb5_free_principal(context, princ);

fail:
	krb5_pcc_write_cccomm(context, fd, retval, 0, 0);
	return;
}

static void
krb5_pcc_s_destroy(krb5_context context, krb5_ccache *cc, int fd)
{
	krb5_error_code retval;

	retval = krb5_cc_destroy(context, *cc);

	if (retval)
		goto errout;

	retval = krb5_cc_resolve(context, PCC_INTERNAL_MEMCACHE, cc);

	if (retval)
		goto errout;
	
	retval = 0;
errout:
	krb5_pcc_write_cccomm(context, fd, retval, 0, 0);
}

/*
 * Handle the STORE request
 */

static void
krb5_pcc_s_store(krb5_context context, krb5_ccache cc, int fd)
{
	krb5_creds creds;
	krb5_error_code retval;

	memset((void *) &creds, 0, sizeof(creds));

	retval = krb5_pcc_read_creds(context, fd, &creds);

	if (retval)
		goto errout;

	retval = krb5_cc_store_cred(context, cc, &creds);

	if (retval)
		goto errout;

	retval = 0;
errout:
	krb5_free_cred_contents(context, &creds);
	krb5_pcc_write_cccomm(context, fd, retval, 0, 0);
}

/*
 * Handle the GET_PRINCIPAL request
 */

static void
krb5_pcc_s_get_principal(krb5_context context, krb5_ccache cc, int fd)
{
	krb5_principal princ = NULL;
	krb5_error_code retval;
	int numargs = 0;
	char *name = NULL;
	int namelen = 0;

	if ((retval = krb5_cc_get_principal(context, cc, &princ)))
		goto errout;

	if ((retval = krb5_unparse_name(context, princ, &name)))
		goto errout;

	namelen = strlen(name);
	numargs = 1;

	retval = 0;
errout:
	krb5_pcc_write_cccomm(context, fd, retval, 0, numargs,
			      (void *) name, namelen);

	if (princ)
		krb5_free_principal(context, princ);
	if (name)
		free(name);
}

/*
 * Handle the START_SEQ request
 */

static void
krb5_pcc_s_start_seq(krb5_context context, krb5_ccache cc, int fd)
{
	krb5_error_code retval;
	uint32_t curindex;
	krb5_cc_cursor cursor;
	krb5_pcc_connection *conn;
	int numargs = 0, i;

	for (conn = pcc_conn_head; conn != NULL; conn = conn->next) {
		if (conn->fd_comm == fd)
			break;
	}

	if (! conn) {
		retval = KRB5_CC_NOTFOUND;
		goto errout;
	}

	retval = krb5_cc_start_seq_get(context, cc, &cursor);

	if (retval)
		goto errout;

	if (conn->carray == NULL) {
		conn->carray = malloc(sizeof(krb5_cc_cursor *));
		if (! conn->carray) {
			krb5_cc_end_seq_get(context, cc, &cursor);
			retval = ENOMEM;
			goto errout;
		}
		conn->carray[0] = malloc(sizeof(krb5_cc_cursor));
		if (! conn->carray[0]) {
			krb5_cc_end_seq_get(context, cc, &cursor);
			retval = ENOMEM;
			goto errout;
		}
		*(conn->carray[0]) = cursor;
		curindex = 0;
		conn->max_cursor = 1;
	} else {
		for (i = 0; i < conn->max_cursor; i++)
			if (conn->carray[i] == NULL) {
				conn->carray[i] =
						malloc(sizeof(krb5_cc_cursor));
				if (! conn->carray[i]) {
					krb5_cc_end_seq_get(context, cc,
							    &cursor);
					retval = ENOMEM;
					goto errout;
				}
				*(conn->carray[i]) = cursor;
				curindex = i;
				break;
			}
		if (i == conn->max_cursor) {
			conn->carray = realloc(conn->carray,
					       sizeof(krb5_cc_cursor *) *
					       ++conn->max_cursor);
			if (! conn->carray) {
				krb5_cc_end_seq_get(context, cc, &cursor);
				retval = ENOMEM;
				goto errout;
			}
			conn->carray[i] = malloc(sizeof(krb5_cc_cursor));
			if (! conn->carray[i]) {
				krb5_cc_end_seq_get(context, cc, &cursor);
				retval = ENOMEM;
				goto errout;
			}
			*(conn->carray[i]) = cursor;
			curindex = i;
		}
	}

	numargs = 1;

	retval = 0;
errout:
	krb5_pcc_write_cccomm(context, fd, retval, 0, numargs,
			      (void *) &curindex, sizeof(curindex));
}

/*
 * Handle the NEXT_CRED request
 */

static void
krb5_pcc_s_next_cred(krb5_context context, krb5_ccache cc, int fd)
{
	krb5_error_code retval;
	uint32_t *curindex = NULL;
	krb5_pcc_connection *conn;
	krb5_creds creds;
	unsigned int curlen;

	memset((void *) &creds, 0, sizeof(creds));

	for (conn = pcc_conn_head; conn != NULL; conn = conn->next)
		if (conn->fd_comm == fd)
			break;

	if (! conn) {
		retval = KRB5_CC_NOTFOUND;
		goto errout;
	}

	retval = krb5_pcc_read_cccomm(context, fd, (void **) &curindex,
				      &curlen);

	if (retval)
		goto errout;

	if (*curindex >= conn->max_cursor) {
		retval = KRB5_CC_NOTFOUND;
		goto errout;
	}

	if (conn->carray[*curindex] == NULL) {
		retval = KRB5_CC_NOTFOUND;
		goto errout;
	}

	retval = krb5_cc_next_cred(context, cc, conn->carray[*curindex],
				      &creds);

errout:
	krb5_pcc_write_creds(context, fd, retval, 0, &creds);

	krb5_free_cred_contents(context, &creds);
	if (curindex)
		free(curindex);
}

/*
 * Handle the END_SEQ request
 */

static void
krb5_pcc_s_end_seq(krb5_context context, krb5_ccache cc, int fd)
{
	krb5_error_code retval;
	uint32_t *curindex = NULL;
	krb5_pcc_connection *conn;
	unsigned int curlen;

	for (conn = pcc_conn_head; conn != NULL; conn = conn->next)
		if (conn->fd_comm == fd)
			break;

	if (! conn) {
		retval = KRB5_CC_NOTFOUND;
		goto errout;
	}

	retval = krb5_pcc_read_cccomm(context, fd, (void **) &curindex,
				      &curlen);

	if (retval)
		goto errout;

	if (curlen != sizeof(uint32_t) || *curindex >= conn->max_cursor ||
	    conn->carray[*curindex] == NULL) {
		retval = KRB5_CC_NOTFOUND;
		goto errout;
	}

	retval = krb5_cc_end_seq_get(context, cc, conn->carray[*curindex]);

	if (retval)
		goto errout;

	free(conn->carray[*curindex]);
	conn->carray[*curindex] = NULL;

	retval = 0;

errout:
	if (curindex)
		free(curindex);
	krb5_pcc_write_cccomm(context, fd, retval, 0, 0);
}

/*
 * Handle requests from credential cache clients.
 *
 * We are making one big assumption here: clients will write the entire
 * request in one operation, so no short reads will result.  This makes
 * coding the server much easier.
 */

static void
krb5_pcc_handle_request(krb5_context context, krb5_ccache *cc_internal, int fd)
{
	int cc;
	uint32_t opcode;
	krb5_pcc_connection *conn, *conn2;

	/*
	 * Read in the opcode from the client
	 */

	cc = read(fd, (void *) &opcode, sizeof(opcode));

	if (cc < sizeof(opcode)) {
		/*
		 * We've gotten an error/EOF/short read here; close the
		 * connection.
		 */
		close(fd);
		if (pcc_conn_head && pcc_conn_head->fd_comm == fd) {
			conn = pcc_conn_head;
			pcc_conn_head = conn->next;
			krb5_pcc_conn_free(context, *cc_internal, conn);
		} else if (pcc_conn_head) {
			for (conn = pcc_conn_head; conn->next != NULL;
			     conn = conn->next) {
				if (conn->next->fd_comm == fd) {
					conn2 = conn->next;
					conn->next = conn2->next;
					krb5_pcc_conn_free(context,
							   *cc_internal, conn2);
					break;
				}
			}
		}
		FD_CLR(fd, &readfds);

		/*
		 * If we got rid of the maximum fd, rescan the list to generate
		 * a new maxmimum.
		 */

		if (fd == pcc_maxfd) {
			pcc_maxfd = 0;
			for (conn = pcc_conn_head; conn != NULL;
			     conn = conn->next) {
				if (conn->fd_comm > pcc_maxfd)
					pcc_maxfd = conn->fd_comm;
			}
			if (pcc_master_fd > pcc_maxfd)
				pcc_maxfd = pcc_master_fd;
		}
		return;
	}

	/*
	 * It wasn't clear to me what the "right" solution is for an
	 * unknown opcode.  I finally decided to return an error code.
	 */

	switch (opcode) {
	case PCC_OP_INITIALIZE:
		krb5_pcc_s_initialize(context, *cc_internal, fd);
		break;
	case PCC_OP_DESTROY:
		krb5_pcc_s_destroy(context, cc_internal, fd);
		break;
	case PCC_OP_STORE:
		krb5_pcc_s_store(context, *cc_internal, fd);
		break;
	case PCC_OP_GET_PRINCIPAL:
		krb5_pcc_s_get_principal(context, *cc_internal, fd);
		break;
	case PCC_OP_START_SEQ:
		krb5_pcc_s_start_seq(context, *cc_internal, fd);
		break;
	case PCC_OP_NEXT_CRED:
		krb5_pcc_s_next_cred(context, *cc_internal, fd);
		break;
	case PCC_OP_END_SEQ:
		krb5_pcc_s_end_seq(context, *cc_internal, fd);
		break;
	default:
		krb5_pcc_write_cccomm(context, fd, KRB5_CC_FORMAT, 0, 0);
		break;
	}
}

/*
 * This function implements the actual "credential cache" server
 */

static void
krb5_pcc_start_ccserver(krb5_context context, int master_fd)
{
	fd_set rfds;
	krb5_ccache cc_internal;
	krb5_error_code retval;
	krb5_pcc_connection *conn;
	struct rlimit rlim;

	/*
	 * Make it so we ignore SIGPIPE
	 */

#ifdef POSIX_SIGNALS
	struct sigaction sa;

	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	sa.sa_handler = SIG_IGN;
	sigaction(SIGPIPE, &sa, NULL);
#else /* POSIX_SIGNALS */
	signal(SIGPIPE, SIG_IGN);
#endif /* POSIX_SIGNALS */

	/*
	 * Create our own process group and become the leader of it.
	 */

#ifdef HAVE_SETPGID
	setpgid(0, 0);
#endif

	/*
	 * Set up our internal memory credential cache
	 */
	
	if ((retval = krb5_cc_resolve(context, PCC_INTERNAL_MEMCACHE,
				      &cc_internal))) {
		com_err("CCserver", retval, "while initializing internal "
			"cache");
		close(master_fd);
		return;
	}

	/*
	 * In some cases, keeping the file descriptors open that we
	 * inherited across the fork() during the ccserver creation can
	 * mess things up.  For example, if a Kerberized daemon creates
	 * a credential cache after setting up a pseudo-tty, the credential
	 * cache server will get a copy of the fd for the master side of
	 * the psuedo-tty ... which means that even if the daemon exits, the
	 * program running on the slave side of the pseudo-tty will never
	 * get a SIGHUP, because the master is still open inside of the
	 * ccserver, which means that the user's processes will never exit.
	 * Close all of our open file descriptors (except the "master"
	 * descriptor, obviously).  This means we can't do error reporting,
	 * but I've never been convinced that is useful anyway.
	 */

	if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) {
		int i;

		/*
		 * Bound this to something reasonable
		 */

		if (rlim.rlim_cur == RLIM_INFINITY || rlim.rlim_cur > 65536)
			rlim.rlim_cur = 65536;

		for (i = 0; i < rlim.rlim_cur; i++) {
			if (i != master_fd)
				close(i);
		}
	}

	/*
	 * Start our listening loop
	 */

	FD_ZERO(&readfds);
	FD_SET(master_fd, &readfds);
	pcc_maxfd = master_fd;
	pcc_master_fd = master_fd;

	while (1) {
		memcpy((void *) &rfds, (void *) &readfds, sizeof(rfds));
		retval = select(pcc_maxfd + 1, &rfds, NULL, NULL, NULL);

		if (retval < 0) {
			com_err("CCserver", retval, "while calling select()");
			goto exitout;
		}

		/*
		 * Handle requests for new connections
		 */
		if (FD_ISSET(master_fd, &rfds)) {
			char buf[1];
			int cc;

			cc = read(master_fd, buf, sizeof(buf));

			if (cc == 0) {
				/*
				 * We should perform some signalling that
				 * we're exiting, but I can't think of a
				 * good one
				 */
				goto exitout;
			}

			if (cc < 0) {
				com_err("CCserver", errno, "while reading "
					"request from master descriptor\n");
				goto exitout;
			}

			/*
			 * We ignore everything else except this
			 */

			if (buf[0] == PCC_NEW_DESCRIPTOR)
				krb5_pcc_send_new_fd(context, master_fd);
			if (buf[0] == PCC_KILL_SERVER)
				goto exitout;
			retval--;
		}

		/*
		 * Check other descriptors
		 */

		if (retval) {
			for (conn = pcc_conn_head; conn != NULL;
			     conn = conn->next) {
				if (FD_ISSET(conn->fd_comm, &rfds)) {
					krb5_pcc_handle_request(context,
								&cc_internal,
								conn->fd_comm);
					if (--retval <= 0)
						break;
				}
			}
		}

	}

exitout:
	krb5_cc_destroy(context, cc_internal);
	close(master_fd);
}

/*
 * Actually set up the so-called "pipe" credential cache, and start the
 * server process.
 */

static krb5_error_code
krb5_pcc_setup_ccserver(krb5_context context, krb5_ccache id)
{
	int sp[2];
	pid_t pid;

	/*
	 * Setup our socket connection before we fork the server
	 */

	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) < 0) {
		free(id);
		return errno;
	}

	/*
	 * Fork to start the credential cache server
	 */

	pid = fork();

	if (pid < 0) {
		return errno;
	}

	if (pid == 0) {
		krb5_context ccache_context;

		close(sp[0]);
		krb5_init_context(&ccache_context);
		krb5_pcc_start_ccserver(ccache_context, sp[1]);
		exit(0);
	} else {
		/*
		 * We want to try as hard as possible to insure that nothing
		 * messes with these descriptors, so we're using the
		 * hard limit as our boundary.  If the OS provides the
		 * FD_SETSIZE definition, the we make sure it doesn't
		 * hit against that.
		 */

		struct rlimit rl;
		rlim_t cur;
		int fd;

		close(sp[1]);

		if (getrlimit(RLIMIT_NOFILE, &rl) < 0) {
			int err = errno;
			close(sp[0]);
			return err;
		}

		fd = rl.rlim_max == RLIM_INFINITY ? 65536 : rl.rlim_max - 1;
		cur = rl.rlim_cur;
#ifdef FD_SETSIZE
		if (fd >= FD_SETSIZE)
			fd = FD_SETSIZE - 1;
#endif /* FD_SETSIZE */

		rl.rlim_cur = rl.rlim_max;
		if (setrlimit(RLIMIT_NOFILE, &rl) < 0) {
			int err = errno;
			close(sp[0]);
			return err;
		}

		if (dup2(sp[0], fd) < 0) {
			int err = errno;
			close(sp[0]);
			return err;
		}

		rl.rlim_max = fd;
		rl.rlim_cur = cur > fd ? fd : cur;

		if (setrlimit(RLIMIT_NOFILE, &rl) < 0) {
			int err = errno;
			close(sp[0]);
			close(fd);
			return err;
		}

		PCACHE(id)->fd_master = fd;
	}

	return 0;
}

/*
 * This does all of the real client-side work
 * in terms of connecting to the credential cache server)
 */

static krb5_error_code
pcc_open(krb5_context context, krb5_ccache *id, const char *residual)
{
	krb5_ccache lid;
	krb5_pcc_data *pd;
	krb5_error_code retval;
	struct timeval tv;
	struct msghdr msg;
	struct iovec iov[1];
	fd_set readfds;
	int cc, newfd;
	char c;

#ifdef POSIX_SIGNALS
	struct sigaction osigint, sa;
#else /* POSIX_SIGNALS */
	RETSIGTYPE (*osigint)();
#endif /* POSIX_SIGNALS */

#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL) && !defined(HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS)
	union {
		struct cmsghdr cm;
		char control[CMSG_SPACE(sizeof(int))];
	} control_un;
	struct cmsghdr *cmptr;
#endif /* HAVE_STRUCT_MSGHDR_MSG_CONTROL && !HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS */

	retval = _krb5_cc_allocate(context, &krb5_pcc_ops, &lid);
	if (retval)
		return retval;

	pd = (krb5_pcc_data *) malloc(sizeof(krb5_pcc_data));
	if (!pd) {
		free(lid);
		return KRB5_CC_NOMEM;
	}
	memset(pd, 0, sizeof(krb5_pcc_data));

	lid->data.data = pd;
	lid->data.length = sizeof(*pd);

	if (!strcmp(residual, "NewCache")) {
		char tmpbuf[256];
		retval = krb5_pcc_setup_ccserver(context, lid);
		if (retval) {
			krb5_pcc_free(context, lid);
			return retval;
		}
		sprintf(tmpbuf, "%d", pd->fd_master);
		pd->name = strdup(tmpbuf);
		if (!pd->name) {
			krb5_pcc_free(context, lid);
			return KRB5_CC_NOMEM;
		}
	} else {
		unsigned long l;
#ifdef HAVE_STRTOUL
		char *endptr;

		l = strtoul(residual, &endptr, 10);

		if (endptr == NULL || endptr == residual) {
			krb5_pcc_free(context, lid);
			return KRB5_CC_BADNAME;
		}
#else /* HAVE_STRTOUL */
		l = atoi(residual);
#endif /* HAVE_STRTOUL */
		if (l < 3) {
			krb5_pcc_free(context, lid);
			return KRB5_CC_BADNAME;
		}

		pd->fd_master = l;
		pd->name = strdup(residual);
	}

	/*
	 * Now that we've got the master descriptor number,
	 * send a message to the credential cache server requesting a
	 * communication descriptor.  Make sure SIGPIPE is blocked before
	 * trying to write to the CCache server.
	 */

#ifdef POSIX_SIGNALS
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	sa.sa_handler = SIG_IGN;
	sigaction(SIGPIPE, &sa, &osigint);
#else /* POSIX_SIGNALS */
	osigint = signal(SIGPIPE, SIG_IGN);
#endif /* POSIX_SIGNALS */

	c = PCC_NEW_DESCRIPTOR;

	cc = write(pd->fd_master, &c, 1);
	retval = errno;

#ifdef POSIX_SIGNALS
	sigaction(SIGPIPE, &osigint, NULL);
#else /* POSIX_SIGNALS */
	signal(SIGPIPE, osigint);
#endif /* POSIX_SIGNALS */

	if (cc < 0) {
		retval = (retval == EBADF || retval == EPIPE) ?
					KRB5_FCC_NOFILE : retval;
		pd->fd_master = 0;
		krb5_pcc_free(context, lid);
		return retval;
	}

	if (cc != 1) {
		pd->fd_master = 0;
		krb5_pcc_free(context, lid);
		return KRB5_CC_WRITE;
	}

	tv.tv_sec = PCC_TIMEOUT;
	tv.tv_usec = 0;

	FD_ZERO(&readfds);
	FD_SET(pd->fd_master, &readfds);

	cc = select(pd->fd_master + 1, &readfds,
		    NULL, NULL, &tv);
	
	if (cc < 0) {
		retval = errno;
		pd->fd_master = 0;
		krb5_pcc_free(context, lid);
		return retval;
	}

	if (cc == 0) {
		pd->fd_master = 0;
		krb5_pcc_free(context, lid);
		return ETIMEDOUT;
	}

	/*
	 * We've gotten a reply; read in the request and see if we got
	 * a valid descriptor.
	 */

#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL) && !defined(HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS)
	msg.msg_control = control_un.control;
	msg.msg_controllen = sizeof(control_un.control);
#else /* HAVE_STRUCT_MSGHDR_MSG_CONTROL && !HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS */
	msg.msg_accrights = (caddr_t) &newfd;
	msg.msg_accrightslen = sizeof(newfd);
#endif /* HAVE_STRUCT_MSGHDR_MSG_CONTROL && !HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS */

	msg.msg_name = NULL;
	msg.msg_namelen = 0;

	iov[0].iov_base = &c;
	iov[0].iov_len = sizeof(c);
	msg.msg_iov = iov;
	msg.msg_iovlen = 1;

	cc = recvmsg(pd->fd_master, &msg, 0);

	if (cc <= 0) {
		retval = errno;
		pd->fd_master = 0;
		krb5_pcc_free(context, lid);
		return cc < 0 ? retval : KRB5_CC_IO;
	}

#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL) && !defined(HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS)
	if ((cmptr = CMSG_FIRSTHDR(&msg)) != NULL &&
	    cmptr->cmsg_len == CMSG_LEN(sizeof(newfd))) {
		if (cmptr->cmsg_level != SOL_SOCKET) {
			pd->fd_master = 0;
			krb5_pcc_free(context, lid);
			return KRB5_CC_IO;
		}
		if (cmptr->cmsg_type != SCM_RIGHTS) {
			pd->fd_master = 0;
			krb5_pcc_free(context, lid);
			return KRB5_CC_IO;
		}
		newfd = *((int *) CMSG_DATA(cmptr));
	} else {
		pd->fd_master = 0;
		krb5_pcc_free(context, lid);
		return KRB5_CC_IO;
	}
#else /* HAVE_STRUCT_MSGHDR_MSG_CONTROL && !HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS */
	if (msg.msg_accrightslen != sizeof(newfd)) {
		pd->fd_master = 0;
		krb5_pcc_free(context, lid);
		return KRB5_CC_IO;
	}
#endif /* HAVE_STRUCT_MSGHDR_MSG_CONTROL !HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS */

	/* Descriptor received! */

	pd->fd_comm = newfd;

	*id = lid;

	return 0;
}

/*
 */

static krb5_error_code
krb5_pcc_resolve(krb5_context context, krb5_ccache *id, const char *residual)
{
	return pcc_open(context, id, residual);
}

/*
 */

static krb5_error_code
krb5_pcc_gen_new(krb5_context context, krb5_ccache *id)
{
	const char *ename, *name;

	name = "NewCache";

	ename = getenv("KRB5CCNAME");
	if (ename && strncmp(ename, "PIPE:", 5) == 0 && strlen(ename + 5) > 0) {
		name = ename + 5;
	}

	return pcc_open(context, id, name);
}

/*
 * Return our credential cache name (which should be the descriptor number)
 */

static const char *
krb5_pcc_get_name(krb5_context context, krb5_ccache id)
{
	return (char *) PCACHE(id)->name;
}

/*
 * Our initialize function (which basically clears out the credential cache
 * and sets up a new primary principal)
 */

static krb5_error_code
krb5_pcc_initialize(krb5_context context, krb5_ccache id,
		    krb5_principal princ)
{
	krb5_pcc_data *data = PCACHE(id);
	krb5_error_code retval;
	char *name;

	if ((retval = krb5_unparse_name(context, princ, &name)))
		return retval;

	retval = krb5_pcc_write_cccomm(context, data->fd_comm,
				       PCC_OP_INITIALIZE, 1, 1,
				       (void *) name, strlen(name));
	
	free(name);

	return retval;
}

/*
 * Our destroy function (clear out and invalidate the credential cache)
 */

static krb5_error_code
krb5_pcc_destroy(krb5_context context, krb5_ccache id)
{
	krb5_pcc_data *data = PCACHE(id);
	krb5_error_code retval;

	retval = krb5_pcc_write_cccomm(context, data->fd_comm,
				       PCC_OP_DESTROY, 1, 0);
	
	if (retval)
		return retval;
	
	data->fd_master = 0;
	krb5_pcc_free(context, id);

	return 0;
}

/*
 * Our close function (simply close the descriptor and free everything)
 */

static krb5_error_code
krb5_pcc_close(krb5_context context, krb5_ccache id)
{
	krb5_pcc_data *data = PCACHE(id);

	if (data)
		data->fd_master = 0;
	krb5_pcc_free(context, id);

	return 0;
}

/*
 * Our function to store credentials to the credential cache server
 */

static krb5_error_code
krb5_pcc_store(krb5_context context, krb5_ccache id, krb5_creds *creds)
{
	krb5_pcc_data *data = PCACHE(id);

	return krb5_pcc_write_creds(context, data->fd_comm, PCC_OP_STORE,
				    1, creds);
}

/*
 * Our stub cc_retrieve function (call the default)
 */

static krb5_error_code
krb5_pcc_retrieve(krb5_context context, krb5_ccache id, krb5_flags whichfields,
		  krb5_creds *mcreds, krb5_creds *creds)
{
	/* blindly changed from krb5_cc_retrieve_cred_default
	 */
	return krb5_cc_retrieve_cred(context, id, whichfields, mcreds,
					     creds);
}

/*
 * Our function to get the primary principal from the credential cache
 */

static krb5_error_code
krb5_pcc_get_principal(krb5_context context, krb5_ccache id,
		       krb5_principal *princ)
{
	krb5_pcc_data *data = PCACHE(id);
	krb5_error_code retval;
	char *namebuf;
	unsigned int namelen;

	retval = krb5_pcc_write_cccomm(context, data->fd_comm,
				       PCC_OP_GET_PRINCIPAL, 1, 0);

	if (retval)
		return retval;

	retval = krb5_pcc_read_cccomm(context, data->fd_comm,
				      (void **) &namebuf, &namelen);

	if (retval)
		return retval;

	namebuf[namelen] = '\0';
	retval = krb5_parse_name(context, namebuf, princ);
	free(namebuf);

	return retval;
}

/*
 * Create a cursor for credential cache sequential access
 */

static krb5_error_code
krb5_pcc_start_seq_get(krb5_context context, krb5_ccache id,
		       krb5_cc_cursor *cursor)
{
	krb5_pcc_data *data = PCACHE(id);
	krb5_error_code retval;
	uint32_t *cnumber;
	unsigned int numlen;

	retval = krb5_pcc_write_cccomm(context, data->fd_comm,
				       PCC_OP_START_SEQ, 1, 0);

	if (retval)
		return retval;

	retval = krb5_pcc_read_cccomm(context, data->fd_comm,
				      (void **) &cnumber, &numlen);

	if (retval)
		return retval;

	if (numlen != sizeof(uint32_t)) {
		free(cnumber);
		return KRB5_CC_IO;
	}

	*cursor = cnumber;

	return 0;
}

/*
 * Get the next credential from the cache, using the cursor
 */

static krb5_error_code
krb5_pcc_next_cred(krb5_context context, krb5_ccache id,
		   krb5_cc_cursor *cursor, krb5_creds *creds)
{
	krb5_pcc_data *data = PCACHE(id);
	krb5_error_code retval;
	uint32_t *cnumber = (uint32_t *) *cursor;

	retval = krb5_pcc_write_cccomm(context, data->fd_comm,
				       PCC_OP_NEXT_CRED, 1, 1,
				       (void *) cnumber, sizeof(uint32_t));

	if (retval)
		return retval;

	return krb5_pcc_read_creds(context, data->fd_comm, creds);
}

/*
 * Close out the cursor used for sequential access
 */

static krb5_error_code
krb5_pcc_end_seq_get(krb5_context context, krb5_ccache id,
		     krb5_cc_cursor *cursor)
{
	krb5_pcc_data *data = PCACHE(id);
	krb5_error_code retval;
	uint32_t *cnumber = (uint32_t *) *cursor;

	retval = krb5_pcc_write_cccomm(context, data->fd_comm,
				       PCC_OP_END_SEQ, 1, 1,
				       (void *) cnumber, sizeof(uint32_t));

	if (retval)
		return retval;

	free(cnumber);
	return 0;
}

/*
 * Right now we don't support remove
 */

static krb5_error_code
krb5_pcc_remove_cred(krb5_context context, krb5_ccache id, krb5_flags flags,
		     krb5_creds *creds)
{
	return KRB5_CC_NOSUPP;
}

/*
 * Our set_flags function (no-op)
 */

static krb5_error_code
krb5_pcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags)
{
	return 0;
}
static krb5_error_code
krb5_pcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
{
	*PCACHE(to) = *PCACHE(from);

	krb5_data_free(&from->data);

	return 0;
}
static krb5_error_code
krb5_pcc_default_name(krb5_context context, char **str)
{
    *str = strdup("PIPE:NewCache");
    if (*str == NULL) {
	krb5_set_error_string(context, "out of memory");
	return ENOMEM;
    }
    return 0;
}

const krb5_cc_ops krb5_pcc_ops = {
	"PIPE",
	krb5_pcc_get_name,
	krb5_pcc_resolve,
	krb5_pcc_gen_new,
	krb5_pcc_initialize,
	krb5_pcc_destroy,
	krb5_pcc_close,
	krb5_pcc_store,
	NULL, /* krb5_pcc_retrieve */
	krb5_pcc_get_principal,
	krb5_pcc_start_seq_get,
	krb5_pcc_next_cred,
	krb5_pcc_end_seq_get,
	krb5_pcc_remove_cred,
	krb5_pcc_set_flags,
	NULL,
	NULL,
	NULL,
	NULL,
	krb5_pcc_move,
	krb5_pcc_default_name
};
#endif /* ENABLE_PIPE_CCACHE */

