#include "k5-int.h"
#include "k5-hashtab.h"
#include "rc-int.h"
#ifndef _WIN32
#include <sys/types.h>
#include <sys/stat.h>
#endif
#define MAX_SIZE INT32_MAX
#define TAG_LEN 12
#define RECORD_LEN (TAG_LEN + 4)
#define FIRST_TABLE_RECORDS 1023
static inline krb5_error_code
next_table(off_t *offset, off_t *nrecords)
{
if (*offset == -1) {
*offset = K5_HASH_SEED_LEN;
*nrecords = FIRST_TABLE_RECORDS;
} else if (*offset == K5_HASH_SEED_LEN) {
*offset += *nrecords * RECORD_LEN;
*nrecords = (FIRST_TABLE_RECORDS + 1) * 2;
} else {
*offset += *nrecords * RECORD_LEN;
*nrecords *= 2;
}
if (*nrecords > MAX_SIZE / RECORD_LEN)
return EOVERFLOW;
if (*offset > MAX_SIZE - (*nrecords * RECORD_LEN))
return EOVERFLOW;
return 0;
}
static krb5_error_code
read_records(int fd, off_t offset, uint8_t tag1_out[TAG_LEN],
uint32_t *timestamp1_out, uint8_t tag2_out[TAG_LEN],
uint32_t *timestamp2_out, int *nread)
{
uint8_t buf[RECORD_LEN * 2];
ssize_t st;
*nread = 0;
st = lseek(fd, offset, SEEK_SET);
if (st == -1)
return errno;
st = read(fd, buf, RECORD_LEN * 2);
if (st == -1)
return errno;
if (st >= RECORD_LEN) {
memcpy(tag1_out, buf, TAG_LEN);
*timestamp1_out = load_32_be(buf + TAG_LEN);
*nread = 1;
}
if (st == RECORD_LEN * 2) {
memcpy(tag2_out, buf + RECORD_LEN, TAG_LEN);
*timestamp2_out = load_32_be(buf + RECORD_LEN + TAG_LEN);
*nread = 2;
}
return 0;
}
static krb5_error_code
write_record(int fd, off_t offset, const uint8_t tag[TAG_LEN],
uint32_t timestamp)
{
uint8_t record[RECORD_LEN];
ssize_t st;
memcpy(record, tag, TAG_LEN);
store_32_be(timestamp, record + TAG_LEN);
st = lseek(fd, offset, SEEK_SET);
if (st == -1)
return errno;
st = write(fd, record, RECORD_LEN);
if (st == -1)
return errno;
if (st != RECORD_LEN)
return EIO;
return 0;
}
static inline krb5_boolean
expired(uint32_t timestamp, uint32_t now, uint32_t skew)
{
return ts_after(now, ts_incr(timestamp, skew));
}
static krb5_error_code
store(krb5_context context, int fd, const uint8_t tag[TAG_LEN], uint32_t now,
uint32_t skew)
{
krb5_error_code ret;
krb5_data d;
off_t table_offset = -1, nrecords = 0, avail_offset = -1, record_offset;
ssize_t st;
int ind, nread;
uint8_t seed[K5_HASH_SEED_LEN], r1tag[TAG_LEN], r2tag[TAG_LEN];
uint32_t r1stamp, r2stamp;
st = read(fd, seed, sizeof(seed));
if (st < 0)
return errno;
if ((size_t)st < sizeof(seed)) {
d = make_data(seed, sizeof(seed));
ret = krb5_c_random_make_octets(context, &d);
if (ret)
return ret;
st = write(fd, seed, sizeof(seed));
if (st < 0)
return errno;
if ((size_t)st != sizeof(seed))
return EIO;
}
for (;;) {
ret = next_table(&table_offset, &nrecords);
if (ret)
return ret;
ind = k5_siphash24(tag, TAG_LEN, seed) % nrecords;
record_offset = table_offset + ind * RECORD_LEN;
ret = read_records(fd, record_offset, r1tag, &r1stamp, r2tag, &r2stamp,
&nread);
if (ret)
return ret;
if ((nread >= 1 && r1stamp && memcmp(r1tag, tag, TAG_LEN) == 0) ||
(nread == 2 && r2stamp && memcmp(r2tag, tag, TAG_LEN) == 0))
return KRB5KRB_AP_ERR_REPEAT;
if (avail_offset == -1) {
if (nread == 0 || !r1stamp || expired(r1stamp, now, skew))
avail_offset = record_offset;
else if (nread == 1 || !r2stamp || expired(r2stamp, now, skew))
avail_offset = record_offset + RECORD_LEN;
}
if (nread < 2 || !r1stamp || !r2stamp)
return write_record(fd, avail_offset, tag, now);
seed[0]++;
}
}
krb5_error_code
k5_rcfile2_store(krb5_context context, int fd, const krb5_data *tag_data)
{
krb5_error_code ret;
krb5_timestamp now;
uint8_t tagbuf[TAG_LEN], *tag;
ret = krb5_timeofday(context, &now);
if (ret)
return ret;
if (tag_data->length >= TAG_LEN) {
tag = (uint8_t *)tag_data->data;
} else {
memcpy(tagbuf, tag_data->data, tag_data->length);
memset(tagbuf + tag_data->length, 0, TAG_LEN - tag_data->length);
tag = tagbuf;
}
ret = krb5_lock_file(context, fd, KRB5_LOCKMODE_EXCLUSIVE);
if (ret)
return ret;
ret = store(context, fd, tag, now, context->clockskew);
(void)krb5_unlock_file(NULL, fd);
return ret;
}
static krb5_error_code
file2_resolve(krb5_context context, const char *residual, void **rcdata_out)
{
*rcdata_out = strdup(residual);
return (*rcdata_out == NULL) ? ENOMEM : 0;
}
static void
file2_close(krb5_context context, void *rcdata)
{
free(rcdata);
}
static krb5_error_code
file2_store(krb5_context context, void *rcdata, const krb5_data *tag)
{
krb5_error_code ret;
const char *filename = rcdata;
int fd;
fd = open(filename, O_CREAT | O_RDWR | O_BINARY, 0600);
if (fd < 0) {
ret = errno;
k5_setmsg(context, ret, "%s (filename: %s)", error_message(ret),
filename);
return ret;
}
ret = k5_rcfile2_store(context, fd, tag);
close(fd);
return ret;
}
const krb5_rc_ops k5_rc_file2_ops =
{
"file2",
file2_resolve,
file2_close,
file2_store
};