Path: blob/main/crypto/krb5/src/lib/kdb/kdb_log.c
105671 views
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */1/*2* Copyright 2004 Sun Microsystems, Inc. All rights reserved.3* Use is subject to license terms.4*/56#include <sys/stat.h>7#include <sys/types.h>8#include <unistd.h>9#include <fcntl.h>10#include <sys/mman.h>11#include <k5-int.h>12#include <stdlib.h>13#include <limits.h>14#include <syslog.h>15#include "kdb5.h"16#include "kdb_log.h"17#include "kdb5int.h"1819#ifndef MAP_FAILED20#define MAP_FAILED ((void *)-1)21#endif2223/* This module includes all the necessary functions that create and modify the24* Kerberos principal update and header logs. */2526#define getpagesize() sysconf(_SC_PAGESIZE)2728static int pagesize = 0;2930#define INIT_ULOG(ctx) \31log_ctx = ctx->kdblog_context; \32assert(log_ctx != NULL); \33ulog = log_ctx->ulog; \34assert(ulog != NULL)3536/* Initialize context->kdblog_context if it does not yet exist, and return it.37* Return NULL on allocation failure. */38static kdb_log_context *39create_log_context(krb5_context context)40{41kdb_log_context *log_ctx;4243if (context->kdblog_context != NULL)44return context->kdblog_context;45log_ctx = calloc(1, sizeof(*log_ctx));46if (log_ctx == NULL)47return NULL;48log_ctx->ulogfd = -1;49context->kdblog_context = log_ctx;50return log_ctx;51}5253static inline krb5_boolean54time_equal(const kdbe_time_t *a, const kdbe_time_t *b)55{56return a->seconds == b->seconds && a->useconds == b->useconds;57}5859static void60time_current(kdbe_time_t *out)61{62struct timeval timestamp;6364(void)gettimeofday(×tamp, NULL);65out->seconds = timestamp.tv_sec;66out->useconds = timestamp.tv_usec;67}6869/* Sync update entry to disk. */70static void71sync_update(kdb_hlog_t *ulog, kdb_ent_header_t *upd)72{73unsigned long start, end, size;7475if (!pagesize)76pagesize = getpagesize();7778start = (unsigned long)upd & ~(pagesize - 1);7980end = ((unsigned long)upd + ulog->kdb_block + (pagesize - 1)) &81~(pagesize - 1);8283size = end - start;84if (msync((caddr_t)start, size, MS_SYNC)) {85/* Couldn't sync to disk, let's panic. */86syslog(LOG_ERR, _("could not sync ulog update to disk"));87abort();88}89}9091/* Sync memory to disk for the update log header. */92static void93sync_header(kdb_hlog_t *ulog)94{95if (!pagesize)96pagesize = getpagesize();9798if (msync((caddr_t)ulog, pagesize, MS_SYNC)) {99/* Couldn't sync to disk, let's panic. */100syslog(LOG_ERR, _("could not sync ulog header to disk"));101abort();102}103}104105/* Sync memory to disk for the entire ulog. */106static void107sync_ulog(kdb_hlog_t *ulog, uint32_t ulogentries)108{109size_t len;110111if (!pagesize)112pagesize = getpagesize();113114len = (sizeof(kdb_hlog_t) + ulogentries * ulog->kdb_block +115(pagesize - 1)) & ~(pagesize - 1);116if (msync(ulog, len, MS_SYNC)) {117/* Couldn't sync to disk, let's panic. */118syslog(LOG_ERR, _("could not sync the whole ulog to disk"));119abort();120}121}122123/* Return true if the ulog entry for sno matches sno and timestamp. */124static krb5_boolean125check_sno(kdb_log_context *log_ctx, kdb_sno_t sno,126const kdbe_time_t *timestamp)127{128unsigned int indx = (sno - 1) % log_ctx->ulogentries;129kdb_ent_header_t *ent = ulog_index(log_ctx->ulog, indx);130131return ent->kdb_entry_sno == sno && time_equal(&ent->kdb_time, timestamp);132}133134/*135* Check last against our ulog and determine whether it is up to date136* (UPDATE_NIL), so far out of date that a full dump is required137* (UPDATE_FULL_RESYNC_NEEDED), or okay to update with ulog entries138* (UPDATE_OK).139*/140static update_status_t141get_sno_status(kdb_log_context *log_ctx, const kdb_last_t *last)142{143kdb_hlog_t *ulog = log_ctx->ulog;144145/* If last matches the ulog's last serial number and time exactly, it are146* up to date even if the ulog is empty. */147if (last->last_sno == ulog->kdb_last_sno &&148time_equal(&last->last_time, &ulog->kdb_last_time))149return UPDATE_NIL;150151/* If our ulog is empty or does not contain last_sno, a full resync is152* required. */153if (ulog->kdb_num == 0 || last->last_sno > ulog->kdb_last_sno ||154last->last_sno < ulog->kdb_first_sno)155return UPDATE_FULL_RESYNC_NEEDED;156157/* If the timestamp in our ulog entry does not match last, then sno was158* reused and a full resync is required. */159if (!check_sno(log_ctx, last->last_sno, &last->last_time))160return UPDATE_FULL_RESYNC_NEEDED;161162/* last is not fully up to date, but can be updated using our ulog. */163return UPDATE_OK;164}165166/* Extend update log file. */167static krb5_error_code168extend_file_to(int fd, unsigned int new_size)169{170off_t current_offset;171static const char zero[512];172ssize_t wrote_size;173size_t write_size;174175current_offset = lseek(fd, 0, SEEK_END);176if (current_offset < 0)177return errno;178if (new_size > INT_MAX)179return EINVAL;180while (current_offset < (off_t)new_size) {181write_size = new_size - current_offset;182if (write_size > 512)183write_size = 512;184wrote_size = write(fd, zero, write_size);185if (wrote_size < 0)186return errno;187if (wrote_size == 0)188return EINVAL;189current_offset += wrote_size;190write_size = new_size - current_offset;191}192return 0;193}194195/* Resize the array elements of ulog to be at least as large as recsize. Move196* the existing elements into the proper offsets for the new block size. */197static krb5_error_code198resize(kdb_hlog_t *ulog, uint32_t ulogentries, int ulogfd,199unsigned int recsize, const kdb_incr_update_t *upd)200{201size_t old_block = ulog->kdb_block, new_block, new_size;202krb5_error_code retval;203uint8_t *old_ent, *new_ent;204uint32_t i;205206if (ulog == NULL)207return KRB5_LOG_ERROR;208209new_size = sizeof(kdb_hlog_t);210new_block = (recsize / ULOG_BLOCK) + 1;211new_block *= ULOG_BLOCK;212new_size += ulogentries * new_block;213214if (new_block > UINT16_MAX) {215syslog(LOG_ERR, _("ulog overflow caused by principal %.*s"),216upd->kdb_princ_name.utf8str_t_len,217upd->kdb_princ_name.utf8str_t_val);218return KRB5_LOG_ERROR;219}220if (new_size > MAXLOGLEN)221return KRB5_LOG_ERROR;222223/* Expand log considering new block size. */224retval = extend_file_to(ulogfd, new_size);225if (retval)226return retval;227228/* Copy each record into its new location and zero out the unused areas.229* The area is overlapping, so we have to iterate backwards. */230for (i = ulogentries; i > 0; i--) {231old_ent = ulog_record_ptr(ulog, i - 1, old_block);232new_ent = ulog_record_ptr(ulog, i - 1, new_block);233memmove(new_ent, old_ent, old_block);234memset(new_ent + old_block, 0, new_block - old_block);235}236237syslog(LOG_INFO, _("ulog block size has been resized from %lu to %lu"),238(unsigned long)old_block, (unsigned long)new_block);239ulog->kdb_block = new_block;240sync_ulog(ulog, ulogentries);241return 0;242}243244/* Set the ulog to contain only a dummy entry with the given serial number and245* timestamp. */246static void247set_dummy(kdb_log_context *log_ctx, kdb_sno_t sno, const kdbe_time_t *kdb_time)248{249kdb_hlog_t *ulog = log_ctx->ulog;250kdb_ent_header_t *ent = ulog_index(ulog, (sno - 1) % log_ctx->ulogentries);251252memset(ent, 0, sizeof(*ent));253ent->kdb_umagic = KDB_ULOG_MAGIC;254ent->kdb_entry_sno = sno;255ent->kdb_time = *kdb_time;256sync_update(ulog, ent);257258ulog->kdb_num = 1;259ulog->kdb_first_sno = ulog->kdb_last_sno = sno;260ulog->kdb_first_time = ulog->kdb_last_time = *kdb_time;261}262263/* Reinitialize the ulog header, starting from sno 1 with the current time. */264static void265reset_ulog(kdb_log_context *log_ctx)266{267kdbe_time_t kdb_time;268kdb_hlog_t *ulog = log_ctx->ulog;269270memset(ulog, 0, sizeof(*ulog));271ulog->kdb_hmagic = KDB_ULOG_HDR_MAGIC;272ulog->db_version_num = KDB_VERSION;273ulog->kdb_block = ULOG_BLOCK;274275/* Create a dummy entry to remember the timestamp for downstreams. */276time_current(&kdb_time);277set_dummy(log_ctx, 1, &kdb_time);278ulog->kdb_state = KDB_STABLE;279sync_header(ulog);280}281282/*283* If any database operations will be invoked while the ulog lock is held, the284* caller must explicitly lock the database before locking the ulog, or285* deadlock may result.286*/287static krb5_error_code288lock_ulog(krb5_context context, int mode)289{290kdb_log_context *log_ctx = NULL;291kdb_hlog_t *ulog = NULL;292293INIT_ULOG(context);294return krb5_lock_file(context, log_ctx->ulogfd, mode);295}296297static void298unlock_ulog(krb5_context context)299{300(void)lock_ulog(context, KRB5_LOCKMODE_UNLOCK);301}302303/*304* Add an update to the log. The update's kdb_entry_sno and kdb_time fields305* must already be set. The layout of the update log looks like:306*307* header log -> [ update header -> xdr(kdb_incr_update_t) ], ...308*/309static krb5_error_code310store_update(kdb_log_context *log_ctx, kdb_incr_update_t *upd)311{312XDR xdrs;313kdb_ent_header_t *indx_log;314unsigned int i, recsize;315unsigned long upd_size;316krb5_error_code retval;317kdb_hlog_t *ulog = log_ctx->ulog;318uint32_t ulogentries = log_ctx->ulogentries;319320upd_size = xdr_sizeof((xdrproc_t)xdr_kdb_incr_update_t, upd);321322recsize = sizeof(kdb_ent_header_t) + upd_size;323324if (recsize > ulog->kdb_block) {325retval = resize(ulog, ulogentries, log_ctx->ulogfd, recsize, upd);326if (retval)327return retval;328}329330ulog->kdb_state = KDB_UNSTABLE;331332i = (upd->kdb_entry_sno - 1) % ulogentries;333indx_log = ulog_index(ulog, i);334335memset(indx_log, 0, ulog->kdb_block);336indx_log->kdb_umagic = KDB_ULOG_MAGIC;337indx_log->kdb_entry_size = upd_size;338indx_log->kdb_entry_sno = upd->kdb_entry_sno;339indx_log->kdb_time = upd->kdb_time;340indx_log->kdb_commit = FALSE;341342xdrmem_create(&xdrs, (char *)indx_log->entry_data,343indx_log->kdb_entry_size, XDR_ENCODE);344if (!xdr_kdb_incr_update_t(&xdrs, upd))345return KRB5_LOG_CONV;346347indx_log->kdb_commit = TRUE;348sync_update(ulog, indx_log);349350/* Modify the ulog header to reflect the new update. */351ulog->kdb_last_sno = upd->kdb_entry_sno;352ulog->kdb_last_time = upd->kdb_time;353if (ulog->kdb_num == 0) {354/* We should only see this in old ulogs. */355ulog->kdb_num = 1;356ulog->kdb_first_sno = upd->kdb_entry_sno;357ulog->kdb_first_time = upd->kdb_time;358} else if (ulog->kdb_num < ulogentries) {359ulog->kdb_num++;360} else {361/* We are circling; set kdb_first_sno and time to the next update. */362i = upd->kdb_entry_sno % ulogentries;363indx_log = ulog_index(ulog, i);364ulog->kdb_first_sno = indx_log->kdb_entry_sno;365ulog->kdb_first_time = indx_log->kdb_time;366}367368ulog->kdb_state = KDB_STABLE;369sync_header(ulog);370return 0;371}372373/* Add an entry to the update log. */374krb5_error_code375ulog_add_update(krb5_context context, kdb_incr_update_t *upd)376{377krb5_error_code ret;378kdb_log_context *log_ctx;379kdb_hlog_t *ulog;380381INIT_ULOG(context);382ret = lock_ulog(context, KRB5_LOCKMODE_EXCLUSIVE);383if (ret)384return ret;385386/* If we have reached the last possible serial number, reinitialize the387* ulog and start over. Replicas will do a full resync. */388if (ulog->kdb_last_sno == (kdb_sno_t)-1)389reset_ulog(log_ctx);390391upd->kdb_entry_sno = ulog->kdb_last_sno + 1;392time_current(&upd->kdb_time);393ret = store_update(log_ctx, upd);394unlock_ulog(context);395return ret;396}397398/* Used by the replica to update its hash db from the incr update log. */399krb5_error_code400ulog_replay(krb5_context context, kdb_incr_result_t *incr_ret, char **db_args)401{402krb5_db_entry *entry = NULL;403kdb_incr_update_t *upd = NULL, *fupd;404int i, no_of_updates;405krb5_error_code retval;406krb5_principal dbprinc;407char *dbprincstr;408kdb_log_context *log_ctx;409kdb_hlog_t *ulog = NULL;410411INIT_ULOG(context);412413retval = krb5_db_open(context, db_args,414KRB5_KDB_OPEN_RW | KRB5_KDB_SRV_TYPE_ADMIN);415if (retval)416return retval;417418no_of_updates = incr_ret->updates.kdb_ulog_t_len;419upd = incr_ret->updates.kdb_ulog_t_val;420fupd = upd;421422for (i = 0; i < no_of_updates; i++) {423if (!upd->kdb_commit)424continue;425426/* Replay this update in the database. */427if (upd->kdb_deleted) {428dbprincstr = k5memdup0(upd->kdb_princ_name.utf8str_t_val,429upd->kdb_princ_name.utf8str_t_len, &retval);430if (dbprincstr == NULL)431goto cleanup;432433retval = krb5_parse_name(context, dbprincstr, &dbprinc);434free(dbprincstr);435if (retval)436goto cleanup;437438retval = krb5int_delete_principal_no_log(context, dbprinc);439krb5_free_principal(context, dbprinc);440if (retval == KRB5_KDB_NOENTRY)441retval = 0;442if (retval)443goto cleanup;444} else {445retval = ulog_conv_2dbentry(context, &entry, upd);446if (retval)447goto cleanup;448449retval = krb5int_put_principal_no_log(context, entry);450krb5_db_free_principal(context, entry);451if (retval)452goto cleanup;453}454455retval = lock_ulog(context, KRB5_LOCKMODE_EXCLUSIVE);456if (retval)457goto cleanup;458459/* If (unexpectedly) this update does not follow the last one we460* stored, discard any previous ulog state. */461if (ulog->kdb_num != 0 && upd->kdb_entry_sno != ulog->kdb_last_sno + 1)462reset_ulog(log_ctx);463464/* Store this update in the ulog for any downstream KDCs. */465retval = store_update(log_ctx, upd);466unlock_ulog(context);467if (retval)468goto cleanup;469470upd++;471}472473cleanup:474if (retval)475(void)ulog_init_header(context);476if (fupd)477ulog_free_entries(fupd, no_of_updates);478return retval;479}480481/* Reinitialize the log header. */482krb5_error_code483ulog_init_header(krb5_context context)484{485krb5_error_code ret;486kdb_log_context *log_ctx;487kdb_hlog_t *ulog;488489INIT_ULOG(context);490ret = lock_ulog(context, KRB5_LOCKMODE_EXCLUSIVE);491if (ret)492return ret;493reset_ulog(log_ctx);494unlock_ulog(context);495return 0;496}497498/* Map the log file to memory for performance and simplicity. */499krb5_error_code500ulog_map(krb5_context context, const char *logname, uint32_t ulogentries)501{502struct stat st;503krb5_error_code retval;504uint32_t filesize;505kdb_log_context *log_ctx;506kdb_hlog_t *ulog = NULL;507krb5_boolean locked = FALSE;508509log_ctx = create_log_context(context);510if (log_ctx == NULL)511return ENOMEM;512513if (stat(logname, &st) == -1) {514log_ctx->ulogfd = open(logname, O_RDWR | O_CREAT, 0600);515if (log_ctx->ulogfd == -1) {516retval = errno;517goto cleanup;518}519520filesize = sizeof(kdb_hlog_t) + ulogentries * ULOG_BLOCK;521retval = extend_file_to(log_ctx->ulogfd, filesize);522if (retval)523goto cleanup;524} else {525log_ctx->ulogfd = open(logname, O_RDWR, 0600);526if (log_ctx->ulogfd == -1) {527retval = errno;528goto cleanup;529}530}531532ulog = mmap(0, MAXLOGLEN, PROT_READ | PROT_WRITE, MAP_SHARED,533log_ctx->ulogfd, 0);534if (ulog == MAP_FAILED) {535retval = errno;536goto cleanup;537}538log_ctx->ulog = ulog;539log_ctx->ulogentries = ulogentries;540541retval = lock_ulog(context, KRB5_LOCKMODE_EXCLUSIVE);542if (retval)543goto cleanup;544locked = TRUE;545546if (ulog->kdb_hmagic != KDB_ULOG_HDR_MAGIC) {547if (ulog->kdb_hmagic != 0) {548retval = KRB5_LOG_CORRUPT;549goto cleanup;550}551reset_ulog(log_ctx);552}553554/* Reinit ulog if ulogentries changed such that we have too many entries or555* our first or last entry was written to the wrong location. */556if (ulog->kdb_num != 0 &&557(ulog->kdb_num > ulogentries ||558!check_sno(log_ctx, ulog->kdb_first_sno, &ulog->kdb_first_time) ||559!check_sno(log_ctx, ulog->kdb_last_sno, &ulog->kdb_last_time)))560reset_ulog(log_ctx);561562if (ulog->kdb_num != ulogentries) {563/* Expand the ulog file if it isn't big enough. */564filesize = sizeof(kdb_hlog_t) + ulogentries * ulog->kdb_block;565retval = extend_file_to(log_ctx->ulogfd, filesize);566if (retval)567goto cleanup;568}569570cleanup:571if (locked)572unlock_ulog(context);573if (retval)574ulog_fini(context);575return retval;576}577578/* Get the last set of updates seen, (last+1) to n is returned. */579krb5_error_code580ulog_get_entries(krb5_context context, const kdb_last_t *last,581kdb_incr_result_t *ulog_handle)582{583XDR xdrs;584kdb_ent_header_t *indx_log;585kdb_incr_update_t *upd;586unsigned int indx, count;587uint32_t sno;588krb5_error_code retval;589kdb_log_context *log_ctx;590kdb_hlog_t *ulog = NULL;591uint32_t ulogentries;592593INIT_ULOG(context);594ulogentries = log_ctx->ulogentries;595596retval = lock_ulog(context, KRB5_LOCKMODE_SHARED);597if (retval)598return retval;599600/* If another process terminated mid-update, reset the ulog and force full601* resyncs. */602if (ulog->kdb_state != KDB_STABLE)603reset_ulog(log_ctx);604605ulog_handle->ret = get_sno_status(log_ctx, last);606if (ulog_handle->ret != UPDATE_OK)607goto cleanup;608609sno = last->last_sno;610count = ulog->kdb_last_sno - sno;611upd = calloc(count, sizeof(kdb_incr_update_t));612if (upd == NULL) {613ulog_handle->ret = UPDATE_ERROR;614retval = ENOMEM;615goto cleanup;616}617ulog_handle->updates.kdb_ulog_t_val = upd;618619for (; sno < ulog->kdb_last_sno; sno++) {620indx = sno % ulogentries;621indx_log = ulog_index(ulog, indx);622623memset(upd, 0, sizeof(kdb_incr_update_t));624xdrmem_create(&xdrs, (char *)indx_log->entry_data,625indx_log->kdb_entry_size, XDR_DECODE);626if (!xdr_kdb_incr_update_t(&xdrs, upd)) {627ulog_handle->ret = UPDATE_ERROR;628retval = KRB5_LOG_CONV;629goto cleanup;630}631632/* Mark commitment since we didn't want to decode and encode the incr633* update record the first time. */634upd->kdb_commit = indx_log->kdb_commit;635upd++;636}637638ulog_handle->updates.kdb_ulog_t_len = count;639640ulog_handle->lastentry.last_sno = ulog->kdb_last_sno;641ulog_handle->lastentry.last_time.seconds = ulog->kdb_last_time.seconds;642ulog_handle->lastentry.last_time.useconds = ulog->kdb_last_time.useconds;643ulog_handle->ret = UPDATE_OK;644645cleanup:646unlock_ulog(context);647return retval;648}649650krb5_error_code651ulog_set_role(krb5_context ctx, iprop_role role)652{653if (create_log_context(ctx) == NULL)654return ENOMEM;655ctx->kdblog_context->iproprole = role;656return 0;657}658659update_status_t660ulog_get_sno_status(krb5_context context, const kdb_last_t *last)661{662update_status_t status;663664if (lock_ulog(context, KRB5_LOCKMODE_SHARED) != 0)665return UPDATE_ERROR;666status = get_sno_status(context->kdblog_context, last);667unlock_ulog(context);668return status;669}670671krb5_error_code672ulog_get_last(krb5_context context, kdb_last_t *last_out)673{674krb5_error_code ret;675kdb_log_context *log_ctx;676kdb_hlog_t *ulog;677678INIT_ULOG(context);679ret = lock_ulog(context, KRB5_LOCKMODE_SHARED);680if (ret)681return ret;682last_out->last_sno = log_ctx->ulog->kdb_last_sno;683last_out->last_time = log_ctx->ulog->kdb_last_time;684unlock_ulog(context);685return 0;686}687688krb5_error_code689ulog_set_last(krb5_context context, const kdb_last_t *last)690{691krb5_error_code ret;692kdb_log_context *log_ctx;693kdb_hlog_t *ulog;694695INIT_ULOG(context);696ret = lock_ulog(context, KRB5_LOCKMODE_EXCLUSIVE);697if (ret)698return ret;699700set_dummy(log_ctx, last->last_sno, &last->last_time);701sync_header(ulog);702unlock_ulog(context);703return 0;704}705706void707ulog_fini(krb5_context context)708{709kdb_log_context *log_ctx = context->kdblog_context;710711if (log_ctx == NULL)712return;713if (log_ctx->ulog != NULL)714munmap(log_ctx->ulog, MAXLOGLEN);715if (log_ctx->ulogfd != -1)716close(log_ctx->ulogfd);717free(log_ctx);718context->kdblog_context = NULL;719}720721722