/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <k5-int.h>
#include <stdlib.h>
#include <limits.h>
#include <syslog.h>
#include "kdb_log.h"
/*
* This modules includes all the necessary functions that create and
* modify the Kerberos principal update and header logs.
*/
#define getpagesize() sysconf(_SC_PAGESIZE)
static int pagesize = 0;
#define INIT_ULOG(ctx) log_ctx = ctx->kdblog_context; \
ulog = log_ctx->ulog
/*
* Sync update entry to disk.
*/
krb5_error_code
ulog_sync_update(kdb_hlog_t *ulog, kdb_ent_header_t *upd)
{
ulong_t start, end, size;
krb5_error_code retval;
if (ulog == NULL)
return (KRB5_LOG_ERROR);
if (!pagesize)
pagesize = getpagesize();
start = ((ulong_t)upd) & (~(pagesize-1));
end = (((ulong_t)upd) + ulog->kdb_block +
(pagesize-1)) & (~(pagesize-1));
size = end - start;
if (retval = msync((caddr_t)start, size, MS_SYNC)) {
return (retval);
}
return (0);
}
/*
* Sync memory to disk for the update log header.
*/
void
ulog_sync_header(kdb_hlog_t *ulog)
{
if (!pagesize)
pagesize = getpagesize();
if (msync((caddr_t)ulog, pagesize, MS_SYNC)) {
/*
* Couldn't sync to disk, let's panic
*/
syslog(LOG_ERR, "ulog_sync_header: could not sync to disk");
abort();
}
}
/*
* Resizes the array elements. We reinitialize the update log rather than
* unrolling the the log and copying it over to a temporary log for obvious
* performance reasons. Slaves will subsequently do a full resync, but
* the need for resizing should be very small.
*/
krb5_error_code
ulog_resize(kdb_hlog_t *ulog, uint32_t ulogentries, int ulogfd, uint_t recsize)
{
uint_t new_block, new_size;
if (ulog == NULL)
return (KRB5_LOG_ERROR);
new_size = sizeof (kdb_hlog_t);
new_block = (recsize / ULOG_BLOCK) + 1;
new_block *= ULOG_BLOCK;
new_size += ulogentries * new_block;
if (new_size <= MAXLOGLEN) {
/*
* Reinit log with new block size
*/
(void) memset(ulog, 0, sizeof (kdb_hlog_t));
ulog->kdb_hmagic = KDB_HMAGIC;
ulog->db_version_num = KDB_VERSION;
ulog->kdb_state = KDB_STABLE;
ulog->kdb_block = new_block;
ulog_sync_header(ulog);
/*
* Time to expand log considering new block size
*/
if (lseek(ulogfd, new_size, SEEK_SET) == -1) {
return (errno);
}
if (write(ulogfd, "+", 1) != 1) {
return (errno);
}
} else {
/*
* Can't map into file larger than MAXLOGLEN
*/
return (KRB5_LOG_ERROR);
}
return (0);
}
/*
* Adds an entry to the update log.
* The layout of the update log looks like:
*
* header log -> [ update header -> xdr(kdb_incr_update_t) ], ...
*/
krb5_error_code
ulog_add_update(krb5_context context, kdb_incr_update_t *upd)
{
XDR xdrs;
kdbe_time_t ktime;
struct timeval timestamp;
kdb_ent_header_t *indx_log;
uint_t i, recsize;
ulong_t upd_size;
krb5_error_code retval;
kdb_sno_t cur_sno;
kdb_log_context *log_ctx;
kdb_hlog_t *ulog = NULL;
uint32_t ulogentries;
int ulogfd;
INIT_ULOG(context);
ulogentries = log_ctx->ulogentries;
ulogfd = log_ctx->ulogfd;
if (upd == NULL)
return (KRB5_LOG_ERROR);
(void) gettimeofday(&timestamp, NULL);
ktime.seconds = timestamp.tv_sec;
ktime.useconds = timestamp.tv_usec;
upd_size = xdr_sizeof((xdrproc_t)xdr_kdb_incr_update_t, upd);
recsize = sizeof (kdb_ent_header_t) + upd_size;
if (recsize > ulog->kdb_block) {
if (retval = ulog_resize(ulog, ulogentries, ulogfd, recsize)) {
/* Resize element array failed */
return (retval);
}
}
cur_sno = ulog->kdb_last_sno;
/*
* We need to overflow our sno, replicas will do full
* resyncs once they see their sno > than the masters.
*/
if (cur_sno == ULONG_MAX)
cur_sno = 1;
else
cur_sno++;
/*
* We squirrel this away for finish_update() to index
*/
upd->kdb_entry_sno = cur_sno;
i = (cur_sno - 1) % ulogentries;
indx_log = (kdb_ent_header_t *)INDEX(ulog, i);
(void) memset(indx_log, 0, ulog->kdb_block);
indx_log->kdb_umagic = KDB_UMAGIC;
indx_log->kdb_entry_size = upd_size;
indx_log->kdb_entry_sno = cur_sno;
indx_log->kdb_time = upd->kdb_time = ktime;
indx_log->kdb_commit = upd->kdb_commit = FALSE;
ulog->kdb_state = KDB_UNSTABLE;
xdrmem_create(&xdrs, (char *)indx_log->entry_data,
indx_log->kdb_entry_size, XDR_ENCODE);
if (!xdr_kdb_incr_update_t(&xdrs, upd))
return (KRB5_LOG_CONV);
if (retval = ulog_sync_update(ulog, indx_log))
return (retval);
if (ulog->kdb_num < ulogentries)
ulog->kdb_num++;
ulog->kdb_last_sno = cur_sno;
ulog->kdb_last_time = ktime;
/*
* Since this is a circular array, once we circled, kdb_first_sno is
* always kdb_entry_sno + 1.
*/
if (cur_sno > ulogentries) {
i = upd->kdb_entry_sno % ulogentries;
indx_log = (kdb_ent_header_t *)INDEX(ulog, i);
ulog->kdb_first_sno = indx_log->kdb_entry_sno;
ulog->kdb_first_time = indx_log->kdb_time;
} else if (cur_sno == 1) {
ulog->kdb_first_sno = 1;
ulog->kdb_first_time = indx_log->kdb_time;
}
ulog_sync_header(ulog);
return (0);
}
/*
* Mark the log entry as committed and sync the memory mapped log
* to file.
*/
krb5_error_code
ulog_finish_update(krb5_context context, kdb_incr_update_t *upd)
{
krb5_error_code retval;
kdb_ent_header_t *indx_log;
uint_t i;
kdb_log_context *log_ctx;
kdb_hlog_t *ulog = NULL;
uint32_t ulogentries;
INIT_ULOG(context);
ulogentries = log_ctx->ulogentries;
i = (upd->kdb_entry_sno - 1) % ulogentries;
indx_log = (kdb_ent_header_t *)INDEX(ulog, i);
indx_log->kdb_commit = TRUE;
ulog->kdb_state = KDB_STABLE;
if (retval = ulog_sync_update(ulog, indx_log))
return (retval);
ulog_sync_header(ulog);
return (0);
}
/*
* Set the header log details on the slave and sync it to file.
*/
void
ulog_finish_update_slave(kdb_hlog_t *ulog, kdb_last_t lastentry)
{
ulog->kdb_last_sno = lastentry.last_sno;
ulog->kdb_last_time = lastentry.last_time;
ulog_sync_header(ulog);
}
/*
* Delete an entry to the update log.
*/
krb5_error_code
ulog_delete_update(krb5_context context, kdb_incr_update_t *upd)
{
upd->kdb_deleted = TRUE;
return (ulog_add_update(context, upd));
}
/*
* Used by the slave or master (during ulog_check) to update it's hash db from
* the incr update log.
*/
krb5_error_code
ulog_replay(krb5_context context, kdb_incr_result_t *incr_ret)
{
krb5_db_entry *entry = NULL;
kdb_incr_update_t *upd = NULL, *fupd;
int i, no_of_updates;
krb5_error_code retval;
krb5_principal dbprinc = NULL;
kdb_last_t errlast;
char *dbprincstr = NULL;
kdb_log_context *log_ctx;
kdb_hlog_t *ulog = NULL;
bool_t fini = FALSE;
INIT_ULOG(context);
no_of_updates = incr_ret->updates.kdb_ulog_t_len;
upd = incr_ret->updates.kdb_ulog_t_val;
fupd = upd;
/*
* We reset last_sno and last_time to 0, if krb5_db_put_principal
* or krb5_db_delete_principal fail.
*/
errlast.last_sno = (unsigned int)0;
errlast.last_time.seconds = (unsigned int)0;
errlast.last_time.useconds = (unsigned int)0;
if (krb5_db_inited(context)) {
retval = krb5_db_open(context, NULL,
KRB5_KDB_OPEN_RW | KRB5_KDB_SRV_TYPE_ADMIN);
if (retval != 0)
goto cleanup;
fini = TRUE;
}
for (i = 0; i < no_of_updates; i++) {
int nentry = 1;
if (!upd->kdb_commit)
continue;
if (upd->kdb_deleted) {
dbprincstr = malloc((upd->kdb_princ_name.utf8str_t_len
+ 1) * sizeof (char));
if (dbprincstr == NULL) {
retval = ENOMEM;
goto cleanup;
}
(void) strlcpy(dbprincstr,
(char *)upd->kdb_princ_name.utf8str_t_val,
(upd->kdb_princ_name.utf8str_t_len + 1));
if (retval = krb5_parse_name(context, dbprincstr,
&dbprinc)) {
goto cleanup;
}
if (dbprincstr)
free(dbprincstr);
retval = krb5_db_delete_principal(context,
dbprinc, &nentry);
if (dbprinc)
krb5_free_principal(context, dbprinc);
if (retval)
goto cleanup;
} else {
entry = (krb5_db_entry *)malloc(sizeof (krb5_db_entry));
if (!entry) {
retval = errno;
goto cleanup;
}
(void) memset(entry, 0, sizeof (krb5_db_entry));
if (retval = ulog_conv_2dbentry(context, entry, upd, 1))
goto cleanup;
retval = krb5_db_put_principal(context, entry,
&nentry);
if (entry) {
krb5_db_free_principal(context, entry, nentry);
free(entry);
entry = NULL;
}
if (retval)
goto cleanup;
}
upd++;
}
cleanup:
if (fupd)
ulog_free_entries(fupd, no_of_updates);
if (log_ctx && (log_ctx->iproprole == IPROP_SLAVE)) {
if (retval)
ulog_finish_update_slave(ulog, errlast);
else
ulog_finish_update_slave(ulog, incr_ret->lastentry);
}
if (fini == TRUE)
krb5_db_fini(context);
return (retval);
}
/*
* Validate the log file and resync any uncommitted update entries
* to the principal database.
*/
krb5_error_code
ulog_check(krb5_context context, kdb_hlog_t *ulog)
{
XDR xdrs;
krb5_error_code retval = 0;
int i;
kdb_ent_header_t *indx_log;
kdb_incr_update_t *upd = NULL;
kdb_incr_result_t *incr_ret = NULL;
ulog->kdb_state = KDB_STABLE;
for (i = 0; i < ulog->kdb_num; i++) {
indx_log = (kdb_ent_header_t *)INDEX(ulog, i);
if (indx_log->kdb_umagic != KDB_UMAGIC) {
/*
* Update entry corrupted we should scream and die
*/
ulog->kdb_state = KDB_CORRUPT;
retval = KRB5_LOG_CORRUPT;
break;
}
if (indx_log->kdb_commit == FALSE) {
ulog->kdb_state = KDB_UNSTABLE;
incr_ret = (kdb_incr_result_t *)
malloc(sizeof (kdb_incr_result_t));
if (incr_ret == NULL) {
retval = errno;
goto error;
}
upd = (kdb_incr_update_t *)
malloc(sizeof (kdb_incr_update_t));
if (upd == NULL) {
retval = errno;
goto error;
}
(void) memset(upd, 0, sizeof (kdb_incr_update_t));
xdrmem_create(&xdrs, (char *)indx_log->entry_data,
indx_log->kdb_entry_size, XDR_DECODE);
if (!xdr_kdb_incr_update_t(&xdrs, upd)) {
retval = KRB5_LOG_CONV;
goto error;
}
incr_ret->updates.kdb_ulog_t_len = 1;
incr_ret->updates.kdb_ulog_t_val = upd;
upd->kdb_commit = TRUE;
/*
* We don't want to readd this update and just use the
* existing update to be propagated later on
*/
ulog_set_role(context, IPROP_NULL);
retval = ulog_replay(context, incr_ret);
/*
* upd was freed by ulog_replay, we NULL
* the pointer in case we subsequently break from loop.
*/
upd = NULL;
if (incr_ret) {
free(incr_ret);
incr_ret = NULL;
}
ulog_set_role(context, IPROP_MASTER);
if (retval)
goto error;
/*
* We flag this as committed since this was
* the last entry before kadmind crashed, ergo
* the slaves have not seen this update before
*/
indx_log->kdb_commit = TRUE;
retval = ulog_sync_update(ulog, indx_log);
if (retval)
goto error;
ulog->kdb_state = KDB_STABLE;
}
}
error:
if (upd)
ulog_free_entries(upd, 1);
if (incr_ret)
free(incr_ret);
ulog_sync_header(ulog);
return (retval);
}
/*
* Map the log file to memory for performance and simplicity.
*
* Called by: if iprop_enabled then ulog_map();
* Assumes that the caller will terminate on ulog_map, hence munmap and
* closing of the fd are implicitly performed by the caller.
* Returns 0 on success else failure.
*/
krb5_error_code
ulog_map(krb5_context context, kadm5_config_params *params, int caller)
{
struct stat st;
krb5_error_code retval;
uint32_t ulog_filesize;
char logname[MAX_FILENAME];
kdb_log_context *log_ctx;
kdb_hlog_t *ulog = NULL;
uint32_t ulogentries;
int ulogfd = -1;
if ((caller == FKADMIND) || (caller == FKCOMMAND))
ulogentries = params->iprop_ulogsize;
ulog_filesize = sizeof (kdb_hlog_t);
if (strlcpy(logname, params->dbname, MAX_FILENAME) >= MAX_FILENAME)
return (KRB5_LOG_ERROR);
if (strlcat(logname, ".ulog", MAX_FILENAME) >= MAX_FILENAME)
return (KRB5_LOG_ERROR);
if (stat(logname, &st) == -1) {
if (caller == FKPROPLOG) {
/*
* File doesn't exist so we exit with kproplog
*/
return (errno);
}
if ((ulogfd = open(logname, O_RDWR+O_CREAT, 0600)) == -1) {
return (errno);
}
if (lseek(ulogfd, 0L, SEEK_CUR) == -1) {
return (errno);
}
if ((caller == FKADMIND) || (caller == FKCOMMAND))
ulog_filesize += ulogentries * ULOG_BLOCK;
if (lseek(ulogfd, ulog_filesize, SEEK_SET) == -1) {
return (errno);
}
if (write(ulogfd, "+", 1) != 1) {
return (errno);
}
} else {
if ((ulogfd = open(logname, O_RDWR, 0600)) == -1) {
/*
* Can't open existing log file
*/
return (errno);
}
}
if (caller == FKPROPLOG) {
fstat(ulogfd, &st);
ulog_filesize = st.st_size;
ulog = (kdb_hlog_t *)mmap(0, ulog_filesize,
PROT_READ+PROT_WRITE, MAP_PRIVATE, ulogfd, 0);
} else {
/*
* else kadmind, kpropd, & kcommands should udpate stores
*/
ulog = (kdb_hlog_t *)mmap(0, MAXLOGLEN,
PROT_READ+PROT_WRITE, MAP_SHARED, ulogfd, 0);
}
if ((int)(ulog) == -1) {
/*
* Can't map update log file to memory
*/
return (errno);
}
if (!context->kdblog_context) {
if (!(log_ctx = malloc(sizeof (kdb_log_context))))
return (errno);
context->kdblog_context = (void *)log_ctx;
} else
log_ctx = context->kdblog_context;
log_ctx->ulog = ulog;
log_ctx->ulogentries = ulogentries;
log_ctx->ulogfd = ulogfd;
if (ulog->kdb_hmagic != KDB_HMAGIC) {
if (ulog->kdb_hmagic == 0) {
/*
* New update log
*/
(void) memset(ulog, 0, sizeof (kdb_hlog_t));
ulog->kdb_hmagic = KDB_HMAGIC;
ulog->db_version_num = KDB_VERSION;
ulog->kdb_state = KDB_STABLE;
ulog->kdb_block = ULOG_BLOCK;
if (!(caller == FKPROPLOG))
ulog_sync_header(ulog);
} else {
return (KRB5_LOG_CORRUPT);
}
}
if (caller == FKADMIND) {
switch (ulog->kdb_state) {
case KDB_STABLE:
case KDB_UNSTABLE:
/*
* Log is currently un/stable, check anyway
*/
retval = ulog_check(context, ulog);
if (retval == KRB5_LOG_CORRUPT) {
return (retval);
}
break;
case KDB_CORRUPT:
return (KRB5_LOG_CORRUPT);
default:
/*
* Invalid db state
*/
return (KRB5_LOG_ERROR);
}
} else if ((caller == FKPROPLOG) || (caller == FKPROPD)) {
/*
* kproplog and kpropd don't need to do anything else
*/
return (0);
}
/*
* Reinit ulog if the log is being truncated or expanded after
* we have circled.
*/
if (ulog->kdb_num != ulogentries) {
if ((ulog->kdb_num != 0) &&
((ulog->kdb_last_sno > ulog->kdb_num) ||
(ulog->kdb_num > ulogentries))) {
(void) memset(ulog, 0, sizeof (kdb_hlog_t));
ulog->kdb_hmagic = KDB_HMAGIC;
ulog->db_version_num = KDB_VERSION;
ulog->kdb_state = KDB_STABLE;
ulog->kdb_block = ULOG_BLOCK;
ulog_sync_header(ulog);
}
/*
* Expand ulog if we have specified a greater size
*/
if (ulog->kdb_num < ulogentries) {
ulog_filesize += ulogentries * ulog->kdb_block;
if (lseek(ulogfd, ulog_filesize, SEEK_SET) == -1) {
return (errno);
}
if (write(ulogfd, "+", 1) != 1) {
return (errno);
}
}
}
return (0);
}
/*
* Get the last set of updates seen, (last+1) to n is returned.
*/
krb5_error_code
ulog_get_entries(
krb5_context context, /* input - krb5 lib config */
kdb_last_t last, /* input - slave's last sno */
kdb_incr_result_t *ulog_handle) /* output - incr result for slave */
{
XDR xdrs;
kdb_ent_header_t *indx_log;
kdb_incr_update_t *upd;
uint_t indx, count, tdiff;
uint32_t sno;
krb5_error_code retval;
struct timeval timestamp;
kdb_log_context *log_ctx;
kdb_hlog_t *ulog = NULL;
uint32_t ulogentries;
INIT_ULOG(context);
ulogentries = log_ctx->ulogentries;
/*
* Check to make sure we don't have a corrupt ulog first.
*/
if (ulog->kdb_state == KDB_CORRUPT) {
ulog_handle->ret = UPDATE_ERROR;
return (KRB5_LOG_CORRUPT);
}
gettimeofday(&timestamp, NULL);
tdiff = timestamp.tv_sec - ulog->kdb_last_time.seconds;
if (tdiff <= ULOG_IDLE_TIME) {
ulog_handle->ret = UPDATE_BUSY;
return (0);
}
/*
* We need to lock out other processes here, such as kadmin.local,
* since we are looking at the last_sno and looking up updates. So
* we can share with other readers.
*/
retval = krb5_db_lock(context, KRB5_LOCKMODE_SHARED);
if (retval)
return (retval);
/*
* We may have overflowed the update log or we shrunk the log, or
* the client's ulog has just been created.
*/
if ((last.last_sno > ulog->kdb_last_sno) ||
(last.last_sno < ulog->kdb_first_sno) ||
(last.last_sno == 0)) {
ulog_handle->lastentry.last_sno = ulog->kdb_last_sno;
(void) krb5_db_unlock(context);
ulog_handle->ret = UPDATE_FULL_RESYNC_NEEDED;
return (0);
} else if (last.last_sno <= ulog->kdb_last_sno) {
sno = last.last_sno;
indx = (sno - 1) % ulogentries;
indx_log = (kdb_ent_header_t *)INDEX(ulog, indx);
/*
* Validate the time stamp just to make sure it was the same sno
*/
if ((indx_log->kdb_time.seconds == last.last_time.seconds) &&
(indx_log->kdb_time.useconds == last.last_time.useconds)) {
/*
* If we have the same sno we return success
*/
if (last.last_sno == ulog->kdb_last_sno) {
(void) krb5_db_unlock(context);
ulog_handle->ret = UPDATE_NIL;
return (0);
}
count = ulog->kdb_last_sno - sno;
ulog_handle->updates.kdb_ulog_t_val =
(kdb_incr_update_t *)malloc(
sizeof (kdb_incr_update_t) * count);
upd = ulog_handle->updates.kdb_ulog_t_val;
if (upd == NULL) {
(void) krb5_db_unlock(context);
ulog_handle->ret = UPDATE_ERROR;
return (errno);
}
while (sno < ulog->kdb_last_sno) {
indx = sno % ulogentries;
indx_log = (kdb_ent_header_t *)
INDEX(ulog, indx);
(void) memset(upd, 0,
sizeof (kdb_incr_update_t));
xdrmem_create(&xdrs,
(char *)indx_log->entry_data,
indx_log->kdb_entry_size, XDR_DECODE);
if (!xdr_kdb_incr_update_t(&xdrs, upd)) {
(void) krb5_db_unlock(context);
ulog_handle->ret = UPDATE_ERROR;
return (KRB5_LOG_CONV);
}
/*
* Mark commitment since we didn't
* want to decode and encode the
* incr update record the first time.
*/
upd->kdb_commit = indx_log->kdb_commit;
upd++;
sno++;
} /* while */
ulog_handle->updates.kdb_ulog_t_len = count;
ulog_handle->lastentry.last_sno = ulog->kdb_last_sno;
ulog_handle->lastentry.last_time.seconds =
ulog->kdb_last_time.seconds;
ulog_handle->lastentry.last_time.useconds =
ulog->kdb_last_time.useconds;
ulog_handle->ret = UPDATE_OK;
(void) krb5_db_unlock(context);
return (0);
} else {
/*
* We have time stamp mismatch or we no longer have
* the slave's last sno, so we brute force it
*/
(void) krb5_db_unlock(context);
ulog_handle->ret = UPDATE_FULL_RESYNC_NEEDED;
return (0);
}
}
/*
* Should never get here, return error
*/
ulog_handle->ret = UPDATE_ERROR;
return (KRB5_LOG_ERROR);
}
krb5_error_code
ulog_set_role(krb5_context ctx, iprop_role role)
{
kdb_log_context *log_ctx;
if (!ctx->kdblog_context) {
if (!(log_ctx = malloc(sizeof (kdb_log_context))))
return (errno);
ctx->kdblog_context = (void *)log_ctx;
} else
log_ctx = ctx->kdblog_context;
log_ctx->iproprole = role;
return (0);
}