#include "k5-int.h"
#include "cc-int.h"
#include <stdio.h>
#include <errno.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifndef O_CLOEXEC
#define O_CLOEXEC 0
#endif
extern const krb5_cc_ops krb5_cc_file_ops;
krb5_error_code krb5_change_cache(void);
static krb5_error_code interpret_errno(krb5_context, int);
#define FVNO_BASE 0x0500
#define FCC_TAG_DELTATIME 1
#ifndef TKT_ROOT
#ifdef MSDOS_FILESYSTEM
#define TKT_ROOT "\\tkt"
#else
#define TKT_ROOT "/tmp/tkt"
#endif
#endif
typedef struct fcc_data_st {
k5_cc_mutex lock;
char *filename;
} fcc_data;
struct krb5_fcc_ptcursor_data {
krb5_boolean first;
};
typedef struct _krb5_fcc_cursor {
FILE *fp;
int version;
} krb5_fcc_cursor;
k5_cc_mutex krb5int_cc_file_mutex = K5_CC_MUTEX_PARTIAL_INITIALIZER;
static krb5_error_code
set_errmsg_filename(krb5_context context, krb5_error_code ret,
const char *fname)
{
if (!ret)
return 0;
k5_setmsg(context, ret, "%s (filename: %s)", error_message(ret), fname);
return ret;
}
static krb5_error_code
get_size(krb5_context context, FILE *fp, size_t *size_out)
{
struct stat sb;
*size_out = 0;
if (fstat(fileno(fp), &sb) == -1)
return interpret_errno(context, errno);
if (sizeof(off_t) > sizeof(size_t) && sb.st_size > (off_t)SIZE_MAX)
*size_out = SIZE_MAX;
else
*size_out = sb.st_size;
return 0;
}
static krb5_error_code
read_bytes(krb5_context context, FILE *fp, void *buf, size_t len)
{
size_t nread;
nread = fread(buf, 1, len, fp);
if (nread < len)
return ferror(fp) ? errno : KRB5_CC_END;
return 0;
}
static krb5_error_code
read32(krb5_context context, FILE *fp, int version, struct k5buf *buf,
uint32_t *out)
{
krb5_error_code ret;
char bytes[4];
ret = read_bytes(context, fp, bytes, 4);
if (ret)
return ret;
if (buf != NULL)
k5_buf_add_len(buf, bytes, 4);
*out = (version < 3) ? load_32_n(bytes) : load_32_be(bytes);
return 0;
}
static krb5_error_code
read16(krb5_context context, FILE *fp, int version, uint16_t *out)
{
krb5_error_code ret;
char bytes[2];
ret = read_bytes(context, fp, bytes, 2);
if (ret)
return ret;
*out = (version < 3) ? load_16_n(bytes) : load_16_be(bytes);
return 0;
}
static krb5_error_code
load_bytes(krb5_context context, FILE *fp, size_t len, struct k5buf *buf)
{
void *ptr;
ptr = k5_buf_get_space(buf, len);
return (ptr == NULL) ? KRB5_CC_NOMEM : read_bytes(context, fp, ptr, len);
}
static krb5_error_code
load_data(krb5_context context, FILE *fp, int version, size_t maxsize,
struct k5buf *buf)
{
krb5_error_code ret;
uint32_t count;
ret = read32(context, fp, version, buf, &count);
if (ret)
return ret;
if (count > maxsize)
return KRB5_CC_FORMAT;
return load_bytes(context, fp, count, buf);
}
static krb5_error_code
load_principal(krb5_context context, FILE *fp, int version, size_t maxsize,
struct k5buf *buf)
{
krb5_error_code ret;
uint32_t count;
if (version > 1) {
ret = load_bytes(context, fp, 4, buf);
if (ret)
return ret;
}
ret = read32(context, fp, version, buf, &count);
if (ret)
return ret;
if (version != 1)
count++;
while (count-- > 0) {
ret = load_data(context, fp, version, maxsize, buf);
if (ret)
return ret;
}
return 0;
}
static krb5_error_code
load_cred(krb5_context context, FILE *fp, int version, size_t maxsize,
struct k5buf *buf)
{
krb5_error_code ret;
uint32_t count, i;
ret = load_principal(context, fp, version, maxsize, buf);
if (ret)
return ret;
ret = load_principal(context, fp, version, maxsize, buf);
if (ret)
return ret;
ret = load_bytes(context, fp, (version == 3) ? 4 : 2, buf);
if (ret)
return ret;
ret = load_data(context, fp, version, maxsize, buf);
if (ret)
return ret;
ret = load_bytes(context, fp, 4 * 4 + 1 + 4, buf);
if (ret)
return ret;
for (i = 0; i < 2; i++) {
ret = read32(context, fp, version, buf, &count);
if (ret)
return ret;
while (count-- > 0) {
ret = load_bytes(context, fp, 2, buf);
if (ret)
return ret;
ret = load_data(context, fp, version, maxsize, buf);
if (ret)
return ret;
}
}
ret = load_data(context, fp, version, maxsize, buf);
if (ret)
return ret;
return load_data(context, fp, version, maxsize, buf);
}
static krb5_error_code
read_principal(krb5_context context, FILE *fp, int version,
krb5_principal *princ)
{
krb5_error_code ret;
struct k5buf buf;
size_t maxsize;
*princ = NULL;
k5_buf_init_dynamic(&buf);
ret = get_size(context, fp, &maxsize);
if (ret)
goto cleanup;
ret = load_principal(context, fp, version, maxsize, &buf);
if (ret)
goto cleanup;
ret = k5_buf_status(&buf);
if (ret)
goto cleanup;
ret = k5_unmarshal_princ(buf.data, buf.len, version, princ);
cleanup:
k5_buf_free(&buf);
return ret;
}
static krb5_error_code
open_cache_file(krb5_context context, const char *filename,
krb5_boolean writable, FILE **fp_out)
{
krb5_error_code ret;
int fd, flags, lockmode;
FILE *fp;
*fp_out = NULL;
flags = writable ? (O_RDWR | O_APPEND) : O_RDONLY;
fd = open(filename, flags | O_BINARY | O_CLOEXEC, 0600);
if (fd == -1)
return interpret_errno(context, errno);
set_cloexec_fd(fd);
lockmode = writable ? KRB5_LOCKMODE_EXCLUSIVE : KRB5_LOCKMODE_SHARED;
ret = krb5_lock_file(context, fd, lockmode);
if (ret) {
(void)close(fd);
return ret;
}
fp = fdopen(fd, writable ? "r+b" : "rb");
if (fp == NULL) {
(void)krb5_unlock_file(context, fd);
(void)close(fd);
return KRB5_CC_NOMEM;
}
*fp_out = fp;
return 0;
}
static krb5_error_code
close_cache_file(krb5_context context, FILE *fp)
{
int st;
krb5_error_code ret;
if (fp == NULL)
return 0;
ret = krb5_unlock_file(context, fileno(fp));
st = fclose(fp);
if (ret)
return ret;
return st ? interpret_errno(context, errno) : 0;
}
static krb5_error_code
read_header(krb5_context context, FILE *fp, int *version_out)
{
krb5_error_code ret;
krb5_os_context os_ctx = &context->os_context;
uint16_t fields_len, tag, flen;
uint32_t time_offset, usec_offset;
char i16buf[2];
int version;
*version_out = 0;
ret = read_bytes(context, fp, i16buf, 2);
if (ret)
return KRB5_CC_FORMAT;
version = load_16_be(i16buf) - FVNO_BASE;
if (version < 1 || version > 4)
return KRB5_CCACHE_BADVNO;
*version_out = version;
if (version < 4)
return 0;
if (read16(context, fp, version, &fields_len))
return KRB5_CC_FORMAT;
while (fields_len) {
if (fields_len < 4 || read16(context, fp, version, &tag) ||
read16(context, fp, version, &flen) || flen > fields_len - 4)
return KRB5_CC_FORMAT;
switch (tag) {
case FCC_TAG_DELTATIME:
if (flen != 8 ||
read32(context, fp, version, NULL, &time_offset) ||
read32(context, fp, version, NULL, &usec_offset))
return KRB5_CC_FORMAT;
if (!(context->library_options & KRB5_LIBOPT_SYNC_KDCTIME) ||
(os_ctx->os_flags & KRB5_OS_TOFFSET_VALID))
break;
os_ctx->time_offset = time_offset;
os_ctx->usec_offset = usec_offset;
os_ctx->os_flags = ((os_ctx->os_flags & ~KRB5_OS_TOFFSET_TIME) |
KRB5_OS_TOFFSET_VALID);
break;
default:
if (flen && fseek(fp, flen, SEEK_CUR) != 0)
return KRB5_CC_FORMAT;
break;
}
fields_len -= (4 + flen);
}
return 0;
}
static void
marshal_header(krb5_context context, struct k5buf *buf, krb5_principal princ)
{
krb5_os_context os_ctx = &context->os_context;
int version = context->fcc_default_format - FVNO_BASE;
uint16_t fields_len;
version = context->fcc_default_format - FVNO_BASE;
k5_buf_add_uint16_be(buf, FVNO_BASE + version);
if (version >= 4) {
fields_len = 0;
if (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID)
fields_len += 12;
k5_buf_add_uint16_be(buf, fields_len);
if (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID) {
k5_buf_add_uint16_be(buf, FCC_TAG_DELTATIME);
k5_buf_add_uint16_be(buf, 8);
k5_buf_add_uint32_be(buf, os_ctx->time_offset);
k5_buf_add_uint32_be(buf, os_ctx->usec_offset);
}
}
k5_marshal_princ(buf, version, princ);
}
static krb5_error_code KRB5_CALLCONV
fcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
{
krb5_error_code ret;
fcc_data *data = id->data;
ssize_t nwritten;
int st, flags, fd = -1;
struct k5buf buf = EMPTY_K5BUF;
krb5_boolean file_locked = FALSE;
k5_cc_mutex_lock(context, &data->lock);
unlink(data->filename);
flags = O_CREAT | O_EXCL | O_RDWR | O_BINARY | O_CLOEXEC;
fd = open(data->filename, flags, 0600);
if (fd == -1) {
ret = interpret_errno(context, errno);
goto cleanup;
}
set_cloexec_fd(fd);
#if defined(HAVE_FCHMOD) || defined(HAVE_CHMOD)
#ifdef HAVE_FCHMOD
st = fchmod(fd, S_IRUSR | S_IWUSR);
#else
st = chmod(data->filename, S_IRUSR | S_IWUSR);
#endif
if (st == -1) {
ret = interpret_errno(context, errno);
goto cleanup;
}
#endif
ret = krb5_lock_file(context, fd, KRB5_LOCKMODE_EXCLUSIVE);
if (ret)
goto cleanup;
file_locked = TRUE;
k5_buf_init_dynamic(&buf);
marshal_header(context, &buf, princ);
ret = k5_buf_status(&buf);
if (ret)
goto cleanup;
nwritten = write(fd, buf.data, buf.len);
if (nwritten == -1)
ret = interpret_errno(context, errno);
if ((size_t)nwritten != buf.len)
ret = KRB5_CC_IO;
cleanup:
k5_buf_free(&buf);
if (file_locked)
krb5_unlock_file(context, fd);
if (fd != -1)
close(fd);
k5_cc_mutex_unlock(context, &data->lock);
krb5_change_cache();
return set_errmsg_filename(context, ret, data->filename);
}
static void
free_fccdata(krb5_context context, fcc_data *data)
{
k5_cc_mutex_assert_unlocked(context, &data->lock);
free(data->filename);
k5_cc_mutex_destroy(&data->lock);
free(data);
}
static krb5_error_code KRB5_CALLCONV
fcc_close(krb5_context context, krb5_ccache id)
{
free_fccdata(context, id->data);
free(id);
return 0;
}
static krb5_error_code KRB5_CALLCONV
fcc_destroy(krb5_context context, krb5_ccache id)
{
krb5_error_code ret = 0;
fcc_data *data = id->data;
int st, fd;
struct stat buf;
unsigned long i, size;
unsigned int wlen;
char zeros[BUFSIZ];
k5_cc_mutex_lock(context, &data->lock);
fd = open(data->filename, O_RDWR | O_BINARY | O_CLOEXEC, 0);
if (fd < 0) {
ret = interpret_errno(context, errno);
goto cleanup;
}
set_cloexec_fd(fd);
#ifdef MSDOS_FILESYSTEM
st = fstat(fd, &buf);
if (st == -1) {
ret = interpret_errno(context, errno);
size = 0;
} else {
size = (unsigned long)buf.st_size;
}
memset(zeros, 0, BUFSIZ);
while (size > 0) {
wlen = (int)((size > BUFSIZ) ? BUFSIZ : size);
i = write(fd, zeros, wlen);
if (i < 0) {
ret = interpret_errno(context, errno);
break;
}
size -= i;
}
(void)close(fd);
st = unlink(data->filename);
if (st < 0) {
ret = interpret_errno(context, errno);
goto cleanup;
}
#else
st = unlink(data->filename);
if (st < 0) {
ret = interpret_errno(context, errno);
(void)close(fd);
goto cleanup;
}
st = fstat(fd, &buf);
if (st < 0) {
ret = interpret_errno(context, errno);
(void)close(fd);
goto cleanup;
}
size = (unsigned long)buf.st_size;
memset(zeros, 0, BUFSIZ);
for (i = 0; i < size / BUFSIZ; i++) {
if (write(fd, zeros, BUFSIZ) < 0) {
ret = interpret_errno(context, errno);
(void)close(fd);
goto cleanup;
}
}
wlen = size % BUFSIZ;
if (write(fd, zeros, wlen) < 0) {
ret = interpret_errno(context, errno);
(void)close(fd);
goto cleanup;
}
st = close(fd);
if (st)
ret = interpret_errno(context, errno);
#endif
cleanup:
(void)set_errmsg_filename(context, ret, data->filename);
k5_cc_mutex_unlock(context, &data->lock);
free_fccdata(context, data);
free(id);
krb5_change_cache();
return ret;
}
extern const krb5_cc_ops krb5_fcc_ops;
static krb5_error_code KRB5_CALLCONV
fcc_resolve(krb5_context context, krb5_ccache *id, const char *residual)
{
krb5_ccache lid;
krb5_error_code ret;
fcc_data *data;
data = malloc(sizeof(fcc_data));
if (data == NULL)
return KRB5_CC_NOMEM;
data->filename = strdup(residual);
if (data->filename == NULL) {
free(data);
return KRB5_CC_NOMEM;
}
ret = k5_cc_mutex_init(&data->lock);
if (ret) {
free(data->filename);
free(data);
return ret;
}
lid = malloc(sizeof(struct _krb5_ccache));
if (lid == NULL) {
free_fccdata(context, data);
return KRB5_CC_NOMEM;
}
lid->ops = &krb5_fcc_ops;
lid->data = data;
lid->magic = KV5M_CCACHE;
*id = lid;
return 0;
}
static krb5_error_code KRB5_CALLCONV
fcc_start_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
{
krb5_fcc_cursor *fcursor = NULL;
krb5_error_code ret;
krb5_principal princ = NULL;
fcc_data *data = id->data;
FILE *fp = NULL;
int version;
k5_cc_mutex_lock(context, &data->lock);
fcursor = malloc(sizeof(krb5_fcc_cursor));
if (fcursor == NULL) {
ret = KRB5_CC_NOMEM;
goto cleanup;
}
ret = open_cache_file(context, data->filename, FALSE, &fp);
if (ret)
goto cleanup;
ret = read_header(context, fp, &version);
if (ret)
goto cleanup;
ret = read_principal(context, fp, version, &princ);
if (ret)
goto cleanup;
(void)krb5_unlock_file(context, fileno(fp));
fcursor->fp = fp;
fp = NULL;
fcursor->version = version;
*cursor = (krb5_cc_cursor)fcursor;
fcursor = NULL;
cleanup:
(void)close_cache_file(context, fp);
free(fcursor);
krb5_free_principal(context, princ);
k5_cc_mutex_unlock(context, &data->lock);
return set_errmsg_filename(context, ret, data->filename);
}
static inline krb5_boolean
cred_removed(krb5_creds *c)
{
return c->times.endtime == 0 && c->times.authtime != 0;
}
static krb5_error_code KRB5_CALLCONV
fcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor,
krb5_creds *creds)
{
krb5_error_code ret;
krb5_fcc_cursor *fcursor = *cursor;
fcc_data *data = id->data;
struct k5buf buf;
size_t maxsize;
krb5_boolean file_locked = FALSE;
memset(creds, 0, sizeof(*creds));
k5_cc_mutex_lock(context, &data->lock);
k5_buf_init_dynamic_zap(&buf);
ret = krb5_lock_file(context, fileno(fcursor->fp), KRB5_LOCKMODE_SHARED);
if (ret)
goto cleanup;
file_locked = TRUE;
for (;;) {
ret = get_size(context, fcursor->fp, &maxsize);
if (ret)
goto cleanup;
ret = load_cred(context, fcursor->fp, fcursor->version, maxsize, &buf);
if (ret)
goto cleanup;
ret = k5_buf_status(&buf);
if (ret)
goto cleanup;
ret = k5_unmarshal_cred(buf.data, buf.len, fcursor->version, creds);
if (ret)
goto cleanup;
if (!cred_removed(creds))
break;
k5_buf_truncate(&buf, 0);
krb5_free_cred_contents(context, creds);
}
cleanup:
if (file_locked)
(void)krb5_unlock_file(context, fileno(fcursor->fp));
k5_cc_mutex_unlock(context, &data->lock);
k5_buf_free(&buf);
return set_errmsg_filename(context, ret, data->filename);
}
static krb5_error_code KRB5_CALLCONV
fcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
{
krb5_fcc_cursor *fcursor = *cursor;
(void)fclose(fcursor->fp);
free(fcursor);
*cursor = NULL;
return 0;
}
krb5_error_code
krb5int_fcc_new_unique(krb5_context context, char *template, krb5_ccache *id)
{
krb5_ccache lid;
int fd;
krb5_error_code ret;
fcc_data *data;
char fcc_fvno[2];
int16_t fcc_flen = 0;
int errsave, cnt;
fd = mkstemp(template);
if (fd == -1)
return interpret_errno(context, errno);
set_cloexec_fd(fd);
data = malloc(sizeof(fcc_data));
if (data == NULL) {
close(fd);
unlink(template);
return KRB5_CC_NOMEM;
}
data->filename = strdup(template);
if (data->filename == NULL) {
free(data);
close(fd);
unlink(template);
return KRB5_CC_NOMEM;
}
ret = k5_cc_mutex_init(&data->lock);
if (ret) {
free(data->filename);
free(data);
close(fd);
unlink(template);
return ret;
}
k5_cc_mutex_lock(context, &data->lock);
#ifndef HAVE_FCHMOD
#ifdef HAVE_CHMOD
chmod(data->filename, S_IRUSR | S_IWUSR);
#endif
#else
fchmod(fd, S_IRUSR | S_IWUSR);
#endif
store_16_be(context->fcc_default_format, fcc_fvno);
cnt = write(fd, &fcc_fvno, 2);
if (cnt != 2) {
errsave = errno;
(void)close(fd);
(void)unlink(data->filename);
ret = (cnt == -1) ? interpret_errno(context, errsave) : KRB5_CC_IO;
goto err_out;
}
if (context->fcc_default_format == FVNO_BASE + 4) {
cnt = write(fd, &fcc_flen, sizeof(fcc_flen));
if (cnt != sizeof(fcc_flen)) {
errsave = errno;
(void)close(fd);
(void)unlink(data->filename);
ret = (cnt == -1) ? interpret_errno(context, errsave) : KRB5_CC_IO;
goto err_out;
}
}
if (close(fd) == -1) {
errsave = errno;
(void)unlink(data->filename);
ret = interpret_errno(context, errsave);
goto err_out;
}
k5_cc_mutex_assert_locked(context, &data->lock);
k5_cc_mutex_unlock(context, &data->lock);
lid = malloc(sizeof(*lid));
if (lid == NULL) {
free_fccdata(context, data);
return KRB5_CC_NOMEM;
}
lid->ops = &krb5_fcc_ops;
lid->data = data;
lid->magic = KV5M_CCACHE;
*id = lid;
krb5_change_cache();
return 0;
err_out:
(void)set_errmsg_filename(context, ret, data->filename);
k5_cc_mutex_unlock(context, &data->lock);
k5_cc_mutex_destroy(&data->lock);
free(data->filename);
free(data);
return ret;
}
static krb5_error_code KRB5_CALLCONV
fcc_generate_new(krb5_context context, krb5_ccache *id)
{
char scratch[sizeof(TKT_ROOT) + 7];
(void)snprintf(scratch, sizeof(scratch), "%sXXXXXX", TKT_ROOT);
return krb5int_fcc_new_unique(context, scratch, id);
}
static const char * KRB5_CALLCONV
fcc_get_name(krb5_context context, krb5_ccache id)
{
return ((fcc_data *)id->data)->filename;
}
static krb5_error_code KRB5_CALLCONV
fcc_get_principal(krb5_context context, krb5_ccache id, krb5_principal *princ)
{
krb5_error_code ret;
fcc_data *data = id->data;
FILE *fp = NULL;
int version;
k5_cc_mutex_lock(context, &data->lock);
ret = open_cache_file(context, data->filename, FALSE, &fp);
if (ret)
goto cleanup;
ret = read_header(context, fp, &version);
if (ret)
goto cleanup;
ret = read_principal(context, fp, version, princ);
cleanup:
(void)close_cache_file(context, fp);
k5_cc_mutex_unlock(context, &data->lock);
return set_errmsg_filename(context, ret, data->filename);
}
static krb5_error_code KRB5_CALLCONV
fcc_retrieve(krb5_context context, krb5_ccache id, krb5_flags whichfields,
krb5_creds *mcreds, krb5_creds *creds)
{
krb5_error_code ret;
ret = k5_cc_retrieve_cred_default(context, id, whichfields, mcreds, creds);
return set_errmsg_filename(context, ret, ((fcc_data *)id->data)->filename);
}
static krb5_error_code KRB5_CALLCONV
fcc_store(krb5_context context, krb5_ccache id, krb5_creds *creds)
{
krb5_error_code ret, ret2;
fcc_data *data = id->data;
FILE *fp = NULL;
int version;
struct k5buf buf = EMPTY_K5BUF;
ssize_t nwritten;
k5_cc_mutex_lock(context, &data->lock);
ret = open_cache_file(context, data->filename, TRUE, &fp);
if (ret)
goto cleanup;
ret = read_header(context, fp, &version);
if (ret)
goto cleanup;
k5_buf_init_dynamic_zap(&buf);
k5_marshal_cred(&buf, version, creds);
ret = k5_buf_status(&buf);
if (ret)
goto cleanup;
nwritten = write(fileno(fp), buf.data, buf.len);
if (nwritten == -1)
ret = interpret_errno(context, errno);
if ((size_t)nwritten != buf.len)
ret = KRB5_CC_IO;
krb5_change_cache();
cleanup:
k5_buf_free(&buf);
ret2 = close_cache_file(context, fp);
k5_cc_mutex_unlock(context, &data->lock);
return set_errmsg_filename(context, ret ? ret : ret2, data->filename);
}
static krb5_error_code
delete_cred(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor,
krb5_creds *cred)
{
krb5_error_code ret;
krb5_fcc_cursor *fcursor = *cursor;
fcc_data *data = cache->data;
struct k5buf expected = EMPTY_K5BUF, overwrite = EMPTY_K5BUF;
int fd = -1;
uint8_t *on_disk = NULL;
ssize_t rwret;
off_t start_offset;
k5_buf_init_dynamic_zap(&expected);
k5_buf_init_dynamic_zap(&overwrite);
k5_marshal_cred(&expected, fcursor->version, cred);
ret = k5_buf_status(&expected);
if (ret)
goto cleanup;
cred->times.endtime = 0;
cred->times.authtime = -1;
if (data_eq_string(cred->server->realm, "X-CACHECONF:"))
memcpy(cred->server->realm.data, "X-RMED-CONF:", 12);
k5_marshal_cred(&overwrite, fcursor->version, cred);
ret = k5_buf_status(&overwrite);
if (ret)
goto cleanup;
if (expected.len != overwrite.len) {
ret = KRB5_CC_FORMAT;
goto cleanup;
}
fd = open(data->filename, O_RDWR | O_BINARY | O_CLOEXEC);
if (fd == -1) {
ret = interpret_errno(context, errno);
goto cleanup;
}
start_offset = ftell(fcursor->fp);
if (start_offset == -1) {
ret = interpret_errno(context, errno);
goto cleanup;
}
start_offset -= expected.len;
if (lseek(fd, start_offset, SEEK_SET) == -1) {
ret = interpret_errno(context, errno);
goto cleanup;
}
on_disk = k5alloc(expected.len, &ret);
if (ret != 0)
goto cleanup;
rwret = read(fd, on_disk, expected.len);
if (rwret < 0) {
ret = interpret_errno(context, errno);
goto cleanup;
} else if ((size_t)rwret != expected.len) {
ret = KRB5_CC_FORMAT;
goto cleanup;
}
if (memcmp(on_disk, expected.data, expected.len) != 0)
goto cleanup;
if (lseek(fd, start_offset, SEEK_SET) == -1) {
ret = interpret_errno(context, errno);
goto cleanup;
}
rwret = write(fd, overwrite.data, overwrite.len);
if (rwret < 0) {
ret = interpret_errno(context, errno);
goto cleanup;
}
cleanup:
if (fd >= 0)
close(fd);
zapfree(on_disk, expected.len);
k5_buf_free(&expected);
k5_buf_free(&overwrite);
return ret;
}
static krb5_error_code KRB5_CALLCONV
fcc_remove_cred(krb5_context context, krb5_ccache cache, krb5_flags flags,
krb5_creds *creds)
{
krb5_error_code ret;
krb5_cc_cursor cursor;
krb5_creds cur;
ret = krb5_cc_start_seq_get(context, cache, &cursor);
if (ret)
return ret;
for (;;) {
ret = krb5_cc_next_cred(context, cache, &cursor, &cur);
if (ret)
break;
if (krb5int_cc_creds_match_request(context, flags, creds, &cur))
ret = delete_cred(context, cache, &cursor, &cur);
krb5_free_cred_contents(context, &cur);
if (ret)
break;
}
krb5_cc_end_seq_get(context, cache, &cursor);
return (ret == KRB5_CC_END) ? 0 : ret;
}
static krb5_error_code KRB5_CALLCONV
fcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags)
{
return 0;
}
static krb5_error_code KRB5_CALLCONV
fcc_get_flags(krb5_context context, krb5_ccache id, krb5_flags *flags)
{
*flags = 0;
return 0;
}
static krb5_error_code KRB5_CALLCONV
fcc_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor)
{
krb5_cc_ptcursor n = NULL;
struct krb5_fcc_ptcursor_data *cdata = NULL;
*cursor = NULL;
n = malloc(sizeof(*n));
if (n == NULL)
return ENOMEM;
n->ops = &krb5_fcc_ops;
cdata = malloc(sizeof(*cdata));
if (cdata == NULL) {
free(n);
return ENOMEM;
}
cdata->first = TRUE;
n->data = cdata;
*cursor = n;
return 0;
}
static krb5_error_code KRB5_CALLCONV
fcc_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor,
krb5_ccache *cache_out)
{
krb5_error_code ret;
struct krb5_fcc_ptcursor_data *cdata = cursor->data;
const char *defname, *residual;
krb5_ccache cache;
struct stat sb;
*cache_out = NULL;
if (!cdata->first)
return 0;
cdata->first = FALSE;
defname = krb5_cc_default_name(context);
if (!defname)
return 0;
if (strncmp(defname, "FILE:", 5) == 0)
residual = defname + 5;
else if (strchr(defname + 2, ':') == NULL)
residual = defname;
else
return 0;
if (stat(residual, &sb) != 0)
return 0;
ret = krb5_cc_resolve(context, defname, &cache);
if (ret)
return set_errmsg_filename(context, ret, defname);
*cache_out = cache;
return 0;
}
static krb5_error_code KRB5_CALLCONV
fcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor)
{
if (*cursor == NULL)
return 0;
free((*cursor)->data);
free(*cursor);
*cursor = NULL;
return 0;
}
static krb5_error_code KRB5_CALLCONV
fcc_lock(krb5_context context, krb5_ccache id)
{
fcc_data *data = id->data;
k5_cc_mutex_lock(context, &data->lock);
return 0;
}
static krb5_error_code KRB5_CALLCONV
fcc_unlock(krb5_context context, krb5_ccache id)
{
fcc_data *data = id->data;
k5_cc_mutex_unlock(context, &data->lock);
return 0;
}
static krb5_error_code KRB5_CALLCONV
fcc_replace(krb5_context context, krb5_ccache id, krb5_principal princ,
krb5_creds **creds)
{
krb5_error_code ret;
fcc_data *data = id->data;
char *tmpname = NULL;
int i, st, fd = -1, version = context->fcc_default_format - FVNO_BASE;
ssize_t nwritten;
struct k5buf buf = EMPTY_K5BUF;
krb5_boolean tmpfile_exists = FALSE;
if (asprintf(&tmpname, "%s.XXXXXX", data->filename) < 0)
return ENOMEM;
fd = mkstemp(tmpname);
if (fd < 0)
goto errno_cleanup;
tmpfile_exists = TRUE;
k5_buf_init_dynamic_zap(&buf);
marshal_header(context, &buf, princ);
for (i = 0; creds[i] != NULL; i++)
k5_marshal_cred(&buf, version, creds[i]);
ret = k5_buf_status(&buf);
if (ret)
goto cleanup;
nwritten = write(fd, buf.data, buf.len);
if (nwritten == -1)
goto errno_cleanup;
if ((size_t)nwritten != buf.len) {
ret = KRB5_CC_IO;
goto cleanup;
}
st = close(fd);
fd = -1;
if (st != 0)
goto errno_cleanup;
st = rename(tmpname, data->filename);
if (st != 0)
goto errno_cleanup;
tmpfile_exists = FALSE;
cleanup:
k5_buf_free(&buf);
if (fd != -1)
close(fd);
if (tmpfile_exists)
unlink(tmpname);
free(tmpname);
return ret;
errno_cleanup:
ret = interpret_errno(context, errno);
goto cleanup;
}
static krb5_error_code
interpret_errno(krb5_context context, int errnum)
{
krb5_error_code ret;
switch (errnum) {
case ENOENT:
case ENOTDIR:
#ifdef ELOOP
case ELOOP:
#endif
#ifdef ENAMETOOLONG
case ENAMETOOLONG:
#endif
ret = KRB5_FCC_NOFILE;
break;
case EPERM:
case EACCES:
#ifdef EISDIR
case EISDIR:
#endif
case EROFS:
ret = KRB5_FCC_PERM;
break;
case EINVAL:
case EEXIST:
case EFAULT:
case EBADF:
#ifdef EWOULDBLOCK
case EWOULDBLOCK:
#endif
ret = KRB5_FCC_INTERNAL;
break;
default:
ret = KRB5_CC_IO;
break;
}
return ret;
}
const krb5_cc_ops krb5_fcc_ops = {
0,
"FILE",
fcc_get_name,
fcc_resolve,
fcc_generate_new,
fcc_initialize,
fcc_destroy,
fcc_close,
fcc_store,
fcc_retrieve,
fcc_get_principal,
fcc_start_seq_get,
fcc_next_cred,
fcc_end_seq_get,
fcc_remove_cred,
fcc_set_flags,
fcc_get_flags,
fcc_ptcursor_new,
fcc_ptcursor_next,
fcc_ptcursor_free,
fcc_replace,
NULL,
fcc_lock,
fcc_unlock,
NULL,
};
#if defined(_WIN32)
krb5_error_code
krb5_change_cache(void)
{
PostMessage(HWND_BROADCAST, krb5_get_notification_message(), 0, 0);
return 0;
}
unsigned int KRB5_CALLCONV
krb5_get_notification_message(void)
{
static unsigned int message = 0;
if (message == 0)
message = RegisterWindowMessage(WM_KERBEROS5_CHANGED);
return message;
}
#else
krb5_error_code
krb5_change_cache(void)
{
return 0;
}
unsigned int
krb5_get_notification_message(void)
{
return 0;
}
#endif
const krb5_cc_ops krb5_cc_file_ops = {
0,
"FILE",
fcc_get_name,
fcc_resolve,
fcc_generate_new,
fcc_initialize,
fcc_destroy,
fcc_close,
fcc_store,
fcc_retrieve,
fcc_get_principal,
fcc_start_seq_get,
fcc_next_cred,
fcc_end_seq_get,
fcc_remove_cred,
fcc_set_flags,
fcc_get_flags,
fcc_ptcursor_new,
fcc_ptcursor_next,
fcc_ptcursor_free,
fcc_replace,
NULL,
fcc_lock,
fcc_unlock,
NULL,
};