log.c revision 1.2 1 1.1 elric /* $NetBSD: log.c,v 1.2 2017/01/28 21:31:49 christos Exp $ */
2 1.1 elric
3 1.1 elric /*
4 1.1 elric * Copyright (c) 1997 - 2007 Kungliga Tekniska Hgskolan
5 1.1 elric * (Royal Institute of Technology, Stockholm, Sweden).
6 1.1 elric * All rights reserved.
7 1.1 elric *
8 1.1 elric * Redistribution and use in source and binary forms, with or without
9 1.1 elric * modification, are permitted provided that the following conditions
10 1.1 elric * are met:
11 1.1 elric *
12 1.1 elric * 1. Redistributions of source code must retain the above copyright
13 1.1 elric * notice, this list of conditions and the following disclaimer.
14 1.1 elric *
15 1.1 elric * 2. Redistributions in binary form must reproduce the above copyright
16 1.1 elric * notice, this list of conditions and the following disclaimer in the
17 1.1 elric * documentation and/or other materials provided with the distribution.
18 1.1 elric *
19 1.1 elric * 3. Neither the name of the Institute nor the names of its contributors
20 1.1 elric * may be used to endorse or promote products derived from this software
21 1.1 elric * without specific prior written permission.
22 1.1 elric *
23 1.1 elric * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24 1.1 elric * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 1.1 elric * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 1.1 elric * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27 1.1 elric * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 1.1 elric * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 1.1 elric * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 1.1 elric * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 1.1 elric * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 1.1 elric * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 1.1 elric * SUCH DAMAGE.
34 1.1 elric */
35 1.1 elric
36 1.1 elric #include "kadm5_locl.h"
37 1.1 elric #include "heim_threads.h"
38 1.1 elric
39 1.1 elric __RCSID("$NetBSD: log.c,v 1.2 2017/01/28 21:31:49 christos Exp $");
40 1.1 elric
41 1.1 elric /*
42 1.2 christos * A log consists of a sequence of records of this form:
43 1.1 elric *
44 1.2 christos * version number 4 bytes -\
45 1.2 christos * time in seconds 4 bytes +> preamble --+> header
46 1.2 christos * operation (enum kadm_ops) 4 bytes -/ /
47 1.2 christos * n, length of payload 4 bytes --------------+
48 1.2 christos * PAYLOAD DATA... n bytes
49 1.2 christos * n, length of payload 4 bytes ----------------+> trailer
50 1.2 christos * version number 4 bytes ->postamble ---/
51 1.1 elric *
52 1.2 christos * I.e., records have a header and a trailer so that knowing the offset
53 1.2 christos * of an record's start or end one can traverse the log forwards and
54 1.2 christos * backwards.
55 1.2 christos *
56 1.2 christos * The log always starts with a nop record (uber record) that contains the
57 1.2 christos * offset (8 bytes) of the first unconfirmed record (typically EOF), and the
58 1.2 christos * version number and timestamp of the preceding last confirmed record:
59 1.2 christos *
60 1.2 christos * offset of next new record 8 bytes
61 1.2 christos * last record time 4 bytes
62 1.2 christos * last record version number 4 bytes
63 1.2 christos *
64 1.2 christos * When an iprop slave receives a complete database, it saves that version as
65 1.2 christos * the last confirmed version, without writing any other records to the log. We
66 1.2 christos * use that version as the basis for further updates.
67 1.2 christos *
68 1.2 christos * kadm5 write operations are done in this order:
69 1.2 christos *
70 1.2 christos * - replay unconfirmed log records
71 1.2 christos * - write (append) and fsync() the log record for the kadm5 update
72 1.2 christos * - update the HDB (which includes fsync() or moral equivalent)
73 1.2 christos * - update the log uber record to mark the log record written as
74 1.2 christos * confirmed (not fsync()ed)
75 1.2 christos *
76 1.2 christos * This makes it possible and safe to seek to the logical end of the log
77 1.2 christos * (that is, the end of the last confirmed record) without traversing
78 1.2 christos * the whole log forward from offset zero. Unconfirmed records (which
79 1.2 christos * -currently- should never be more than one) can then be found (and
80 1.2 christos * rolled forward) by traversing forward from the logical end of the
81 1.2 christos * log. The trailers make it possible to traverse the log backwards
82 1.2 christos * from the logical end.
83 1.2 christos *
84 1.2 christos * This also makes the log + the HDB a two-phase commit with
85 1.2 christos * roll-forward system.
86 1.2 christos *
87 1.2 christos * HDB entry exists and HDB entry does not exist errors occurring during
88 1.2 christos * replay of unconfirmed records are ignored. This is because the
89 1.2 christos * corresponding HDB update might have completed. But also because a
90 1.2 christos * change to add aliases to a principal can fail because we don't check
91 1.2 christos * for alias conflicts before going ahead with the write operation.
92 1.2 christos *
93 1.2 christos * Non-sensical and incomplete log records found during roll-forward are
94 1.2 christos * truncated. A log record is non-sensical if its header and trailer
95 1.2 christos * don't match.
96 1.2 christos *
97 1.2 christos * Recovery (by rolling forward) occurs at the next read or write by a
98 1.2 christos * kadm5 API reader (e.g., kadmin), but not by an hdb API reader (e.g.,
99 1.2 christos * the KDC). This means that, e.g., a principal rename could fail in
100 1.2 christos * between the store and the delete, and recovery might not take place
101 1.2 christos * until the next write operation.
102 1.2 christos *
103 1.2 christos * The log record payload format for create is:
104 1.2 christos *
105 1.2 christos * DER-encoded HDB_entry n bytes
106 1.2 christos *
107 1.2 christos * The log record payload format for update is:
108 1.2 christos *
109 1.2 christos * mask 4 bytes
110 1.2 christos * DER-encoded HDB_entry n-4 bytes
111 1.2 christos *
112 1.2 christos * The log record payload format for delete is:
113 1.2 christos *
114 1.2 christos * krb5_store_principal n bytes
115 1.2 christos *
116 1.2 christos * The log record payload format for rename is:
117 1.2 christos *
118 1.2 christos * krb5_store_principal m bytes (old principal name)
119 1.2 christos * DER-encoded HDB_entry n-m bytes (new record)
120 1.2 christos *
121 1.2 christos * The log record payload format for nop varies:
122 1.2 christos *
123 1.2 christos * - The zeroth record in new logs is a nop with a 16 byte payload:
124 1.2 christos *
125 1.2 christos * offset of end of last confirmed record 8 bytes
126 1.2 christos * timestamp of last confirmed record 4 bytes
127 1.2 christos * version number of last confirmed record 4 bytes
128 1.2 christos *
129 1.2 christos * - New non-zeroth nop records:
130 1.2 christos *
131 1.2 christos * nop type 4 bytes
132 1.2 christos *
133 1.2 christos * - Old nop records:
134 1.2 christos *
135 1.2 christos * version number 4 bytes
136 1.2 christos * timestamp 4 bytes
137 1.2 christos *
138 1.2 christos * Upon initialization, the log's uber record will have version 1, and
139 1.2 christos * will be followed by a nop record with version 2. The version numbers
140 1.2 christos * of additional records will be monotonically increasing.
141 1.2 christos *
142 1.2 christos * Truncation (kadm5_log_truncate()) takes some N > 0 records from the
143 1.2 christos * tail of the log and writes them to the beginning of the log after an
144 1.2 christos * uber record whose version will then be one less than the first of
145 1.2 christos * those records.
146 1.2 christos *
147 1.2 christos * On masters the log should never have more than one unconfirmed
148 1.2 christos * record, but slaves append all of a master's "diffs" and then call
149 1.2 christos * kadm5_log_recover() to recover.
150 1.2 christos */
151 1.2 christos
152 1.2 christos /*
153 1.2 christos * HDB and log lock order on the master:
154 1.2 christos *
155 1.2 christos * 1) open and lock the HDB
156 1.2 christos * 2) open and lock the log
157 1.2 christos * 3) do something
158 1.2 christos * 4) unlock and close the log
159 1.2 christos * 5) repeat (2)..(4) if desired
160 1.2 christos * 6) unlock and close the HDB
161 1.2 christos *
162 1.2 christos * The kadmin -l lock command can be used to hold the HDB open and
163 1.2 christos * locked for multiple operations.
164 1.2 christos *
165 1.2 christos * HDB and log lock order on the slave:
166 1.2 christos *
167 1.2 christos * 1) open and lock the log
168 1.2 christos * 2) open and lock the HDB
169 1.2 christos * 3) replay entries
170 1.2 christos * 4) unlock and close the HDB
171 1.2 christos * 5) repeat (2)..(4) until signaled
172 1.2 christos * 6) unlock and close the HDB
173 1.2 christos *
174 1.2 christos * The slave doesn't want to allow other local writers, after all, thus
175 1.2 christos * the order is reversed. This means that using "kadmin -l" on a slave
176 1.2 christos * will deadlock with ipropd-slave -- don't do that.
177 1.2 christos */
178 1.2 christos
179 1.2 christos #define LOG_HEADER_SZ ((off_t)(sizeof(uint32_t) * 4))
180 1.2 christos #define LOG_TRAILER_SZ ((off_t)(sizeof(uint32_t) * 2))
181 1.2 christos #define LOG_WRAPPER_SZ ((off_t)(LOG_HEADER_SZ + LOG_TRAILER_SZ))
182 1.2 christos #define LOG_UBER_LEN ((off_t)(sizeof(uint64_t) + sizeof(uint32_t) * 2))
183 1.2 christos #define LOG_UBER_SZ ((off_t)(LOG_WRAPPER_SZ + LOG_UBER_LEN))
184 1.2 christos
185 1.2 christos #define LOG_NOPEEK 0
186 1.2 christos #define LOG_DOPEEK 1
187 1.2 christos
188 1.2 christos /*
189 1.2 christos * Read the header of the record starting at the current offset into sp.
190 1.2 christos *
191 1.2 christos * Preserves sp's offset on success if `peek', else skips the header.
192 1.2 christos *
193 1.2 christos * Preserves sp's offset on failure where possible.
194 1.2 christos */
195 1.2 christos static kadm5_ret_t
196 1.2 christos get_header(krb5_storage *sp, int peek, uint32_t *verp, uint32_t *tstampp,
197 1.2 christos enum kadm_ops *opp, uint32_t *lenp)
198 1.2 christos {
199 1.2 christos krb5_error_code ret;
200 1.2 christos uint32_t tstamp, op, len;
201 1.2 christos off_t off, new_off;
202 1.2 christos
203 1.2 christos if (tstampp == NULL)
204 1.2 christos tstampp = &tstamp;
205 1.2 christos if (lenp == NULL)
206 1.2 christos lenp = &len;
207 1.2 christos
208 1.2 christos *verp = 0;
209 1.2 christos *tstampp = 0;
210 1.2 christos if (opp != NULL)
211 1.2 christos *opp = kadm_nop;
212 1.2 christos *lenp = 0;
213 1.2 christos
214 1.2 christos off = krb5_storage_seek(sp, 0, SEEK_CUR);
215 1.2 christos if (off < 0)
216 1.2 christos return errno;
217 1.2 christos ret = krb5_ret_uint32(sp, verp);
218 1.2 christos if (ret == HEIM_ERR_EOF) {
219 1.2 christos (void) krb5_storage_seek(sp, off, SEEK_SET);
220 1.2 christos return HEIM_ERR_EOF;
221 1.2 christos }
222 1.2 christos if (ret)
223 1.2 christos goto log_corrupt;
224 1.2 christos ret = krb5_ret_uint32(sp, tstampp);
225 1.2 christos if (ret)
226 1.2 christos goto log_corrupt;
227 1.2 christos
228 1.2 christos /* Note: sizeof(*opp) might not == sizeof(op) */
229 1.2 christos ret = krb5_ret_uint32(sp, &op);
230 1.2 christos if (ret)
231 1.2 christos goto log_corrupt;
232 1.2 christos if (opp != NULL)
233 1.2 christos *opp = op;
234 1.2 christos
235 1.2 christos ret = krb5_ret_uint32(sp, lenp);
236 1.2 christos if (ret)
237 1.2 christos goto log_corrupt;
238 1.2 christos
239 1.2 christos /* Restore offset if requested */
240 1.2 christos if (peek == LOG_DOPEEK) {
241 1.2 christos new_off = krb5_storage_seek(sp, off, SEEK_SET);
242 1.2 christos if (new_off == -1)
243 1.2 christos return errno;
244 1.2 christos if (new_off != off)
245 1.2 christos return EIO;
246 1.2 christos }
247 1.2 christos
248 1.2 christos return 0;
249 1.2 christos
250 1.2 christos log_corrupt:
251 1.2 christos (void) krb5_storage_seek(sp, off, SEEK_SET);
252 1.2 christos return KADM5_LOG_CORRUPT;
253 1.2 christos }
254 1.2 christos
255 1.2 christos /*
256 1.2 christos * Seek to the start of the preceding record's header and returns its
257 1.2 christos * offset. If sp is at offset zero this sets *verp = 0 and returns 0.
258 1.2 christos *
259 1.2 christos * Does not verify the header of the previous entry.
260 1.2 christos *
261 1.2 christos * On error returns -1, setting errno (possibly to a kadm5_ret_t or
262 1.2 christos * krb5_error_code value) and preserves sp's offset where possible.
263 1.2 christos */
264 1.2 christos static off_t
265 1.2 christos seek_prev(krb5_storage *sp, uint32_t *verp, uint32_t *lenp)
266 1.2 christos {
267 1.2 christos krb5_error_code ret;
268 1.2 christos uint32_t len, ver;
269 1.2 christos off_t off_len;
270 1.2 christos off_t off, new_off;
271 1.2 christos
272 1.2 christos if (lenp == NULL)
273 1.2 christos lenp = &len;
274 1.2 christos if (verp == NULL)
275 1.2 christos verp = &ver;
276 1.2 christos
277 1.2 christos *verp = 0;
278 1.2 christos *lenp = 0;
279 1.2 christos
280 1.2 christos off = krb5_storage_seek(sp, 0, SEEK_CUR);
281 1.2 christos if (off < 0)
282 1.2 christos return off;
283 1.2 christos if (off == 0)
284 1.2 christos return 0;
285 1.2 christos
286 1.2 christos /* Check that `off' allows for the record's header and trailer */
287 1.2 christos if (off < LOG_WRAPPER_SZ)
288 1.2 christos goto log_corrupt;
289 1.2 christos
290 1.2 christos /* Get the previous entry's length and version from its trailer */
291 1.2 christos new_off = krb5_storage_seek(sp, -8, SEEK_CUR);
292 1.2 christos if (new_off == -1)
293 1.2 christos return -1;
294 1.2 christos if (new_off != off - 8) {
295 1.2 christos errno = EIO;
296 1.2 christos return -1;
297 1.2 christos }
298 1.2 christos ret = krb5_ret_uint32(sp, lenp);
299 1.2 christos if (ret)
300 1.2 christos goto log_corrupt;
301 1.2 christos
302 1.2 christos /* Check for overflow/sign extension */
303 1.2 christos off_len = (off_t)*lenp;
304 1.2 christos if (off_len < 0 || *lenp != (uint32_t)off_len)
305 1.2 christos goto log_corrupt;
306 1.2 christos
307 1.2 christos ret = krb5_ret_uint32(sp, verp);
308 1.2 christos if (ret)
309 1.2 christos goto log_corrupt;
310 1.2 christos
311 1.2 christos /* Check that `off' allows for the record */
312 1.2 christos if (off < LOG_WRAPPER_SZ + off_len)
313 1.2 christos goto log_corrupt;
314 1.2 christos
315 1.2 christos /* Seek backwards to the entry's start */
316 1.2 christos new_off = krb5_storage_seek(sp, -(LOG_WRAPPER_SZ + off_len), SEEK_CUR);
317 1.2 christos if (new_off == -1)
318 1.2 christos return -1;
319 1.2 christos if (new_off != off - (LOG_WRAPPER_SZ + off_len)) {
320 1.2 christos errno = EIO;
321 1.2 christos return -1;
322 1.2 christos }
323 1.2 christos return new_off;
324 1.2 christos
325 1.2 christos log_corrupt:
326 1.2 christos (void) krb5_storage_seek(sp, off, SEEK_SET);
327 1.2 christos errno = KADM5_LOG_CORRUPT;
328 1.2 christos return -1;
329 1.2 christos }
330 1.2 christos
331 1.2 christos /*
332 1.2 christos * Seek to the start of the next entry's header.
333 1.2 christos *
334 1.2 christos * On error returns -1 and preserves sp's offset.
335 1.2 christos */
336 1.2 christos static off_t
337 1.2 christos seek_next(krb5_storage *sp)
338 1.2 christos {
339 1.2 christos krb5_error_code ret;
340 1.2 christos uint32_t ver, ver2, len, len2;
341 1.2 christos enum kadm_ops op;
342 1.2 christos uint32_t tstamp;
343 1.2 christos off_t off, off_len, new_off;
344 1.2 christos
345 1.2 christos off = krb5_storage_seek(sp, 0, SEEK_CUR);
346 1.2 christos if (off < 0)
347 1.2 christos return off;
348 1.2 christos
349 1.2 christos errno = get_header(sp, LOG_NOPEEK, &ver, &tstamp, &op, &len);
350 1.2 christos if (errno)
351 1.2 christos return -1;
352 1.2 christos
353 1.2 christos /* Check for overflow */
354 1.2 christos off_len = len;
355 1.2 christos if (off_len < 0)
356 1.2 christos goto log_corrupt;
357 1.2 christos
358 1.2 christos new_off = krb5_storage_seek(sp, off_len, SEEK_CUR);
359 1.2 christos if (new_off == -1) {
360 1.2 christos (void) krb5_storage_seek(sp, off, SEEK_SET);
361 1.2 christos return -1;
362 1.2 christos }
363 1.2 christos if (new_off != off + LOG_HEADER_SZ + off_len)
364 1.2 christos goto log_corrupt;
365 1.2 christos ret = krb5_ret_uint32(sp, &len2);
366 1.2 christos if (ret || len2 != len)
367 1.2 christos goto log_corrupt;
368 1.2 christos ret = krb5_ret_uint32(sp, &ver2);
369 1.2 christos if (ret || ver2 != ver)
370 1.2 christos goto log_corrupt;
371 1.2 christos new_off = krb5_storage_seek(sp, 0, SEEK_CUR);
372 1.2 christos if (new_off == -1) {
373 1.2 christos (void) krb5_storage_seek(sp, off, SEEK_SET);
374 1.2 christos return -1;
375 1.2 christos }
376 1.2 christos if (new_off != off + off_len + LOG_WRAPPER_SZ)
377 1.2 christos goto log_corrupt;
378 1.2 christos
379 1.2 christos return off + off_len + LOG_WRAPPER_SZ;
380 1.2 christos
381 1.2 christos log_corrupt:
382 1.2 christos (void) krb5_storage_seek(sp, off, SEEK_SET);
383 1.2 christos errno = KADM5_LOG_CORRUPT;
384 1.2 christos return -1;
385 1.2 christos }
386 1.2 christos
387 1.2 christos /*
388 1.2 christos * Get the version of the entry ending at the current offset into sp.
389 1.2 christos * If it is the uber record, return its nominal version instead.
390 1.2 christos *
391 1.2 christos * Returns HEIM_ERR_EOF if sp is at offset zero.
392 1.2 christos *
393 1.2 christos * Preserves sp's offset.
394 1.1 elric */
395 1.2 christos static kadm5_ret_t
396 1.2 christos get_version_prev(krb5_storage *sp, uint32_t *verp, uint32_t *tstampp)
397 1.2 christos {
398 1.2 christos krb5_error_code ret;
399 1.2 christos uint32_t ver, ver2, len, len2;
400 1.2 christos off_t off, prev_off, new_off;
401 1.2 christos
402 1.2 christos *verp = 0;
403 1.2 christos if (tstampp != NULL)
404 1.2 christos *tstampp = 0;
405 1.2 christos
406 1.2 christos off = krb5_storage_seek(sp, 0, SEEK_CUR);
407 1.2 christos if (off < 0)
408 1.2 christos return errno;
409 1.2 christos if (off == 0)
410 1.2 christos return HEIM_ERR_EOF;
411 1.2 christos
412 1.2 christos /* Read the trailer and seek back */
413 1.2 christos prev_off = seek_prev(sp, &ver, &len);
414 1.2 christos if (prev_off == -1)
415 1.2 christos return errno;
416 1.2 christos
417 1.2 christos /* Uber record? Return nominal version. */
418 1.2 christos if (prev_off == 0 && len == LOG_UBER_LEN && ver == 0) {
419 1.2 christos /* Skip 8 byte offset and 4 byte time */
420 1.2 christos if (krb5_storage_seek(sp, LOG_HEADER_SZ + 12, SEEK_SET)
421 1.2 christos != LOG_HEADER_SZ + 12)
422 1.2 christos return errno;
423 1.2 christos ret = krb5_ret_uint32(sp, verp);
424 1.2 christos if (krb5_storage_seek(sp, 0, SEEK_SET) != 0)
425 1.2 christos return errno;
426 1.2 christos if (ret != 0)
427 1.2 christos return ret;
428 1.2 christos } else {
429 1.2 christos *verp = ver;
430 1.2 christos }
431 1.2 christos
432 1.2 christos /* Verify that the trailer matches header */
433 1.2 christos ret = get_header(sp, LOG_NOPEEK, &ver2, tstampp, NULL, &len2);
434 1.2 christos if (ret || ver != ver2 || len != len2)
435 1.2 christos goto log_corrupt;
436 1.2 christos
437 1.2 christos /* Preserve offset */
438 1.2 christos new_off = krb5_storage_seek(sp, off, SEEK_SET);
439 1.2 christos if (new_off == -1)
440 1.2 christos return errno;
441 1.2 christos if (new_off != off) {
442 1.2 christos errno = EIO;
443 1.2 christos return errno;
444 1.2 christos }
445 1.2 christos return 0;
446 1.2 christos
447 1.2 christos log_corrupt:
448 1.2 christos (void) krb5_storage_seek(sp, off, SEEK_SET);
449 1.2 christos return KADM5_LOG_CORRUPT;
450 1.2 christos }
451 1.2 christos
452 1.2 christos static size_t
453 1.2 christos get_max_log_size(krb5_context context)
454 1.2 christos {
455 1.2 christos off_t n;
456 1.2 christos
457 1.2 christos /* Use database-label-specific lookup? No, ETOOHARD. */
458 1.2 christos /* Default to 50MB max log size */
459 1.2 christos n = krb5_config_get_int_default(context, NULL, 52428800,
460 1.2 christos "kdc",
461 1.2 christos "log-max-size",
462 1.2 christos NULL);
463 1.2 christos if (n >= 4 * (LOG_UBER_LEN + LOG_WRAPPER_SZ) && n == (size_t)n)
464 1.2 christos return (size_t)n;
465 1.2 christos return 0;
466 1.2 christos }
467 1.2 christos
468 1.2 christos static kadm5_ret_t truncate_if_needed(kadm5_server_context *);
469 1.2 christos static krb5_storage *log_goto_first(kadm5_server_context *, int);
470 1.1 elric
471 1.2 christos /*
472 1.2 christos * Get the version and timestamp metadata of either the first, or last
473 1.2 christos * confirmed entry in the log.
474 1.2 christos *
475 1.2 christos * If `which' is LOG_VERSION_UBER, then this gets the version number of the uber
476 1.2 christos * uber record which must be 0, or else we need to upgrade the log.
477 1.2 christos *
478 1.2 christos * If `which' is LOG_VERSION_FIRST, then this gets the metadata for the
479 1.2 christos * logically first entry past the uberblock, or returns HEIM_EOF if
480 1.2 christos * only the uber record is present.
481 1.2 christos *
482 1.2 christos * If `which' is LOG_VERSION_LAST, then this gets metadata for the last
483 1.2 christos * confirmed entry's version and timestamp. If only the uber record is present,
484 1.2 christos * then the version will be its "nominal" version, which may differ from its
485 1.2 christos * actual version (0).
486 1.2 christos *
487 1.2 christos * The `fd''s offset will be set to the start of the header of the entry
488 1.2 christos * identified by `which'.
489 1.2 christos */
490 1.1 elric kadm5_ret_t
491 1.2 christos kadm5_log_get_version_fd(kadm5_server_context *server_context, int fd,
492 1.2 christos int which, uint32_t *ver, uint32_t *tstamp)
493 1.1 elric {
494 1.2 christos kadm5_ret_t ret = 0;
495 1.1 elric krb5_storage *sp;
496 1.2 christos enum kadm_ops op = kadm_get;
497 1.2 christos uint32_t len = 0;
498 1.2 christos uint32_t tmp;
499 1.2 christos
500 1.2 christos if (fd == -1)
501 1.2 christos return 0; /* /dev/null */
502 1.2 christos
503 1.2 christos if (tstamp == NULL)
504 1.2 christos tstamp = &tmp;
505 1.2 christos
506 1.2 christos *ver = 0;
507 1.2 christos *tstamp = 0;
508 1.2 christos
509 1.2 christos switch (which) {
510 1.2 christos case LOG_VERSION_LAST:
511 1.2 christos sp = kadm5_log_goto_end(server_context, fd);
512 1.2 christos if (sp == NULL)
513 1.2 christos return errno;
514 1.2 christos ret = get_version_prev(sp, ver, tstamp);
515 1.2 christos krb5_storage_free(sp);
516 1.2 christos break;
517 1.2 christos case LOG_VERSION_FIRST:
518 1.2 christos sp = log_goto_first(server_context, fd);
519 1.2 christos if (sp == NULL)
520 1.2 christos return errno;
521 1.2 christos ret = get_header(sp, LOG_DOPEEK, ver, tstamp, NULL, NULL);
522 1.2 christos krb5_storage_free(sp);
523 1.2 christos break;
524 1.2 christos case LOG_VERSION_UBER:
525 1.2 christos sp = krb5_storage_from_fd(server_context->log_context.log_fd);
526 1.2 christos if (sp == NULL)
527 1.2 christos return errno;
528 1.2 christos if (krb5_storage_seek(sp, 0, SEEK_SET) == 0)
529 1.2 christos ret = get_header(sp, LOG_DOPEEK, ver, tstamp, &op, &len);
530 1.2 christos else
531 1.2 christos ret = errno;
532 1.2 christos if (ret == 0 && (op != kadm_nop || len != LOG_UBER_LEN || *ver != 0))
533 1.2 christos ret = KADM5_LOG_NEEDS_UPGRADE;
534 1.2 christos krb5_storage_free(sp);
535 1.2 christos break;
536 1.2 christos default:
537 1.2 christos return ENOTSUP;
538 1.2 christos }
539 1.1 elric
540 1.2 christos return ret;
541 1.1 elric }
542 1.1 elric
543 1.2 christos /* Get the version of the last confirmed entry in the log */
544 1.1 elric kadm5_ret_t
545 1.2 christos kadm5_log_get_version(kadm5_server_context *server_context, uint32_t *ver)
546 1.1 elric {
547 1.2 christos return kadm5_log_get_version_fd(server_context,
548 1.2 christos server_context->log_context.log_fd,
549 1.2 christos LOG_VERSION_LAST, ver, NULL);
550 1.1 elric }
551 1.1 elric
552 1.2 christos /* Sets the version in the context, but NOT in the log */
553 1.1 elric kadm5_ret_t
554 1.2 christos kadm5_log_set_version(kadm5_server_context *context, uint32_t vno)
555 1.1 elric {
556 1.1 elric kadm5_log_context *log_context = &context->log_context;
557 1.1 elric
558 1.1 elric log_context->version = vno;
559 1.1 elric return 0;
560 1.1 elric }
561 1.1 elric
562 1.2 christos /*
563 1.2 christos * Open the log and setup server_context->log_context
564 1.2 christos */
565 1.2 christos static kadm5_ret_t
566 1.2 christos log_open(kadm5_server_context *server_context, int lock_mode)
567 1.1 elric {
568 1.2 christos int fd = -1;
569 1.2 christos int lock_it = 0;
570 1.2 christos int lock_nb = 0;
571 1.2 christos int oflags = O_RDWR;
572 1.1 elric kadm5_ret_t ret;
573 1.2 christos kadm5_log_context *log_context = &server_context->log_context;
574 1.2 christos
575 1.2 christos if (lock_mode & LOCK_NB) {
576 1.2 christos lock_mode &= ~LOCK_NB;
577 1.2 christos lock_nb = LOCK_NB;
578 1.2 christos }
579 1.2 christos
580 1.2 christos if (lock_mode == log_context->lock_mode && log_context->log_fd != -1)
581 1.2 christos return 0;
582 1.2 christos
583 1.2 christos if (strcmp(log_context->log_file, "/dev/null") == 0) {
584 1.2 christos /* log_context->log_fd should be -1 here */
585 1.2 christos return 0;
586 1.2 christos }
587 1.1 elric
588 1.2 christos if (log_context->log_fd != -1) {
589 1.2 christos /* Lock or change lock */
590 1.2 christos fd = log_context->log_fd;
591 1.2 christos if (lseek(fd, 0, SEEK_SET) == -1)
592 1.2 christos return errno;
593 1.2 christos lock_it = (lock_mode != log_context->lock_mode);
594 1.2 christos } else {
595 1.2 christos /* Open and lock */
596 1.2 christos if (lock_mode != LOCK_UN)
597 1.2 christos oflags |= O_CREAT;
598 1.2 christos fd = open(log_context->log_file, oflags, 0600);
599 1.2 christos if (fd < 0) {
600 1.2 christos ret = errno;
601 1.2 christos krb5_set_error_message(server_context->context, ret,
602 1.2 christos "log_open: open %s", log_context->log_file);
603 1.2 christos return ret;
604 1.2 christos }
605 1.2 christos lock_it = (lock_mode != LOCK_UN);
606 1.2 christos }
607 1.2 christos if (lock_it && flock(fd, lock_mode | lock_nb) < 0) {
608 1.1 elric ret = errno;
609 1.2 christos krb5_set_error_message(server_context->context, ret,
610 1.2 christos "log_open: flock %s", log_context->log_file);
611 1.2 christos if (fd != log_context->log_fd)
612 1.2 christos (void) close(fd);
613 1.1 elric return ret;
614 1.1 elric }
615 1.2 christos
616 1.2 christos log_context->log_fd = fd;
617 1.2 christos log_context->lock_mode = lock_mode;
618 1.2 christos log_context->read_only = (lock_mode != LOCK_EX);
619 1.2 christos
620 1.2 christos return 0;
621 1.2 christos }
622 1.2 christos
623 1.2 christos /*
624 1.2 christos * Open the log and setup server_context->log_context
625 1.2 christos */
626 1.2 christos static kadm5_ret_t
627 1.2 christos log_init(kadm5_server_context *server_context, int lock_mode)
628 1.2 christos {
629 1.2 christos int fd;
630 1.2 christos struct stat st;
631 1.2 christos uint32_t vno;
632 1.2 christos size_t maxbytes = get_max_log_size(server_context->context);
633 1.2 christos kadm5_ret_t ret;
634 1.2 christos kadm5_log_context *log_context = &server_context->log_context;
635 1.2 christos
636 1.2 christos if (strcmp(log_context->log_file, "/dev/null") == 0) {
637 1.2 christos /* log_context->log_fd should be -1 here */
638 1.2 christos return 0;
639 1.2 christos }
640 1.2 christos
641 1.2 christos ret = log_open(server_context, lock_mode);
642 1.2 christos if (ret)
643 1.2 christos return ret;
644 1.2 christos
645 1.2 christos fd = log_context->log_fd;
646 1.2 christos if (!log_context->read_only) {
647 1.2 christos if (fstat(fd, &st) == -1)
648 1.2 christos ret = errno;
649 1.2 christos if (ret == 0 && st.st_size == 0) {
650 1.2 christos /* Write first entry */
651 1.2 christos log_context->version = 0;
652 1.2 christos ret = kadm5_log_nop(server_context, kadm_nop_plain);
653 1.2 christos if (ret == 0)
654 1.2 christos return 0; /* no need to truncate_if_needed(): it's not */
655 1.2 christos }
656 1.2 christos if (ret == 0) {
657 1.2 christos ret = kadm5_log_get_version_fd(server_context, fd,
658 1.2 christos LOG_VERSION_UBER, &vno, NULL);
659 1.2 christos
660 1.2 christos /* Upgrade the log if it was an old-style log */
661 1.2 christos if (ret == KADM5_LOG_NEEDS_UPGRADE)
662 1.2 christos ret = kadm5_log_truncate(server_context, 0, maxbytes / 4);
663 1.2 christos }
664 1.2 christos if (ret == 0)
665 1.2 christos ret = kadm5_log_recover(server_context, kadm_recover_replay);
666 1.2 christos }
667 1.2 christos
668 1.2 christos if (ret == 0) {
669 1.2 christos ret = kadm5_log_get_version_fd(server_context, fd, LOG_VERSION_LAST,
670 1.2 christos &log_context->version, NULL);
671 1.2 christos if (ret == HEIM_ERR_EOF)
672 1.2 christos ret = 0;
673 1.1 elric }
674 1.1 elric
675 1.2 christos if (ret == 0)
676 1.2 christos ret = truncate_if_needed(server_context);
677 1.2 christos
678 1.2 christos if (ret != 0)
679 1.2 christos (void) kadm5_log_end(server_context);
680 1.2 christos return ret;
681 1.2 christos }
682 1.2 christos
683 1.2 christos /* Open the log with an exclusive lock */
684 1.2 christos kadm5_ret_t
685 1.2 christos kadm5_log_init(kadm5_server_context *server_context)
686 1.2 christos {
687 1.2 christos return log_init(server_context, LOCK_EX);
688 1.2 christos }
689 1.2 christos
690 1.2 christos /* Open the log with an exclusive non-blocking lock */
691 1.2 christos kadm5_ret_t
692 1.2 christos kadm5_log_init_nb(kadm5_server_context *server_context)
693 1.2 christos {
694 1.2 christos return log_init(server_context, LOCK_EX | LOCK_NB);
695 1.2 christos }
696 1.2 christos
697 1.2 christos /* Open the log with no locks */
698 1.2 christos kadm5_ret_t
699 1.2 christos kadm5_log_init_nolock(kadm5_server_context *server_context)
700 1.2 christos {
701 1.2 christos return log_init(server_context, LOCK_UN);
702 1.2 christos }
703 1.1 elric
704 1.2 christos /* Open the log with a shared lock */
705 1.2 christos kadm5_ret_t
706 1.2 christos kadm5_log_init_sharedlock(kadm5_server_context *server_context, int lock_flags)
707 1.2 christos {
708 1.2 christos return log_init(server_context, LOCK_SH | lock_flags);
709 1.1 elric }
710 1.1 elric
711 1.2 christos /*
712 1.2 christos * Reinitialize the log and open it
713 1.2 christos */
714 1.1 elric kadm5_ret_t
715 1.2 christos kadm5_log_reinit(kadm5_server_context *server_context, uint32_t vno)
716 1.1 elric {
717 1.2 christos int ret;
718 1.2 christos kadm5_log_context *log_context = &server_context->log_context;
719 1.1 elric
720 1.2 christos ret = log_open(server_context, LOCK_EX);
721 1.2 christos if (ret)
722 1.2 christos return ret;
723 1.1 elric if (log_context->log_fd != -1) {
724 1.2 christos if (ftruncate(log_context->log_fd, 0) < 0) {
725 1.2 christos ret = errno;
726 1.2 christos return ret;
727 1.2 christos }
728 1.2 christos if (lseek(log_context->log_fd, 0, SEEK_SET) < 0) {
729 1.2 christos ret = errno;
730 1.2 christos return ret;
731 1.2 christos }
732 1.1 elric }
733 1.1 elric
734 1.2 christos /* Write uber entry and truncation nop with version `vno` */
735 1.2 christos log_context->version = vno;
736 1.2 christos return kadm5_log_nop(server_context, kadm_nop_plain);
737 1.1 elric }
738 1.1 elric
739 1.2 christos /* Close the server_context->log_context. */
740 1.1 elric kadm5_ret_t
741 1.2 christos kadm5_log_end(kadm5_server_context *server_context)
742 1.1 elric {
743 1.2 christos kadm5_log_context *log_context = &server_context->log_context;
744 1.2 christos kadm5_ret_t ret = 0;
745 1.1 elric int fd = log_context->log_fd;
746 1.1 elric
747 1.2 christos if (fd != -1) {
748 1.2 christos if (log_context->lock_mode != LOCK_UN) {
749 1.2 christos if (flock(fd, LOCK_UN) == -1 && errno == EBADF)
750 1.2 christos ret = errno;
751 1.2 christos }
752 1.2 christos if (ret != EBADF && close(fd) == -1)
753 1.2 christos ret = errno;
754 1.2 christos }
755 1.1 elric log_context->log_fd = -1;
756 1.2 christos log_context->lock_mode = LOCK_UN;
757 1.2 christos return ret;
758 1.1 elric }
759 1.1 elric
760 1.2 christos /*
761 1.2 christos * Write the version, timestamp, and op for a new entry.
762 1.2 christos *
763 1.2 christos * Note that the sp should be a krb5_storage_emem(), not a file.
764 1.2 christos *
765 1.2 christos * On success the sp's offset will be where the length of the payload
766 1.2 christos * should be written.
767 1.2 christos */
768 1.1 elric static kadm5_ret_t
769 1.2 christos kadm5_log_preamble(kadm5_server_context *context,
770 1.2 christos krb5_storage *sp,
771 1.2 christos enum kadm_ops op,
772 1.2 christos uint32_t vno)
773 1.1 elric {
774 1.1 elric kadm5_log_context *log_context = &context->log_context;
775 1.2 christos time_t now = time(NULL);
776 1.2 christos kadm5_ret_t ret;
777 1.1 elric
778 1.2 christos ret = krb5_store_uint32(sp, vno);
779 1.2 christos if (ret)
780 1.2 christos return ret;
781 1.2 christos ret = krb5_store_uint32(sp, now);
782 1.2 christos if (ret)
783 1.2 christos return ret;
784 1.2 christos log_context->last_time = now;
785 1.1 elric
786 1.2 christos if (op < kadm_first || op > kadm_last)
787 1.2 christos return ERANGE;
788 1.2 christos return krb5_store_uint32(sp, op);
789 1.1 elric }
790 1.1 elric
791 1.2 christos /* Writes the version part of the trailer */
792 1.1 elric static kadm5_ret_t
793 1.2 christos kadm5_log_postamble(kadm5_log_context *context,
794 1.2 christos krb5_storage *sp,
795 1.2 christos uint32_t vno)
796 1.2 christos {
797 1.2 christos return krb5_store_uint32(sp, vno);
798 1.2 christos }
799 1.2 christos
800 1.2 christos /*
801 1.2 christos * Signal the ipropd-master about changes to the log.
802 1.2 christos */
803 1.2 christos /*
804 1.2 christos * XXX Get rid of the ifdef by having a sockaddr in log_context in both
805 1.2 christos * cases.
806 1.2 christos *
807 1.2 christos * XXX Better yet, just connect to the master's socket that slaves
808 1.2 christos * connect to, and then disconnect. The master should then check the
809 1.2 christos * log on every connection accepted. Then we wouldn't need IPC to
810 1.2 christos * signal the master.
811 1.2 christos */
812 1.2 christos void
813 1.2 christos kadm5_log_signal_master(kadm5_server_context *context)
814 1.1 elric {
815 1.2 christos kadm5_log_context *log_context = &context->log_context;
816 1.2 christos #ifndef NO_UNIX_SOCKETS
817 1.2 christos sendto(log_context->socket_fd,
818 1.2 christos (void *)&log_context->version,
819 1.2 christos sizeof(log_context->version),
820 1.2 christos 0,
821 1.2 christos (struct sockaddr *)&log_context->socket_name,
822 1.2 christos sizeof(log_context->socket_name));
823 1.2 christos #else
824 1.2 christos sendto(log_context->socket_fd,
825 1.2 christos (void *)&log_context->version,
826 1.2 christos sizeof(log_context->version),
827 1.2 christos 0,
828 1.2 christos log_context->socket_info->ai_addr,
829 1.2 christos log_context->socket_info->ai_addrlen);
830 1.2 christos #endif
831 1.1 elric }
832 1.1 elric
833 1.1 elric /*
834 1.2 christos * Write sp's contents (which must be a fully formed record, complete
835 1.2 christos * with header, payload, and trailer) to the log and fsync the log.
836 1.2 christos *
837 1.2 christos * Does not free sp.
838 1.1 elric */
839 1.1 elric
840 1.1 elric static kadm5_ret_t
841 1.2 christos kadm5_log_flush(kadm5_server_context *context, krb5_storage *sp)
842 1.1 elric {
843 1.2 christos kadm5_log_context *log_context = &context->log_context;
844 1.2 christos kadm5_ret_t ret;
845 1.1 elric krb5_data data;
846 1.1 elric size_t len;
847 1.2 christos krb5_ssize_t bytes;
848 1.2 christos uint32_t new_ver, prev_ver;
849 1.2 christos off_t off, end;
850 1.2 christos
851 1.2 christos if (strcmp(log_context->log_file, "/dev/null") == 0)
852 1.2 christos return 0;
853 1.2 christos
854 1.2 christos if (log_context->read_only)
855 1.2 christos return EROFS;
856 1.2 christos
857 1.2 christos if (krb5_storage_seek(sp, 0, SEEK_SET) == -1)
858 1.2 christos return errno;
859 1.2 christos
860 1.2 christos ret = get_header(sp, LOG_DOPEEK, &new_ver, NULL, NULL, NULL);
861 1.2 christos if (ret)
862 1.2 christos return ret;
863 1.2 christos
864 1.2 christos ret = krb5_storage_to_data(sp, &data);
865 1.2 christos if (ret)
866 1.2 christos return ret;
867 1.2 christos
868 1.2 christos /* Abandon the emem storage reference */
869 1.2 christos sp = krb5_storage_from_fd(log_context->log_fd);
870 1.2 christos if (sp == NULL) {
871 1.2 christos krb5_data_free(&data);
872 1.2 christos return ENOMEM;
873 1.2 christos }
874 1.2 christos
875 1.2 christos /* Check that we are at the end of the log and fail if not */
876 1.2 christos off = krb5_storage_seek(sp, 0, SEEK_CUR);
877 1.2 christos if (off == -1) {
878 1.2 christos krb5_data_free(&data);
879 1.2 christos krb5_storage_free(sp);
880 1.2 christos return errno;
881 1.2 christos }
882 1.2 christos end = krb5_storage_seek(sp, 0, SEEK_END);
883 1.2 christos if (end == -1) {
884 1.2 christos krb5_data_free(&data);
885 1.2 christos krb5_storage_free(sp);
886 1.2 christos return errno;
887 1.2 christos }
888 1.2 christos if (end != off) {
889 1.2 christos krb5_data_free(&data);
890 1.2 christos krb5_storage_free(sp);
891 1.2 christos return KADM5_LOG_CORRUPT;
892 1.2 christos }
893 1.2 christos
894 1.2 christos /* Enforce monotonically incremented versioning of records */
895 1.2 christos if (seek_prev(sp, &prev_ver, NULL) == -1 ||
896 1.2 christos krb5_storage_seek(sp, end, SEEK_SET) == -1) {
897 1.2 christos ret = errno;
898 1.2 christos krb5_data_free(&data);
899 1.2 christos krb5_storage_free(sp);
900 1.2 christos return ret;
901 1.2 christos }
902 1.2 christos
903 1.2 christos if (prev_ver != 0 && prev_ver != log_context->version)
904 1.2 christos return EINVAL; /* Internal error, really; just a consistency check */
905 1.2 christos
906 1.2 christos if (prev_ver != 0 && new_ver != prev_ver + 1) {
907 1.2 christos krb5_warnx(context->context, "refusing to write a log record "
908 1.2 christos "with non-monotonic version (new: %u, old: %u)",
909 1.2 christos new_ver, prev_ver);
910 1.2 christos return KADM5_LOG_CORRUPT;
911 1.2 christos }
912 1.1 elric
913 1.1 elric len = data.length;
914 1.2 christos bytes = krb5_storage_write(sp, data.data, len);
915 1.2 christos krb5_data_free(&data);
916 1.2 christos if (bytes < 0) {
917 1.2 christos krb5_storage_free(sp);
918 1.1 elric return errno;
919 1.1 elric }
920 1.2 christos if (bytes != (krb5_ssize_t)len) {
921 1.2 christos krb5_storage_free(sp);
922 1.2 christos return EIO;
923 1.1 elric }
924 1.1 elric
925 1.2 christos ret = krb5_storage_fsync(sp);
926 1.2 christos krb5_storage_free(sp);
927 1.2 christos if (ret)
928 1.2 christos return ret;
929 1.1 elric
930 1.2 christos /* Retain the nominal database version when flushing the uber record */
931 1.2 christos if (new_ver != 0)
932 1.2 christos log_context->version = new_ver;
933 1.1 elric return 0;
934 1.1 elric }
935 1.1 elric
936 1.1 elric /*
937 1.2 christos * Add a `create' operation to the log and perform the create against the HDB.
938 1.1 elric */
939 1.1 elric kadm5_ret_t
940 1.2 christos kadm5_log_create(kadm5_server_context *context, hdb_entry *entry)
941 1.1 elric {
942 1.1 elric krb5_storage *sp;
943 1.1 elric kadm5_ret_t ret;
944 1.1 elric krb5_data value;
945 1.2 christos hdb_entry_ex ent;
946 1.1 elric kadm5_log_context *log_context = &context->log_context;
947 1.1 elric
948 1.2 christos memset(&ent, 0, sizeof(ent));
949 1.2 christos ent.ctx = 0;
950 1.2 christos ent.free_entry = 0;
951 1.2 christos ent.entry = *entry;
952 1.2 christos
953 1.2 christos /*
954 1.2 christos * If we're not logging then we can't recover-to-perform, so just
955 1.2 christos * perform.
956 1.2 christos */
957 1.2 christos if (strcmp(log_context->log_file, "/dev/null") == 0)
958 1.2 christos return context->db->hdb_store(context->context, context->db, 0, &ent);
959 1.2 christos
960 1.2 christos /*
961 1.2 christos * Test for any conflicting entries before writing the log. If we commit
962 1.2 christos * to the log we'll end-up rolling forward on recovery, but that would be
963 1.2 christos * wrong if the initial create is rejected.
964 1.2 christos */
965 1.2 christos ret = context->db->hdb_store(context->context, context->db,
966 1.2 christos HDB_F_PRECHECK, &ent);
967 1.2 christos if (ret == 0)
968 1.2 christos ret = hdb_entry2value(context->context, entry, &value);
969 1.2 christos if (ret)
970 1.2 christos return ret;
971 1.1 elric sp = krb5_storage_emem();
972 1.2 christos if (sp == NULL)
973 1.2 christos ret = ENOMEM;
974 1.2 christos if (ret == 0)
975 1.2 christos ret = kadm5_log_preamble(context, sp, kadm_create,
976 1.2 christos log_context->version + 1);
977 1.2 christos if (ret == 0)
978 1.2 christos ret = krb5_store_uint32(sp, value.length);
979 1.2 christos if (ret == 0) {
980 1.2 christos if (krb5_storage_write(sp, value.data, value.length) !=
981 1.2 christos (krb5_ssize_t)value.length)
982 1.2 christos ret = errno;
983 1.2 christos }
984 1.2 christos if (ret == 0)
985 1.2 christos ret = krb5_store_uint32(sp, value.length);
986 1.2 christos if (ret == 0)
987 1.2 christos ret = kadm5_log_postamble(log_context, sp,
988 1.2 christos log_context->version + 1);
989 1.2 christos if (ret == 0)
990 1.2 christos ret = kadm5_log_flush(context, sp);
991 1.2 christos krb5_storage_free(sp);
992 1.2 christos krb5_data_free(&value);
993 1.2 christos if (ret == 0)
994 1.2 christos ret = kadm5_log_recover(context, kadm_recover_commit);
995 1.1 elric return ret;
996 1.1 elric }
997 1.1 elric
998 1.1 elric /*
999 1.1 elric * Read the data of a create log record from `sp' and change the
1000 1.1 elric * database.
1001 1.1 elric */
1002 1.1 elric static kadm5_ret_t
1003 1.2 christos kadm5_log_replay_create(kadm5_server_context *context,
1004 1.2 christos uint32_t ver,
1005 1.2 christos uint32_t len,
1006 1.2 christos krb5_storage *sp)
1007 1.1 elric {
1008 1.1 elric krb5_error_code ret;
1009 1.1 elric krb5_data data;
1010 1.1 elric hdb_entry_ex ent;
1011 1.1 elric
1012 1.1 elric memset(&ent, 0, sizeof(ent));
1013 1.1 elric
1014 1.2 christos ret = krb5_data_alloc(&data, len);
1015 1.1 elric if (ret) {
1016 1.1 elric krb5_set_error_message(context->context, ret, "out of memory");
1017 1.1 elric return ret;
1018 1.1 elric }
1019 1.2 christos krb5_storage_read(sp, data.data, len);
1020 1.2 christos ret = hdb_value2entry(context->context, &data, &ent.entry);
1021 1.1 elric krb5_data_free(&data);
1022 1.1 elric if (ret) {
1023 1.1 elric krb5_set_error_message(context->context, ret,
1024 1.2 christos "Unmarshaling hdb entry in log failed, "
1025 1.2 christos "version: %ld", (long)ver);
1026 1.1 elric return ret;
1027 1.1 elric }
1028 1.1 elric ret = context->db->hdb_store(context->context, context->db, 0, &ent);
1029 1.2 christos hdb_free_entry(context->context, &ent);
1030 1.1 elric return ret;
1031 1.1 elric }
1032 1.1 elric
1033 1.1 elric /*
1034 1.1 elric * Add a `delete' operation to the log.
1035 1.1 elric */
1036 1.1 elric kadm5_ret_t
1037 1.2 christos kadm5_log_delete(kadm5_server_context *context,
1038 1.2 christos krb5_principal princ)
1039 1.1 elric {
1040 1.2 christos kadm5_ret_t ret;
1041 1.2 christos kadm5_log_context *log_context = &context->log_context;
1042 1.1 elric krb5_storage *sp;
1043 1.2 christos uint32_t len = 0; /* So dumb compilers don't warn */
1044 1.2 christos off_t end_off = 0; /* Ditto; this allows de-indentation by two levels */
1045 1.1 elric off_t off;
1046 1.1 elric
1047 1.2 christos if (strcmp(log_context->log_file, "/dev/null") == 0)
1048 1.2 christos return context->db->hdb_remove(context->context, context->db, 0,
1049 1.2 christos princ);
1050 1.2 christos ret = context->db->hdb_remove(context->context, context->db,
1051 1.2 christos HDB_F_PRECHECK, princ);
1052 1.2 christos if (ret)
1053 1.2 christos return ret;
1054 1.1 elric sp = krb5_storage_emem();
1055 1.1 elric if (sp == NULL)
1056 1.2 christos ret = ENOMEM;
1057 1.2 christos if (ret == 0)
1058 1.2 christos ret = kadm5_log_preamble(context, sp, kadm_delete,
1059 1.2 christos log_context->version + 1);
1060 1.2 christos if (ret) {
1061 1.2 christos krb5_storage_free(sp);
1062 1.2 christos return ret;
1063 1.2 christos }
1064 1.2 christos
1065 1.2 christos /*
1066 1.2 christos * Write a 0 length which we overwrite once we know the length of
1067 1.2 christos * the principal name payload.
1068 1.2 christos */
1069 1.2 christos off = krb5_storage_seek(sp, 0, SEEK_CUR);
1070 1.2 christos if (off == -1)
1071 1.2 christos ret = errno;
1072 1.2 christos if (ret == 0)
1073 1.2 christos ret = krb5_store_uint32(sp, 0);
1074 1.2 christos if (ret == 0)
1075 1.2 christos ret = krb5_store_principal(sp, princ);
1076 1.2 christos if (ret == 0) {
1077 1.2 christos end_off = krb5_storage_seek(sp, 0, SEEK_CUR);
1078 1.2 christos if (end_off == -1)
1079 1.2 christos ret = errno;
1080 1.2 christos else if (end_off < off)
1081 1.2 christos ret = KADM5_LOG_CORRUPT;
1082 1.2 christos }
1083 1.2 christos if (ret == 0) {
1084 1.2 christos /* We wrote sizeof(uint32_t) + payload length bytes */
1085 1.2 christos len = (uint32_t)(end_off - off);
1086 1.2 christos if (end_off - off != len || len < sizeof(len))
1087 1.2 christos ret = KADM5_LOG_CORRUPT;
1088 1.2 christos else
1089 1.2 christos len -= sizeof(len);
1090 1.2 christos }
1091 1.2 christos if (ret == 0 && krb5_storage_seek(sp, off, SEEK_SET) == -1)
1092 1.2 christos ret = errno;
1093 1.2 christos if (ret == 0)
1094 1.2 christos ret = krb5_store_uint32(sp, len);
1095 1.2 christos if (ret == 0 && krb5_storage_seek(sp, end_off, SEEK_SET) == -1)
1096 1.2 christos ret = errno;
1097 1.2 christos if (ret == 0)
1098 1.2 christos ret = krb5_store_uint32(sp, len);
1099 1.2 christos if (ret == 0)
1100 1.2 christos ret = kadm5_log_postamble(log_context, sp,
1101 1.2 christos log_context->version + 1);
1102 1.2 christos if (ret == 0)
1103 1.2 christos ret = kadm5_log_flush(context, sp);
1104 1.2 christos if (ret == 0)
1105 1.2 christos ret = kadm5_log_recover(context, kadm_recover_commit);
1106 1.2 christos krb5_storage_free(sp);
1107 1.1 elric return ret;
1108 1.1 elric }
1109 1.1 elric
1110 1.1 elric /*
1111 1.1 elric * Read a `delete' log operation from `sp' and apply it.
1112 1.1 elric */
1113 1.1 elric static kadm5_ret_t
1114 1.2 christos kadm5_log_replay_delete(kadm5_server_context *context,
1115 1.2 christos uint32_t ver, uint32_t len, krb5_storage *sp)
1116 1.1 elric {
1117 1.1 elric krb5_error_code ret;
1118 1.1 elric krb5_principal principal;
1119 1.1 elric
1120 1.2 christos ret = krb5_ret_principal(sp, &principal);
1121 1.1 elric if (ret) {
1122 1.1 elric krb5_set_error_message(context->context, ret, "Failed to read deleted "
1123 1.1 elric "principal from log version: %ld", (long)ver);
1124 1.1 elric return ret;
1125 1.1 elric }
1126 1.1 elric
1127 1.2 christos ret = context->db->hdb_remove(context->context, context->db, 0, principal);
1128 1.2 christos krb5_free_principal(context->context, principal);
1129 1.1 elric return ret;
1130 1.1 elric }
1131 1.1 elric
1132 1.2 christos static kadm5_ret_t kadm5_log_replay_rename(kadm5_server_context *,
1133 1.2 christos uint32_t, uint32_t,
1134 1.2 christos krb5_storage *);
1135 1.2 christos
1136 1.1 elric /*
1137 1.1 elric * Add a `rename' operation to the log.
1138 1.1 elric */
1139 1.1 elric kadm5_ret_t
1140 1.2 christos kadm5_log_rename(kadm5_server_context *context,
1141 1.2 christos krb5_principal source,
1142 1.2 christos hdb_entry *entry)
1143 1.1 elric {
1144 1.1 elric krb5_storage *sp;
1145 1.1 elric kadm5_ret_t ret;
1146 1.2 christos uint32_t len = 0; /* So dumb compilers don't warn */
1147 1.2 christos off_t end_off = 0; /* Ditto; this allows de-indentation by two levels */
1148 1.1 elric off_t off;
1149 1.1 elric krb5_data value;
1150 1.2 christos hdb_entry_ex ent;
1151 1.1 elric kadm5_log_context *log_context = &context->log_context;
1152 1.1 elric
1153 1.2 christos memset(&ent, 0, sizeof(ent));
1154 1.2 christos ent.ctx = 0;
1155 1.2 christos ent.free_entry = 0;
1156 1.2 christos ent.entry = *entry;
1157 1.2 christos
1158 1.2 christos if (strcmp(log_context->log_file, "/dev/null") == 0) {
1159 1.2 christos ret = context->db->hdb_store(context->context, context->db, 0, &ent);
1160 1.2 christos if (ret == 0)
1161 1.2 christos return context->db->hdb_remove(context->context, context->db, 0,
1162 1.2 christos source);
1163 1.2 christos return ret;
1164 1.2 christos }
1165 1.1 elric
1166 1.2 christos /*
1167 1.2 christos * Pre-check that the transaction will succeed.
1168 1.2 christos *
1169 1.2 christos * Note that rename doesn't work to swap a principal's canonical
1170 1.2 christos * name with one of its aliases. To make that work would require
1171 1.2 christos * adding an hdb_rename() method for renaming principals (there's an
1172 1.2 christos * hdb_rename() method already, but for renaming the HDB), which is
1173 1.2 christos * ETOOMUCHWORK for the time being.
1174 1.2 christos */
1175 1.2 christos ret = context->db->hdb_store(context->context, context->db,
1176 1.2 christos HDB_F_PRECHECK, &ent);
1177 1.2 christos if (ret == 0)
1178 1.2 christos ret = context->db->hdb_remove(context->context, context->db,
1179 1.2 christos HDB_F_PRECHECK, source);
1180 1.1 elric if (ret)
1181 1.2 christos return ret;
1182 1.1 elric
1183 1.2 christos sp = krb5_storage_emem();
1184 1.2 christos krb5_data_zero(&value);
1185 1.2 christos if (sp == NULL)
1186 1.2 christos ret = ENOMEM;
1187 1.2 christos if (ret == 0)
1188 1.2 christos ret = kadm5_log_preamble(context, sp, kadm_rename,
1189 1.2 christos log_context->version + 1);
1190 1.2 christos if (ret == 0)
1191 1.2 christos ret = hdb_entry2value(context->context, entry, &value);
1192 1.2 christos if (ret) {
1193 1.2 christos krb5_data_free(&value);
1194 1.2 christos krb5_storage_free(sp);
1195 1.2 christos return ret;
1196 1.2 christos }
1197 1.1 elric
1198 1.2 christos /*
1199 1.2 christos * Write a zero length which we'll overwrite once we know the length of the
1200 1.2 christos * payload.
1201 1.2 christos */
1202 1.2 christos off = krb5_storage_seek(sp, 0, SEEK_CUR);
1203 1.2 christos if (off == -1)
1204 1.2 christos ret = errno;
1205 1.2 christos if (ret == 0)
1206 1.2 christos ret = krb5_store_uint32(sp, 0);
1207 1.2 christos if (ret == 0)
1208 1.2 christos ret = krb5_store_principal(sp, source);
1209 1.2 christos if (ret == 0) {
1210 1.2 christos errno = 0;
1211 1.2 christos if (krb5_storage_write(sp, value.data, value.length) !=
1212 1.2 christos (krb5_ssize_t)value.length)
1213 1.2 christos ret = errno ? errno : EIO;
1214 1.2 christos }
1215 1.2 christos if (ret == 0) {
1216 1.2 christos end_off = krb5_storage_seek(sp, 0, SEEK_CUR);
1217 1.2 christos if (end_off == -1)
1218 1.2 christos ret = errno;
1219 1.2 christos else if (end_off < off)
1220 1.2 christos ret = KADM5_LOG_CORRUPT;
1221 1.2 christos }
1222 1.2 christos if (ret == 0) {
1223 1.2 christos /* We wrote sizeof(uint32_t) + payload length bytes */
1224 1.2 christos len = (uint32_t)(end_off - off);
1225 1.2 christos if (end_off - off != len || len < sizeof(len))
1226 1.2 christos ret = KADM5_LOG_CORRUPT;
1227 1.2 christos else
1228 1.2 christos len -= sizeof(len);
1229 1.2 christos if (ret == 0 && krb5_storage_seek(sp, off, SEEK_SET) == -1)
1230 1.2 christos ret = errno;
1231 1.2 christos if (ret == 0)
1232 1.2 christos ret = krb5_store_uint32(sp, len);
1233 1.2 christos if (ret == 0 && krb5_storage_seek(sp, end_off, SEEK_SET) == -1)
1234 1.2 christos ret = errno;
1235 1.2 christos if (ret == 0)
1236 1.2 christos ret = krb5_store_uint32(sp, len);
1237 1.2 christos if (ret == 0)
1238 1.2 christos ret = kadm5_log_postamble(log_context, sp,
1239 1.2 christos log_context->version + 1);
1240 1.2 christos if (ret == 0)
1241 1.2 christos ret = kadm5_log_flush(context, sp);
1242 1.2 christos if (ret == 0)
1243 1.2 christos ret = kadm5_log_recover(context, kadm_recover_commit);
1244 1.2 christos }
1245 1.1 elric krb5_data_free(&value);
1246 1.1 elric krb5_storage_free(sp);
1247 1.1 elric return ret;
1248 1.1 elric }
1249 1.1 elric
1250 1.1 elric /*
1251 1.1 elric * Read a `rename' log operation from `sp' and apply it.
1252 1.1 elric */
1253 1.1 elric
1254 1.1 elric static kadm5_ret_t
1255 1.2 christos kadm5_log_replay_rename(kadm5_server_context *context,
1256 1.2 christos uint32_t ver,
1257 1.2 christos uint32_t len,
1258 1.2 christos krb5_storage *sp)
1259 1.1 elric {
1260 1.1 elric krb5_error_code ret;
1261 1.1 elric krb5_principal source;
1262 1.1 elric hdb_entry_ex target_ent;
1263 1.1 elric krb5_data value;
1264 1.1 elric off_t off;
1265 1.1 elric size_t princ_len, data_len;
1266 1.1 elric
1267 1.1 elric memset(&target_ent, 0, sizeof(target_ent));
1268 1.1 elric
1269 1.1 elric off = krb5_storage_seek(sp, 0, SEEK_CUR);
1270 1.2 christos ret = krb5_ret_principal(sp, &source);
1271 1.1 elric if (ret) {
1272 1.1 elric krb5_set_error_message(context->context, ret, "Failed to read renamed "
1273 1.1 elric "principal in log, version: %ld", (long)ver);
1274 1.1 elric return ret;
1275 1.1 elric }
1276 1.1 elric princ_len = krb5_storage_seek(sp, 0, SEEK_CUR) - off;
1277 1.1 elric data_len = len - princ_len;
1278 1.2 christos ret = krb5_data_alloc(&value, data_len);
1279 1.1 elric if (ret) {
1280 1.1 elric krb5_free_principal (context->context, source);
1281 1.1 elric return ret;
1282 1.1 elric }
1283 1.2 christos krb5_storage_read(sp, value.data, data_len);
1284 1.2 christos ret = hdb_value2entry(context->context, &value, &target_ent.entry);
1285 1.1 elric krb5_data_free(&value);
1286 1.1 elric if (ret) {
1287 1.2 christos krb5_free_principal(context->context, source);
1288 1.1 elric return ret;
1289 1.1 elric }
1290 1.2 christos ret = context->db->hdb_store(context->context, context->db,
1291 1.2 christos 0, &target_ent);
1292 1.2 christos hdb_free_entry(context->context, &target_ent);
1293 1.1 elric if (ret) {
1294 1.2 christos krb5_free_principal(context->context, source);
1295 1.1 elric return ret;
1296 1.1 elric }
1297 1.2 christos ret = context->db->hdb_remove(context->context, context->db, 0, source);
1298 1.2 christos krb5_free_principal(context->context, source);
1299 1.2 christos
1300 1.1 elric return ret;
1301 1.1 elric }
1302 1.1 elric
1303 1.1 elric /*
1304 1.1 elric * Add a `modify' operation to the log.
1305 1.1 elric */
1306 1.1 elric kadm5_ret_t
1307 1.2 christos kadm5_log_modify(kadm5_server_context *context,
1308 1.2 christos hdb_entry *entry,
1309 1.2 christos uint32_t mask)
1310 1.1 elric {
1311 1.1 elric krb5_storage *sp;
1312 1.1 elric kadm5_ret_t ret;
1313 1.1 elric krb5_data value;
1314 1.1 elric uint32_t len;
1315 1.2 christos hdb_entry_ex ent;
1316 1.1 elric kadm5_log_context *log_context = &context->log_context;
1317 1.1 elric
1318 1.2 christos memset(&ent, 0, sizeof(ent));
1319 1.2 christos ent.ctx = 0;
1320 1.2 christos ent.free_entry = 0;
1321 1.2 christos ent.entry = *entry;
1322 1.2 christos
1323 1.2 christos if (strcmp(log_context->log_file, "/dev/null") == 0)
1324 1.2 christos return context->db->hdb_store(context->context, context->db,
1325 1.2 christos HDB_F_REPLACE, &ent);
1326 1.1 elric
1327 1.2 christos ret = context->db->hdb_store(context->context, context->db,
1328 1.2 christos HDB_F_PRECHECK | HDB_F_REPLACE, &ent);
1329 1.1 elric if (ret)
1330 1.2 christos return ret;
1331 1.1 elric
1332 1.2 christos sp = krb5_storage_emem();
1333 1.2 christos krb5_data_zero(&value);
1334 1.2 christos if (sp == NULL)
1335 1.2 christos ret = ENOMEM;
1336 1.2 christos if (ret == 0)
1337 1.2 christos ret = hdb_entry2value(context->context, entry, &value);
1338 1.2 christos if (ret) {
1339 1.2 christos krb5_data_free(&value);
1340 1.2 christos krb5_storage_free(sp);
1341 1.2 christos return ret;
1342 1.2 christos }
1343 1.1 elric
1344 1.2 christos len = value.length + sizeof(len);
1345 1.2 christos if (value.length > len || len > INT32_MAX)
1346 1.2 christos ret = E2BIG;
1347 1.2 christos if (ret == 0)
1348 1.2 christos ret = kadm5_log_preamble(context, sp, kadm_modify,
1349 1.2 christos log_context->version + 1);
1350 1.2 christos if (ret == 0)
1351 1.2 christos ret = krb5_store_uint32(sp, len);
1352 1.2 christos if (ret == 0)
1353 1.2 christos ret = krb5_store_uint32(sp, mask);
1354 1.2 christos if (ret == 0) {
1355 1.2 christos if (krb5_storage_write(sp, value.data, value.length) !=
1356 1.2 christos (krb5_ssize_t)value.length)
1357 1.2 christos ret = errno;
1358 1.2 christos }
1359 1.2 christos if (ret == 0)
1360 1.2 christos ret = krb5_store_uint32(sp, len);
1361 1.2 christos if (ret == 0)
1362 1.2 christos ret = kadm5_log_postamble(log_context, sp,
1363 1.2 christos log_context->version + 1);
1364 1.2 christos if (ret == 0)
1365 1.2 christos ret = kadm5_log_flush(context, sp);
1366 1.2 christos if (ret == 0)
1367 1.2 christos ret = kadm5_log_recover(context, kadm_recover_commit);
1368 1.1 elric krb5_data_free(&value);
1369 1.1 elric krb5_storage_free(sp);
1370 1.1 elric return ret;
1371 1.1 elric }
1372 1.1 elric
1373 1.1 elric /*
1374 1.1 elric * Read a `modify' log operation from `sp' and apply it.
1375 1.1 elric */
1376 1.1 elric static kadm5_ret_t
1377 1.2 christos kadm5_log_replay_modify(kadm5_server_context *context,
1378 1.2 christos uint32_t ver,
1379 1.2 christos uint32_t len,
1380 1.2 christos krb5_storage *sp)
1381 1.1 elric {
1382 1.1 elric krb5_error_code ret;
1383 1.2 christos uint32_t mask;
1384 1.1 elric krb5_data value;
1385 1.1 elric hdb_entry_ex ent, log_ent;
1386 1.1 elric
1387 1.1 elric memset(&log_ent, 0, sizeof(log_ent));
1388 1.1 elric
1389 1.2 christos ret = krb5_ret_uint32(sp, &mask);
1390 1.2 christos if (ret)
1391 1.2 christos return ret;
1392 1.1 elric len -= 4;
1393 1.1 elric ret = krb5_data_alloc (&value, len);
1394 1.1 elric if (ret) {
1395 1.1 elric krb5_set_error_message(context->context, ret, "out of memory");
1396 1.1 elric return ret;
1397 1.1 elric }
1398 1.2 christos errno = 0;
1399 1.2 christos if (krb5_storage_read (sp, value.data, len) != (krb5_ssize_t)len) {
1400 1.2 christos ret = errno ? errno : EIO;
1401 1.2 christos return ret;
1402 1.2 christos }
1403 1.1 elric ret = hdb_value2entry (context->context, &value, &log_ent.entry);
1404 1.1 elric krb5_data_free(&value);
1405 1.1 elric if (ret)
1406 1.1 elric return ret;
1407 1.1 elric
1408 1.1 elric memset(&ent, 0, sizeof(ent));
1409 1.1 elric ret = context->db->hdb_fetch_kvno(context->context, context->db,
1410 1.1 elric log_ent.entry.principal,
1411 1.2 christos HDB_F_DECRYPT|HDB_F_ALL_KVNOS|
1412 1.2 christos HDB_F_GET_ANY|HDB_F_ADMIN_DATA, 0, &ent);
1413 1.1 elric if (ret)
1414 1.1 elric goto out;
1415 1.1 elric if (mask & KADM5_PRINC_EXPIRE_TIME) {
1416 1.1 elric if (log_ent.entry.valid_end == NULL) {
1417 1.1 elric ent.entry.valid_end = NULL;
1418 1.1 elric } else {
1419 1.1 elric if (ent.entry.valid_end == NULL) {
1420 1.1 elric ent.entry.valid_end = malloc(sizeof(*ent.entry.valid_end));
1421 1.1 elric if (ent.entry.valid_end == NULL) {
1422 1.1 elric ret = ENOMEM;
1423 1.1 elric krb5_set_error_message(context->context, ret, "out of memory");
1424 1.1 elric goto out;
1425 1.1 elric }
1426 1.1 elric }
1427 1.1 elric *ent.entry.valid_end = *log_ent.entry.valid_end;
1428 1.1 elric }
1429 1.1 elric }
1430 1.1 elric if (mask & KADM5_PW_EXPIRATION) {
1431 1.1 elric if (log_ent.entry.pw_end == NULL) {
1432 1.1 elric ent.entry.pw_end = NULL;
1433 1.1 elric } else {
1434 1.1 elric if (ent.entry.pw_end == NULL) {
1435 1.1 elric ent.entry.pw_end = malloc(sizeof(*ent.entry.pw_end));
1436 1.1 elric if (ent.entry.pw_end == NULL) {
1437 1.1 elric ret = ENOMEM;
1438 1.1 elric krb5_set_error_message(context->context, ret, "out of memory");
1439 1.1 elric goto out;
1440 1.1 elric }
1441 1.1 elric }
1442 1.1 elric *ent.entry.pw_end = *log_ent.entry.pw_end;
1443 1.1 elric }
1444 1.1 elric }
1445 1.1 elric if (mask & KADM5_LAST_PWD_CHANGE) {
1446 1.2 christos krb5_warnx (context->context,
1447 1.2 christos "Unimplemented mask KADM5_LAST_PWD_CHANGE");
1448 1.1 elric }
1449 1.1 elric if (mask & KADM5_ATTRIBUTES) {
1450 1.1 elric ent.entry.flags = log_ent.entry.flags;
1451 1.1 elric }
1452 1.1 elric if (mask & KADM5_MAX_LIFE) {
1453 1.1 elric if (log_ent.entry.max_life == NULL) {
1454 1.1 elric ent.entry.max_life = NULL;
1455 1.1 elric } else {
1456 1.1 elric if (ent.entry.max_life == NULL) {
1457 1.1 elric ent.entry.max_life = malloc (sizeof(*ent.entry.max_life));
1458 1.1 elric if (ent.entry.max_life == NULL) {
1459 1.1 elric ret = ENOMEM;
1460 1.1 elric krb5_set_error_message(context->context, ret, "out of memory");
1461 1.1 elric goto out;
1462 1.1 elric }
1463 1.1 elric }
1464 1.1 elric *ent.entry.max_life = *log_ent.entry.max_life;
1465 1.1 elric }
1466 1.1 elric }
1467 1.1 elric if ((mask & KADM5_MOD_TIME) && (mask & KADM5_MOD_NAME)) {
1468 1.1 elric if (ent.entry.modified_by == NULL) {
1469 1.1 elric ent.entry.modified_by = malloc(sizeof(*ent.entry.modified_by));
1470 1.1 elric if (ent.entry.modified_by == NULL) {
1471 1.1 elric ret = ENOMEM;
1472 1.1 elric krb5_set_error_message(context->context, ret, "out of memory");
1473 1.1 elric goto out;
1474 1.1 elric }
1475 1.1 elric } else
1476 1.1 elric free_Event(ent.entry.modified_by);
1477 1.1 elric ret = copy_Event(log_ent.entry.modified_by, ent.entry.modified_by);
1478 1.1 elric if (ret) {
1479 1.1 elric krb5_set_error_message(context->context, ret, "out of memory");
1480 1.1 elric goto out;
1481 1.1 elric }
1482 1.1 elric }
1483 1.1 elric if (mask & KADM5_KVNO) {
1484 1.1 elric ent.entry.kvno = log_ent.entry.kvno;
1485 1.1 elric }
1486 1.1 elric if (mask & KADM5_MKVNO) {
1487 1.2 christos krb5_warnx(context->context, "Unimplemented mask KADM5_KVNO");
1488 1.1 elric }
1489 1.1 elric if (mask & KADM5_AUX_ATTRIBUTES) {
1490 1.2 christos krb5_warnx(context->context,
1491 1.2 christos "Unimplemented mask KADM5_AUX_ATTRIBUTES");
1492 1.1 elric }
1493 1.1 elric if (mask & KADM5_POLICY_CLR) {
1494 1.2 christos krb5_warnx(context->context, "Unimplemented mask KADM5_POLICY_CLR");
1495 1.1 elric }
1496 1.1 elric if (mask & KADM5_MAX_RLIFE) {
1497 1.1 elric if (log_ent.entry.max_renew == NULL) {
1498 1.1 elric ent.entry.max_renew = NULL;
1499 1.1 elric } else {
1500 1.1 elric if (ent.entry.max_renew == NULL) {
1501 1.1 elric ent.entry.max_renew = malloc (sizeof(*ent.entry.max_renew));
1502 1.1 elric if (ent.entry.max_renew == NULL) {
1503 1.1 elric ret = ENOMEM;
1504 1.1 elric krb5_set_error_message(context->context, ret, "out of memory");
1505 1.1 elric goto out;
1506 1.1 elric }
1507 1.1 elric }
1508 1.1 elric *ent.entry.max_renew = *log_ent.entry.max_renew;
1509 1.1 elric }
1510 1.1 elric }
1511 1.1 elric if (mask & KADM5_LAST_SUCCESS) {
1512 1.2 christos krb5_warnx(context->context, "Unimplemented mask KADM5_LAST_SUCCESS");
1513 1.1 elric }
1514 1.1 elric if (mask & KADM5_LAST_FAILED) {
1515 1.2 christos krb5_warnx(context->context, "Unimplemented mask KADM5_LAST_FAILED");
1516 1.1 elric }
1517 1.1 elric if (mask & KADM5_FAIL_AUTH_COUNT) {
1518 1.2 christos krb5_warnx(context->context,
1519 1.2 christos "Unimplemented mask KADM5_FAIL_AUTH_COUNT");
1520 1.1 elric }
1521 1.1 elric if (mask & KADM5_KEY_DATA) {
1522 1.1 elric size_t num;
1523 1.2 christos size_t i;
1524 1.2 christos
1525 1.2 christos /*
1526 1.2 christos * We don't need to do anything about key history here because
1527 1.2 christos * the log entry contains a complete entry, including hdb
1528 1.2 christos * extensions. We do need to make sure that KADM5_TL_DATA is in
1529 1.2 christos * the mask though, since that's what it takes to update the
1530 1.2 christos * extensions (see below).
1531 1.2 christos */
1532 1.2 christos mask |= KADM5_TL_DATA;
1533 1.1 elric
1534 1.1 elric for (i = 0; i < ent.entry.keys.len; ++i)
1535 1.1 elric free_Key(&ent.entry.keys.val[i]);
1536 1.1 elric free (ent.entry.keys.val);
1537 1.1 elric
1538 1.1 elric num = log_ent.entry.keys.len;
1539 1.1 elric
1540 1.1 elric ent.entry.keys.len = num;
1541 1.1 elric ent.entry.keys.val = malloc(len * sizeof(*ent.entry.keys.val));
1542 1.1 elric if (ent.entry.keys.val == NULL) {
1543 1.1 elric krb5_set_error_message(context->context, ENOMEM, "out of memory");
1544 1.2 christos ret = ENOMEM;
1545 1.2 christos goto out;
1546 1.1 elric }
1547 1.1 elric for (i = 0; i < ent.entry.keys.len; ++i) {
1548 1.1 elric ret = copy_Key(&log_ent.entry.keys.val[i],
1549 1.1 elric &ent.entry.keys.val[i]);
1550 1.1 elric if (ret) {
1551 1.1 elric krb5_set_error_message(context->context, ret, "out of memory");
1552 1.1 elric goto out;
1553 1.1 elric }
1554 1.1 elric }
1555 1.1 elric }
1556 1.1 elric if ((mask & KADM5_TL_DATA) && log_ent.entry.extensions) {
1557 1.1 elric HDB_extensions *es = ent.entry.extensions;
1558 1.1 elric
1559 1.1 elric ent.entry.extensions = calloc(1, sizeof(*ent.entry.extensions));
1560 1.1 elric if (ent.entry.extensions == NULL)
1561 1.1 elric goto out;
1562 1.1 elric
1563 1.1 elric ret = copy_HDB_extensions(log_ent.entry.extensions,
1564 1.1 elric ent.entry.extensions);
1565 1.1 elric if (ret) {
1566 1.1 elric krb5_set_error_message(context->context, ret, "out of memory");
1567 1.1 elric free(ent.entry.extensions);
1568 1.1 elric ent.entry.extensions = es;
1569 1.1 elric goto out;
1570 1.1 elric }
1571 1.1 elric if (es) {
1572 1.1 elric free_HDB_extensions(es);
1573 1.1 elric free(es);
1574 1.1 elric }
1575 1.1 elric }
1576 1.1 elric ret = context->db->hdb_store(context->context, context->db,
1577 1.1 elric HDB_F_REPLACE, &ent);
1578 1.1 elric out:
1579 1.2 christos hdb_free_entry(context->context, &ent);
1580 1.2 christos hdb_free_entry(context->context, &log_ent);
1581 1.2 christos return ret;
1582 1.2 christos }
1583 1.2 christos
1584 1.2 christos /*
1585 1.2 christos * Update the first entry (which should be a `nop'), the "uber-entry".
1586 1.2 christos */
1587 1.2 christos static kadm5_ret_t
1588 1.2 christos log_update_uber(kadm5_server_context *context, off_t off)
1589 1.2 christos {
1590 1.2 christos kadm5_log_context *log_context = &context->log_context;
1591 1.2 christos kadm5_ret_t ret = 0;
1592 1.2 christos krb5_storage *sp, *mem_sp;
1593 1.2 christos krb5_data data;
1594 1.2 christos uint32_t op, len;
1595 1.2 christos ssize_t bytes;
1596 1.2 christos
1597 1.2 christos if (strcmp(log_context->log_file, "/dev/null") == 0)
1598 1.2 christos return 0;
1599 1.2 christos
1600 1.2 christos if (log_context->read_only)
1601 1.2 christos return EROFS;
1602 1.2 christos
1603 1.2 christos krb5_data_zero(&data);
1604 1.2 christos
1605 1.2 christos mem_sp = krb5_storage_emem();
1606 1.2 christos if (mem_sp == NULL)
1607 1.2 christos return ENOMEM;
1608 1.2 christos
1609 1.2 christos sp = krb5_storage_from_fd(log_context->log_fd);
1610 1.2 christos if (sp == NULL) {
1611 1.2 christos krb5_storage_free(mem_sp);
1612 1.2 christos return ENOMEM;
1613 1.2 christos }
1614 1.2 christos
1615 1.2 christos /* Skip first entry's version and timestamp */
1616 1.2 christos if (krb5_storage_seek(sp, 8, SEEK_SET) == -1) {
1617 1.2 christos ret = errno;
1618 1.2 christos goto out;
1619 1.2 christos }
1620 1.2 christos
1621 1.2 christos /* If the first entry is not a nop, there's nothing we can do here */
1622 1.2 christos ret = krb5_ret_uint32(sp, &op);
1623 1.2 christos if (ret || op != kadm_nop)
1624 1.2 christos goto out;
1625 1.2 christos
1626 1.2 christos /* If the first entry is not a 16-byte nop, ditto */
1627 1.2 christos ret = krb5_ret_uint32(sp, &len);
1628 1.2 christos if (ret || len != LOG_UBER_LEN)
1629 1.2 christos goto out;
1630 1.2 christos
1631 1.2 christos /*
1632 1.2 christos * Try to make the writes here as close to atomic as possible: a
1633 1.2 christos * single write() call.
1634 1.2 christos */
1635 1.2 christos ret = krb5_store_uint64(mem_sp, off);
1636 1.2 christos if (ret)
1637 1.2 christos goto out;
1638 1.2 christos ret = krb5_store_uint32(mem_sp, log_context->last_time);
1639 1.2 christos if (ret)
1640 1.2 christos goto out;
1641 1.2 christos ret = krb5_store_uint32(mem_sp, log_context->version);
1642 1.2 christos if (ret)
1643 1.2 christos goto out;
1644 1.2 christos
1645 1.2 christos krb5_storage_to_data(mem_sp, &data);
1646 1.2 christos bytes = krb5_storage_write(sp, data.data, data.length);
1647 1.2 christos if (bytes < 0)
1648 1.2 christos ret = errno;
1649 1.2 christos else if (bytes != data.length)
1650 1.2 christos ret = EIO;
1651 1.2 christos
1652 1.2 christos /*
1653 1.2 christos * We don't fsync() this write because we can recover if the write
1654 1.2 christos * doesn't complete, though for now we don't have code for properly
1655 1.2 christos * dealing with the offset not getting written completely.
1656 1.2 christos *
1657 1.2 christos * We should probably have two copies of the offset so we can use
1658 1.2 christos * one copy to verify the other, and when they don't match we could
1659 1.2 christos * traverse the whole log forwards, replaying just the last entry.
1660 1.2 christos */
1661 1.2 christos
1662 1.2 christos out:
1663 1.2 christos if (ret == 0)
1664 1.2 christos kadm5_log_signal_master(context);
1665 1.2 christos krb5_data_free(&data);
1666 1.2 christos krb5_storage_free(sp);
1667 1.2 christos krb5_storage_free(mem_sp);
1668 1.2 christos if (lseek(log_context->log_fd, off, SEEK_SET) == -1)
1669 1.2 christos ret = ret ? ret : errno;
1670 1.2 christos
1671 1.1 elric return ret;
1672 1.1 elric }
1673 1.1 elric
1674 1.1 elric /*
1675 1.1 elric * Add a `nop' operation to the log. Does not close the log.
1676 1.1 elric */
1677 1.1 elric kadm5_ret_t
1678 1.2 christos kadm5_log_nop(kadm5_server_context *context, enum kadm_nop_type nop_type)
1679 1.1 elric {
1680 1.1 elric krb5_storage *sp;
1681 1.1 elric kadm5_ret_t ret;
1682 1.1 elric kadm5_log_context *log_context = &context->log_context;
1683 1.2 christos off_t off;
1684 1.2 christos uint32_t vno = log_context->version;
1685 1.2 christos
1686 1.2 christos if (strcmp(log_context->log_file, "/dev/null") == 0)
1687 1.2 christos return 0;
1688 1.2 christos
1689 1.2 christos off = lseek(log_context->log_fd, 0, SEEK_CUR);
1690 1.2 christos if (off == -1)
1691 1.2 christos return errno;
1692 1.1 elric
1693 1.1 elric sp = krb5_storage_emem();
1694 1.2 christos ret = kadm5_log_preamble(context, sp, kadm_nop, off == 0 ? 0 : vno + 1);
1695 1.2 christos if (ret)
1696 1.2 christos goto out;
1697 1.2 christos
1698 1.2 christos if (off == 0) {
1699 1.2 christos /*
1700 1.2 christos * First entry (uber-entry) gets room for offset of next new
1701 1.2 christos * entry and time and version of last entry.
1702 1.2 christos */
1703 1.2 christos ret = krb5_store_uint32(sp, LOG_UBER_LEN);
1704 1.2 christos /* These get overwritten with the same values below */
1705 1.2 christos if (ret == 0)
1706 1.2 christos ret = krb5_store_uint64(sp, LOG_UBER_SZ);
1707 1.2 christos if (ret == 0)
1708 1.2 christos ret = krb5_store_uint32(sp, log_context->last_time);
1709 1.2 christos if (ret == 0)
1710 1.2 christos ret = krb5_store_uint32(sp, vno);
1711 1.2 christos if (ret == 0)
1712 1.2 christos ret = krb5_store_uint32(sp, LOG_UBER_LEN);
1713 1.2 christos } else if (nop_type == kadm_nop_plain) {
1714 1.2 christos ret = krb5_store_uint32(sp, 0);
1715 1.2 christos if (ret == 0)
1716 1.2 christos ret = krb5_store_uint32(sp, 0);
1717 1.2 christos } else {
1718 1.2 christos ret = krb5_store_uint32(sp, sizeof(uint32_t));
1719 1.2 christos if (ret == 0)
1720 1.2 christos ret = krb5_store_uint32(sp, nop_type);
1721 1.2 christos if (ret == 0)
1722 1.2 christos ret = krb5_store_uint32(sp, sizeof(uint32_t));
1723 1.2 christos }
1724 1.2 christos
1725 1.2 christos if (ret == 0)
1726 1.2 christos ret = kadm5_log_postamble(log_context, sp, off == 0 ? 0 : vno + 1);
1727 1.2 christos if (ret == 0)
1728 1.2 christos ret = kadm5_log_flush(context, sp);
1729 1.2 christos
1730 1.2 christos if (ret == 0 && off == 0 && nop_type != kadm_nop_plain)
1731 1.2 christos ret = kadm5_log_nop(context, nop_type);
1732 1.2 christos
1733 1.2 christos if (ret == 0 && off != 0)
1734 1.2 christos ret = kadm5_log_recover(context, kadm_recover_commit);
1735 1.1 elric
1736 1.2 christos out:
1737 1.2 christos krb5_storage_free(sp);
1738 1.1 elric return ret;
1739 1.1 elric }
1740 1.1 elric
1741 1.1 elric /*
1742 1.2 christos * Read a `nop' log operation from `sp' and "apply" it (there's nothing
1743 1.2 christos * to do).
1744 1.2 christos *
1745 1.2 christos * FIXME Actually, if the nop payload is 4 bytes and contains an enum
1746 1.2 christos * kadm_nop_type value of kadm_nop_trunc then we should truncate the
1747 1.2 christos * log, and if it contains a kadm_nop_close then we should rename a new
1748 1.2 christos * log into place. However, this is not implemented yet.
1749 1.1 elric */
1750 1.1 elric static kadm5_ret_t
1751 1.2 christos kadm5_log_replay_nop(kadm5_server_context *context,
1752 1.2 christos uint32_t ver,
1753 1.2 christos uint32_t len,
1754 1.2 christos krb5_storage *sp)
1755 1.1 elric {
1756 1.1 elric return 0;
1757 1.1 elric }
1758 1.1 elric
1759 1.2 christos struct replay_cb_data {
1760 1.2 christos size_t count;
1761 1.2 christos uint32_t ver;
1762 1.2 christos enum kadm_recover_mode mode;
1763 1.2 christos };
1764 1.2 christos
1765 1.2 christos
1766 1.1 elric /*
1767 1.2 christos * Recover or perform the initial commit of an unconfirmed log entry
1768 1.1 elric */
1769 1.2 christos static kadm5_ret_t
1770 1.2 christos recover_replay(kadm5_server_context *context,
1771 1.2 christos uint32_t ver, time_t timestamp, enum kadm_ops op,
1772 1.2 christos uint32_t len, krb5_storage *sp, void *ctx)
1773 1.2 christos {
1774 1.2 christos struct replay_cb_data *data = ctx;
1775 1.2 christos kadm5_ret_t ret;
1776 1.2 christos off_t off;
1777 1.2 christos
1778 1.2 christos /* On initial commit there must be just one pending unconfirmed entry */
1779 1.2 christos if (data->count > 0 && data->mode == kadm_recover_commit)
1780 1.2 christos return KADM5_LOG_CORRUPT;
1781 1.2 christos
1782 1.2 christos /* We're at the start of the payload; compute end of entry offset */
1783 1.2 christos off = krb5_storage_seek(sp, 0, SEEK_CUR) + len + LOG_TRAILER_SZ;
1784 1.2 christos
1785 1.2 christos /* We cannot perform log recovery on LDAP and such backends */
1786 1.2 christos if (data->mode == kadm_recover_replay &&
1787 1.2 christos (context->db->hdb_capability_flags & HDB_CAP_F_SHARED_DIRECTORY))
1788 1.2 christos ret = 0;
1789 1.2 christos else
1790 1.2 christos ret = kadm5_log_replay(context, op, ver, len, sp);
1791 1.2 christos switch (ret) {
1792 1.2 christos case HDB_ERR_NOENTRY:
1793 1.2 christos case HDB_ERR_EXISTS:
1794 1.2 christos if (data->mode != kadm_recover_replay)
1795 1.2 christos return ret;
1796 1.2 christos case 0:
1797 1.2 christos break;
1798 1.2 christos case KADM5_LOG_CORRUPT:
1799 1.2 christos return -1;
1800 1.2 christos default:
1801 1.2 christos krb5_warn(context->context, ret, "unexpected error while replaying");
1802 1.2 christos return -1;
1803 1.2 christos }
1804 1.2 christos data->count++;
1805 1.2 christos data->ver = ver;
1806 1.2 christos
1807 1.2 christos /*
1808 1.2 christos * With replay we may be making multiple HDB changes. We must sync the
1809 1.2 christos * confirmation of each one before moving on to the next. Otherwise, we
1810 1.2 christos * might attempt to replay multiple already applied updates, and this may
1811 1.2 christos * introduce unintended intermediate states or fail to yield the same final
1812 1.2 christos * result.
1813 1.2 christos */
1814 1.2 christos kadm5_log_set_version(context, ver);
1815 1.2 christos ret = log_update_uber(context, off);
1816 1.2 christos if (ret == 0 && data->mode != kadm_recover_commit)
1817 1.2 christos ret = krb5_storage_fsync(sp);
1818 1.2 christos return ret;
1819 1.2 christos }
1820 1.1 elric
1821 1.2 christos
1822 1.2 christos kadm5_ret_t
1823 1.2 christos kadm5_log_recover(kadm5_server_context *context, enum kadm_recover_mode mode)
1824 1.2 christos {
1825 1.2 christos kadm5_ret_t ret;
1826 1.2 christos krb5_storage *sp;
1827 1.2 christos struct replay_cb_data replay_data;
1828 1.2 christos
1829 1.2 christos replay_data.count = 0;
1830 1.2 christos replay_data.ver = 0;
1831 1.2 christos replay_data.mode = mode;
1832 1.2 christos
1833 1.2 christos sp = kadm5_log_goto_end(context, context->log_context.log_fd);
1834 1.2 christos if (sp == NULL)
1835 1.2 christos return errno ? errno : EIO;
1836 1.2 christos
1837 1.2 christos ret = kadm5_log_foreach(context, kadm_forward | kadm_unconfirmed,
1838 1.2 christos NULL, recover_replay, &replay_data);
1839 1.2 christos if (ret == 0 && mode == kadm_recover_commit && replay_data.count != 1)
1840 1.2 christos ret = KADM5_LOG_CORRUPT;
1841 1.2 christos krb5_storage_free(sp);
1842 1.2 christos return ret;
1843 1.2 christos }
1844 1.2 christos
1845 1.2 christos /*
1846 1.2 christos * Call `func' for each log record in the log in `context'.
1847 1.2 christos *
1848 1.2 christos * `func' is optional.
1849 1.2 christos *
1850 1.2 christos * If `func' returns -1 then log traversal terminates and this returns 0.
1851 1.2 christos * Otherwise `func''s return is returned if there are no other errors.
1852 1.2 christos */
1853 1.1 elric kadm5_ret_t
1854 1.2 christos kadm5_log_foreach(kadm5_server_context *context,
1855 1.2 christos enum kadm_iter_opts iter_opts,
1856 1.2 christos off_t *off_lastp,
1857 1.2 christos kadm5_ret_t (*func)(kadm5_server_context *server_context,
1858 1.2 christos uint32_t ver, time_t timestamp,
1859 1.2 christos enum kadm_ops op, uint32_t len,
1860 1.2 christos krb5_storage *sp, void *ctx),
1861 1.2 christos void *ctx)
1862 1.1 elric {
1863 1.2 christos kadm5_ret_t ret = 0;
1864 1.1 elric int fd = context->log_context.log_fd;
1865 1.1 elric krb5_storage *sp;
1866 1.2 christos off_t off_last;
1867 1.2 christos off_t this_entry = 0;
1868 1.2 christos off_t log_end = 0;
1869 1.2 christos
1870 1.2 christos if (strcmp(context->log_context.log_file, "/dev/null") == 0)
1871 1.2 christos return 0;
1872 1.2 christos
1873 1.2 christos if (off_lastp == NULL)
1874 1.2 christos off_lastp = &off_last;
1875 1.2 christos *off_lastp = -1;
1876 1.2 christos
1877 1.2 christos if (((iter_opts & kadm_forward) && (iter_opts & kadm_backward)) ||
1878 1.2 christos (!(iter_opts & kadm_confirmed) && !(iter_opts & kadm_unconfirmed)))
1879 1.2 christos return EINVAL;
1880 1.2 christos
1881 1.2 christos if ((iter_opts & kadm_forward) && (iter_opts & kadm_confirmed) &&
1882 1.2 christos (iter_opts & kadm_unconfirmed)) {
1883 1.2 christos /*
1884 1.2 christos * We want to traverse all log entries, confirmed or not, from
1885 1.2 christos * the start, then there's no need to kadm5_log_goto_end()
1886 1.2 christos * -- no reason to try to find the end.
1887 1.2 christos */
1888 1.2 christos sp = krb5_storage_from_fd(fd);
1889 1.2 christos if (sp == NULL)
1890 1.2 christos return errno;
1891 1.2 christos
1892 1.2 christos log_end = krb5_storage_seek(sp, 0, SEEK_END);
1893 1.2 christos if (log_end == -1 ||
1894 1.2 christos krb5_storage_seek(sp, 0, SEEK_SET) == -1) {
1895 1.2 christos ret = errno;
1896 1.2 christos krb5_storage_free(sp);
1897 1.2 christos return ret;
1898 1.2 christos }
1899 1.2 christos } else {
1900 1.2 christos /* Get the end of the log based on the uber entry */
1901 1.2 christos sp = kadm5_log_goto_end(context, fd);
1902 1.2 christos if (sp == NULL)
1903 1.2 christos return errno;
1904 1.2 christos log_end = krb5_storage_seek(sp, 0, SEEK_CUR);
1905 1.2 christos }
1906 1.2 christos
1907 1.2 christos *off_lastp = log_end;
1908 1.2 christos
1909 1.2 christos if ((iter_opts & kadm_forward) && (iter_opts & kadm_confirmed)) {
1910 1.2 christos /* Start at the beginning */
1911 1.2 christos if (krb5_storage_seek(sp, 0, SEEK_SET) == -1) {
1912 1.2 christos ret = errno;
1913 1.2 christos krb5_storage_free(sp);
1914 1.2 christos return ret;
1915 1.2 christos }
1916 1.2 christos } else if ((iter_opts & kadm_backward) && (iter_opts & kadm_unconfirmed)) {
1917 1.2 christos /*
1918 1.2 christos * We're at the confirmed end but need to be at the unconfirmed
1919 1.2 christos * end. Skip forward to the real end, re-entering to do it.
1920 1.2 christos */
1921 1.2 christos ret = kadm5_log_foreach(context, kadm_forward | kadm_unconfirmed,
1922 1.2 christos &log_end, NULL, NULL);
1923 1.2 christos if (ret)
1924 1.2 christos return ret;
1925 1.2 christos if (krb5_storage_seek(sp, log_end, SEEK_SET) == -1) {
1926 1.2 christos ret = errno;
1927 1.2 christos krb5_storage_free(sp);
1928 1.2 christos return ret;
1929 1.2 christos }
1930 1.2 christos }
1931 1.1 elric
1932 1.1 elric for (;;) {
1933 1.2 christos uint32_t ver, ver2, len, len2;
1934 1.2 christos uint32_t tstamp;
1935 1.2 christos time_t timestamp;
1936 1.2 christos enum kadm_ops op;
1937 1.2 christos
1938 1.2 christos if ((iter_opts & kadm_backward)) {
1939 1.2 christos off_t o;
1940 1.2 christos
1941 1.2 christos o = krb5_storage_seek(sp, 0, SEEK_CUR);
1942 1.2 christos if (o == 0 ||
1943 1.2 christos ((iter_opts & kadm_unconfirmed) && o <= *off_lastp))
1944 1.2 christos break;
1945 1.2 christos ret = kadm5_log_previous(context->context, sp, &ver,
1946 1.2 christos ×tamp, &op, &len);
1947 1.2 christos if (ret)
1948 1.2 christos break;
1949 1.2 christos
1950 1.2 christos /* Offset is now at payload of current entry */
1951 1.2 christos
1952 1.2 christos o = krb5_storage_seek(sp, 0, SEEK_CUR);
1953 1.2 christos if (o == -1) {
1954 1.2 christos ret = errno;
1955 1.2 christos break;
1956 1.2 christos }
1957 1.2 christos this_entry = o - LOG_HEADER_SZ;
1958 1.2 christos if (this_entry < 0) {
1959 1.2 christos ret = KADM5_LOG_CORRUPT;
1960 1.2 christos break;
1961 1.2 christos }
1962 1.2 christos } else {
1963 1.2 christos /* Offset is now at start of current entry, read header */
1964 1.2 christos this_entry = krb5_storage_seek(sp, 0, SEEK_CUR);
1965 1.2 christos if (!(iter_opts & kadm_unconfirmed) && this_entry == log_end)
1966 1.2 christos break;
1967 1.2 christos ret = get_header(sp, LOG_NOPEEK, &ver, &tstamp, &op, &len);
1968 1.2 christos if (ret == HEIM_ERR_EOF) {
1969 1.2 christos ret = 0;
1970 1.2 christos break;
1971 1.2 christos }
1972 1.2 christos timestamp = tstamp;
1973 1.2 christos if (ret)
1974 1.2 christos break;
1975 1.2 christos /* Offset is now at payload of current entry */
1976 1.2 christos }
1977 1.2 christos
1978 1.2 christos /* Validate trailer before calling the callback */
1979 1.2 christos if (krb5_storage_seek(sp, len, SEEK_CUR) == -1) {
1980 1.2 christos ret = errno;
1981 1.2 christos break;
1982 1.2 christos }
1983 1.2 christos
1984 1.2 christos ret = krb5_ret_uint32(sp, &len2);
1985 1.2 christos if (ret)
1986 1.2 christos break;
1987 1.2 christos ret = krb5_ret_uint32(sp, &ver2);
1988 1.2 christos if (ret)
1989 1.2 christos break;
1990 1.2 christos if (len != len2 || ver != ver2) {
1991 1.2 christos ret = KADM5_LOG_CORRUPT;
1992 1.2 christos break;
1993 1.2 christos }
1994 1.1 elric
1995 1.2 christos /* Rewind to start of payload and call callback if we have one */
1996 1.2 christos if (krb5_storage_seek(sp, this_entry + LOG_HEADER_SZ,
1997 1.2 christos SEEK_SET) == -1) {
1998 1.2 christos ret = errno;
1999 1.2 christos break;
2000 1.2 christos }
2001 1.2 christos
2002 1.2 christos if (func != NULL) {
2003 1.2 christos ret = (*func)(context, ver, timestamp, op, len, sp, ctx);
2004 1.2 christos if (ret) {
2005 1.2 christos /* Callback signals desire to stop by returning -1 */
2006 1.2 christos if (ret == -1)
2007 1.2 christos ret = 0;
2008 1.2 christos break;
2009 1.2 christos }
2010 1.2 christos }
2011 1.2 christos if ((iter_opts & kadm_forward)) {
2012 1.2 christos off_t o;
2013 1.2 christos
2014 1.2 christos o = krb5_storage_seek(sp, this_entry+LOG_WRAPPER_SZ+len, SEEK_SET);
2015 1.2 christos if (o == -1) {
2016 1.2 christos ret = errno;
2017 1.2 christos break;
2018 1.2 christos }
2019 1.2 christos if (o > log_end)
2020 1.2 christos *off_lastp = o;
2021 1.2 christos } else if ((iter_opts & kadm_backward)) {
2022 1.2 christos /*
2023 1.2 christos * Rewind to the start of this entry so kadm5_log_previous()
2024 1.2 christos * can find the previous one.
2025 1.2 christos */
2026 1.2 christos if (krb5_storage_seek(sp, this_entry, SEEK_SET) == -1) {
2027 1.2 christos ret = errno;
2028 1.2 christos break;
2029 1.2 christos }
2030 1.2 christos }
2031 1.2 christos }
2032 1.2 christos if ((ret == HEIM_ERR_EOF || ret == KADM5_LOG_CORRUPT) &&
2033 1.2 christos (iter_opts & kadm_forward) &&
2034 1.2 christos context->log_context.lock_mode == LOCK_EX) {
2035 1.2 christos /*
2036 1.2 christos * Truncate partially written last log entry so we can write
2037 1.2 christos * again.
2038 1.2 christos */
2039 1.2 christos ret = krb5_storage_truncate(sp, this_entry);
2040 1.2 christos if (ret == 0 &&
2041 1.2 christos krb5_storage_seek(sp, this_entry, SEEK_SET) == -1)
2042 1.2 christos ret = errno;
2043 1.2 christos krb5_warnx(context->context, "Truncating log at partial or "
2044 1.2 christos "corrupt %s entry",
2045 1.2 christos this_entry > log_end ? "unconfirmed" : "confirmed");
2046 1.1 elric }
2047 1.1 elric krb5_storage_free(sp);
2048 1.2 christos return ret;
2049 1.2 christos }
2050 1.2 christos
2051 1.2 christos /*
2052 1.2 christos * Go to the second record, which, if we have an uber record, will be
2053 1.2 christos * the first record.
2054 1.2 christos */
2055 1.2 christos static krb5_storage *
2056 1.2 christos log_goto_first(kadm5_server_context *server_context, int fd)
2057 1.2 christos {
2058 1.2 christos krb5_storage *sp;
2059 1.2 christos enum kadm_ops op;
2060 1.2 christos uint32_t ver, len;
2061 1.2 christos kadm5_ret_t ret;
2062 1.2 christos
2063 1.2 christos if (fd == -1) {
2064 1.2 christos errno = EINVAL;
2065 1.2 christos return NULL;
2066 1.2 christos }
2067 1.2 christos
2068 1.2 christos sp = krb5_storage_from_fd(fd);
2069 1.2 christos if (sp == NULL)
2070 1.2 christos return NULL;
2071 1.2 christos
2072 1.2 christos if (krb5_storage_seek(sp, 0, SEEK_SET) == -1)
2073 1.2 christos return NULL;
2074 1.2 christos
2075 1.2 christos ret = get_header(sp, LOG_DOPEEK, &ver, NULL, &op, &len);
2076 1.2 christos if (ret) {
2077 1.2 christos krb5_storage_free(sp);
2078 1.2 christos errno = ret;
2079 1.2 christos return NULL;
2080 1.2 christos }
2081 1.2 christos if (op == kadm_nop && len == LOG_UBER_LEN && seek_next(sp) == -1) {
2082 1.2 christos krb5_storage_free(sp);
2083 1.2 christos return NULL;
2084 1.2 christos }
2085 1.2 christos return sp;
2086 1.1 elric }
2087 1.1 elric
2088 1.1 elric /*
2089 1.1 elric * Go to end of log.
2090 1.2 christos *
2091 1.2 christos * XXX This really needs to return a kadm5_ret_t and either output a
2092 1.2 christos * krb5_storage * via an argument, or take one as input.
2093 1.1 elric */
2094 1.1 elric
2095 1.1 elric krb5_storage *
2096 1.2 christos kadm5_log_goto_end(kadm5_server_context *server_context, int fd)
2097 1.1 elric {
2098 1.2 christos krb5_error_code ret = 0;
2099 1.1 elric krb5_storage *sp;
2100 1.2 christos enum kadm_ops op;
2101 1.2 christos uint32_t ver, len;
2102 1.2 christos uint32_t tstamp;
2103 1.2 christos uint64_t off;
2104 1.2 christos
2105 1.2 christos if (fd == -1) {
2106 1.2 christos errno = EINVAL;
2107 1.2 christos return NULL;
2108 1.2 christos }
2109 1.2 christos
2110 1.2 christos sp = krb5_storage_from_fd(fd);
2111 1.2 christos if (sp == NULL)
2112 1.2 christos return NULL;
2113 1.1 elric
2114 1.2 christos if (krb5_storage_seek(sp, 0, SEEK_SET) == -1) {
2115 1.2 christos ret = errno;
2116 1.2 christos goto fail;
2117 1.2 christos }
2118 1.2 christos ret = get_header(sp, LOG_NOPEEK, &ver, &tstamp, &op, &len);
2119 1.2 christos if (ret == HEIM_ERR_EOF) {
2120 1.2 christos (void) krb5_storage_seek(sp, 0, SEEK_SET);
2121 1.2 christos return sp;
2122 1.2 christos }
2123 1.2 christos if (ret == KADM5_LOG_CORRUPT)
2124 1.2 christos goto truncate;
2125 1.2 christos if (ret)
2126 1.2 christos goto fail;
2127 1.2 christos
2128 1.2 christos if (op == kadm_nop && len == LOG_UBER_LEN) {
2129 1.2 christos /* New style log */
2130 1.2 christos ret = krb5_ret_uint64(sp, &off);
2131 1.2 christos if (ret)
2132 1.2 christos goto truncate;
2133 1.2 christos
2134 1.2 christos if (krb5_storage_seek(sp, off, SEEK_SET) == -1)
2135 1.2 christos goto fail;
2136 1.2 christos
2137 1.2 christos if (off >= LOG_UBER_SZ) {
2138 1.2 christos ret = get_version_prev(sp, &ver, NULL);
2139 1.2 christos if (ret == 0)
2140 1.2 christos return sp;
2141 1.2 christos }
2142 1.2 christos /* Invalid offset in uber entry */
2143 1.2 christos goto truncate;
2144 1.2 christos }
2145 1.2 christos
2146 1.2 christos /* Old log with no uber entry */
2147 1.2 christos if (krb5_storage_seek(sp, 0, SEEK_END) == -1) {
2148 1.2 christos static int warned = 0;
2149 1.2 christos if (!warned) {
2150 1.2 christos warned = 1;
2151 1.2 christos krb5_warnx(server_context->context,
2152 1.2 christos "Old log found; truncate it to upgrade");
2153 1.2 christos }
2154 1.2 christos }
2155 1.2 christos ret = get_version_prev(sp, &ver, NULL);
2156 1.2 christos if (ret)
2157 1.2 christos goto truncate;
2158 1.1 elric return sp;
2159 1.2 christos
2160 1.2 christos truncate:
2161 1.2 christos /* If we can, truncate */
2162 1.2 christos if (server_context->log_context.lock_mode == LOCK_EX) {
2163 1.2 christos ret = kadm5_log_reinit(server_context, 0);
2164 1.2 christos if (ret == 0) {
2165 1.2 christos krb5_warn(server_context->context, ret,
2166 1.2 christos "Invalid log; truncating to recover");
2167 1.2 christos if (krb5_storage_seek(sp, 0, SEEK_END) == -1)
2168 1.2 christos return NULL;
2169 1.2 christos return sp;
2170 1.2 christos }
2171 1.2 christos }
2172 1.2 christos krb5_warn(server_context->context, ret,
2173 1.2 christos "Invalid log; truncate to recover");
2174 1.2 christos
2175 1.2 christos fail:
2176 1.2 christos errno = ret;
2177 1.2 christos krb5_storage_free(sp);
2178 1.2 christos return NULL;
2179 1.1 elric }
2180 1.1 elric
2181 1.1 elric /*
2182 1.1 elric * Return previous log entry.
2183 1.1 elric *
2184 1.2 christos * The pointer in `sp' is assumed to be at the top of the entry after
2185 1.2 christos * previous entry (e.g., at EOF). On success, the `sp' pointer is set to
2186 1.2 christos * data portion of previous entry. In case of error, it's not changed
2187 1.2 christos * at all.
2188 1.1 elric */
2189 1.1 elric kadm5_ret_t
2190 1.2 christos kadm5_log_previous(krb5_context context,
2191 1.2 christos krb5_storage *sp,
2192 1.2 christos uint32_t *verp,
2193 1.2 christos time_t *tstampp,
2194 1.2 christos enum kadm_ops *opp,
2195 1.2 christos uint32_t *lenp)
2196 1.1 elric {
2197 1.1 elric krb5_error_code ret;
2198 1.2 christos off_t oldoff;
2199 1.2 christos uint32_t ver2, len2;
2200 1.2 christos uint32_t tstamp;
2201 1.1 elric
2202 1.1 elric oldoff = krb5_storage_seek(sp, 0, SEEK_CUR);
2203 1.2 christos if (oldoff == -1)
2204 1.2 christos goto log_corrupt;
2205 1.2 christos
2206 1.2 christos /* This reads the physical version of the uber record */
2207 1.2 christos if (seek_prev(sp, verp, lenp) == -1)
2208 1.2 christos goto log_corrupt;
2209 1.1 elric
2210 1.2 christos ret = get_header(sp, LOG_NOPEEK, &ver2, &tstamp, opp, &len2);
2211 1.2 christos if (ret) {
2212 1.2 christos (void) krb5_storage_seek(sp, oldoff, SEEK_SET);
2213 1.2 christos return ret;
2214 1.1 elric }
2215 1.2 christos if (tstampp)
2216 1.2 christos *tstampp = tstamp;
2217 1.2 christos if (ver2 != *verp || len2 != *lenp)
2218 1.2 christos goto log_corrupt;
2219 1.2 christos
2220 1.1 elric return 0;
2221 1.1 elric
2222 1.2 christos log_corrupt:
2223 1.2 christos (void) krb5_storage_seek(sp, oldoff, SEEK_SET);
2224 1.2 christos return KADM5_LOG_CORRUPT;
2225 1.1 elric }
2226 1.1 elric
2227 1.1 elric /*
2228 1.1 elric * Replay a record from the log
2229 1.1 elric */
2230 1.1 elric
2231 1.1 elric kadm5_ret_t
2232 1.2 christos kadm5_log_replay(kadm5_server_context *context,
2233 1.2 christos enum kadm_ops op,
2234 1.2 christos uint32_t ver,
2235 1.2 christos uint32_t len,
2236 1.2 christos krb5_storage *sp)
2237 1.1 elric {
2238 1.1 elric switch (op) {
2239 1.1 elric case kadm_create :
2240 1.2 christos return kadm5_log_replay_create(context, ver, len, sp);
2241 1.1 elric case kadm_delete :
2242 1.2 christos return kadm5_log_replay_delete(context, ver, len, sp);
2243 1.1 elric case kadm_rename :
2244 1.2 christos return kadm5_log_replay_rename(context, ver, len, sp);
2245 1.1 elric case kadm_modify :
2246 1.2 christos return kadm5_log_replay_modify(context, ver, len, sp);
2247 1.1 elric case kadm_nop :
2248 1.2 christos return kadm5_log_replay_nop(context, ver, len, sp);
2249 1.1 elric default :
2250 1.2 christos /*
2251 1.2 christos * FIXME This default arm makes it difficult to add new kadm_ops
2252 1.2 christos * values.
2253 1.2 christos */
2254 1.1 elric krb5_set_error_message(context->context, KADM5_FAILURE,
2255 1.1 elric "Unsupported replay op %d", (int)op);
2256 1.2 christos (void) krb5_storage_seek(sp, len, SEEK_CUR);
2257 1.1 elric return KADM5_FAILURE;
2258 1.1 elric }
2259 1.1 elric }
2260 1.1 elric
2261 1.2 christos struct load_entries_data {
2262 1.2 christos krb5_data *entries;
2263 1.2 christos unsigned char *p;
2264 1.2 christos uint32_t first;
2265 1.2 christos uint32_t last;
2266 1.2 christos size_t bytes;
2267 1.2 christos size_t nentries;
2268 1.2 christos size_t maxbytes;
2269 1.2 christos size_t maxentries;
2270 1.2 christos };
2271 1.2 christos
2272 1.2 christos
2273 1.1 elric /*
2274 1.2 christos * Prepend one entry with header and trailer to the entry buffer, stopping when
2275 1.2 christos * we've reached either of the byte or entry-count limits (if non-zero).
2276 1.2 christos *
2277 1.2 christos * This is a two-pass algorithm:
2278 1.2 christos *
2279 1.2 christos * In the first pass, when entries->entries == NULL, we compute the space
2280 1.2 christos * required, and count the entries that fit up from zero.
2281 1.2 christos *
2282 1.2 christos * In the second pass we fill the buffer, and count the entries back down to
2283 1.2 christos * zero. The space used must be an exact fit, and the number of entries must
2284 1.2 christos * reach zero at that point or an error is returned.
2285 1.2 christos *
2286 1.2 christos * The caller MUST check that entries->nentries == 0 at the end of the second
2287 1.2 christos * pass.
2288 1.1 elric */
2289 1.2 christos static kadm5_ret_t
2290 1.2 christos load_entries_cb(kadm5_server_context *server_context,
2291 1.2 christos uint32_t ver,
2292 1.2 christos time_t timestamp,
2293 1.2 christos enum kadm_ops op,
2294 1.2 christos uint32_t len,
2295 1.2 christos krb5_storage *sp,
2296 1.2 christos void *ctx)
2297 1.2 christos {
2298 1.2 christos struct load_entries_data *entries = ctx;
2299 1.2 christos kadm5_ret_t ret;
2300 1.2 christos ssize_t bytes;
2301 1.2 christos size_t entry_len = len + LOG_WRAPPER_SZ;
2302 1.2 christos unsigned char *base;
2303 1.2 christos
2304 1.2 christos if (entries->entries == NULL) {
2305 1.2 christos size_t total = entries->bytes + entry_len;
2306 1.2 christos
2307 1.2 christos /*
2308 1.2 christos * First run: find the size of krb5_data buffer needed.
2309 1.2 christos *
2310 1.2 christos * If the log was huge we'd have to perhaps open a temp file for this.
2311 1.2 christos * For now KISS.
2312 1.2 christos */
2313 1.2 christos if ((op == kadm_nop && entry_len == LOG_UBER_SZ) ||
2314 1.2 christos entry_len < len /*overflow?*/ ||
2315 1.2 christos (entries->maxbytes > 0 && total > entries->maxbytes) ||
2316 1.2 christos total < entries->bytes /*overflow?*/ ||
2317 1.2 christos (entries->maxentries > 0 && entries->nentries == entries->maxentries))
2318 1.2 christos return -1; /* stop iteration */
2319 1.2 christos entries->bytes = total;
2320 1.2 christos entries->first = ver;
2321 1.2 christos if (entries->nentries++ == 0)
2322 1.2 christos entries->last = ver;
2323 1.2 christos return 0;
2324 1.2 christos }
2325 1.2 christos
2326 1.2 christos /* Second run: load the data into memory */
2327 1.2 christos base = (unsigned char *)entries->entries->data;
2328 1.2 christos if (entries->p - base < entry_len && entries->p != base) {
2329 1.2 christos /*
2330 1.2 christos * This can't happen normally: we stop the log record iteration
2331 1.2 christos * above before we get here. This could happen if someone wrote
2332 1.2 christos * garbage to the log while we were traversing it. We return an
2333 1.2 christos * error instead of asserting.
2334 1.2 christos */
2335 1.2 christos return KADM5_LOG_CORRUPT;
2336 1.2 christos }
2337 1.2 christos
2338 1.2 christos /*
2339 1.2 christos * sp here is a krb5_storage_from_fd() of the log file, and the
2340 1.2 christos * offset pointer points at the current log record payload.
2341 1.2 christos *
2342 1.2 christos * Seek back to the start of the record poayload so we can read the
2343 1.2 christos * whole record.
2344 1.2 christos */
2345 1.2 christos if (krb5_storage_seek(sp, -LOG_HEADER_SZ, SEEK_CUR) == -1)
2346 1.2 christos return errno;
2347 1.2 christos
2348 1.2 christos /*
2349 1.2 christos * We read the header, payload, and trailer into the buffer we have, that
2350 1.2 christos * many bytes before the previous record we read.
2351 1.2 christos */
2352 1.2 christos errno = 0;
2353 1.2 christos bytes = krb5_storage_read(sp, entries->p - entry_len, entry_len);
2354 1.2 christos ret = errno;
2355 1.2 christos if (bytes < 0 || bytes != entry_len)
2356 1.2 christos return ret ? ret : EIO;
2357 1.2 christos
2358 1.2 christos entries->first = ver;
2359 1.2 christos --entries->nentries;
2360 1.2 christos entries->p -= entry_len;
2361 1.2 christos return (entries->p == base) ? -1 : 0;
2362 1.2 christos }
2363 1.1 elric
2364 1.2 christos
2365 1.2 christos /*
2366 1.2 christos * Serialize a tail fragment of the log as a krb5_data, this is constrained to
2367 1.2 christos * at most `maxbytes' bytes and to at most `maxentries' entries if not zero.
2368 1.2 christos */
2369 1.2 christos static kadm5_ret_t
2370 1.2 christos load_entries(kadm5_server_context *context, krb5_data *p,
2371 1.2 christos size_t maxentries, size_t maxbytes,
2372 1.2 christos uint32_t *first, uint32_t *last)
2373 1.1 elric {
2374 1.2 christos struct load_entries_data entries;
2375 1.1 elric kadm5_ret_t ret;
2376 1.2 christos unsigned char *base;
2377 1.2 christos
2378 1.2 christos krb5_data_zero(p);
2379 1.2 christos
2380 1.2 christos *first = 0;
2381 1.2 christos
2382 1.2 christos memset(&entries, 0, sizeof(entries));
2383 1.2 christos entries.entries = NULL;
2384 1.2 christos entries.p = NULL;
2385 1.2 christos entries.maxentries = maxentries;
2386 1.2 christos entries.maxbytes = maxbytes;
2387 1.1 elric
2388 1.2 christos /* Figure out how many bytes it will take */
2389 1.2 christos ret = kadm5_log_foreach(context, kadm_backward | kadm_confirmed,
2390 1.2 christos NULL, load_entries_cb, &entries);
2391 1.1 elric if (ret)
2392 1.2 christos return ret;
2393 1.2 christos
2394 1.2 christos /*
2395 1.2 christos * If no entries fit our limits, we do not truncate, instead the caller can
2396 1.2 christos * call kadm5_log_reinit() if desired.
2397 1.2 christos */
2398 1.2 christos if (entries.bytes == 0)
2399 1.2 christos return 0;
2400 1.1 elric
2401 1.2 christos ret = krb5_data_alloc(p, entries.bytes);
2402 1.1 elric if (ret)
2403 1.2 christos return ret;
2404 1.1 elric
2405 1.2 christos *first = entries.first;
2406 1.2 christos *last = entries.last;
2407 1.2 christos entries.entries = p;
2408 1.2 christos base = (unsigned char *)entries.entries->data;
2409 1.2 christos entries.p = base + entries.bytes;
2410 1.2 christos
2411 1.2 christos ret = kadm5_log_foreach(context, kadm_backward | kadm_confirmed,
2412 1.2 christos NULL, load_entries_cb, &entries);
2413 1.2 christos if (ret == 0 &&
2414 1.2 christos (entries.nentries || entries.p != base || entries.first != *first))
2415 1.2 christos ret = KADM5_LOG_CORRUPT;
2416 1.1 elric if (ret)
2417 1.2 christos krb5_data_free(p);
2418 1.2 christos return ret;
2419 1.2 christos }
2420 1.2 christos
2421 1.2 christos /*
2422 1.2 christos * Truncate the log, retaining at most `keep' entries and at most `maxbytes'.
2423 1.2 christos * If `maxbytes' is zero, keep at most the default log size limit.
2424 1.2 christos */
2425 1.2 christos kadm5_ret_t
2426 1.2 christos kadm5_log_truncate(kadm5_server_context *context, size_t keep, size_t maxbytes)
2427 1.2 christos {
2428 1.2 christos kadm5_ret_t ret;
2429 1.2 christos uint32_t first, last, last_tstamp;
2430 1.2 christos time_t now = time(NULL);
2431 1.2 christos krb5_data entries;
2432 1.2 christos krb5_storage *sp;
2433 1.2 christos ssize_t bytes;
2434 1.2 christos uint64_t sz;
2435 1.2 christos off_t off;
2436 1.2 christos
2437 1.2 christos if (maxbytes == 0)
2438 1.2 christos maxbytes = get_max_log_size(context->context);
2439 1.2 christos
2440 1.2 christos if (strcmp(context->log_context.log_file, "/dev/null") == 0)
2441 1.2 christos return 0;
2442 1.2 christos
2443 1.2 christos if (context->log_context.read_only)
2444 1.2 christos return EROFS;
2445 1.2 christos
2446 1.2 christos /* Get the desired records. */
2447 1.2 christos krb5_data_zero(&entries);
2448 1.2 christos ret = load_entries(context, &entries, keep, maxbytes, &first, &last);
2449 1.2 christos if (ret)
2450 1.2 christos return ret;
2451 1.2 christos
2452 1.2 christos if (first == 0) {
2453 1.2 christos /*
2454 1.2 christos * No records found/fit within resource limits. The caller should call
2455 1.2 christos * kadm5_log_reinit(context) to truly truncate and reset the log to
2456 1.2 christos * version 0, else call again with better limits.
2457 1.2 christos */
2458 1.2 christos krb5_data_free(&entries);
2459 1.2 christos return EINVAL;
2460 1.2 christos }
2461 1.2 christos
2462 1.2 christos /* Check that entries.length won't overflow off_t */
2463 1.2 christos sz = LOG_UBER_SZ + entries.length;
2464 1.2 christos off = (off_t)sz;
2465 1.2 christos if (off < 0 || off != sz || sz < entries.length) {
2466 1.2 christos krb5_data_free(&entries);
2467 1.2 christos return EOVERFLOW; /* caller should ask for fewer entries */
2468 1.2 christos }
2469 1.2 christos
2470 1.2 christos /* Truncate to zero size and seek to zero offset */
2471 1.2 christos if (ftruncate(context->log_context.log_fd, 0) < 0 ||
2472 1.2 christos lseek(context->log_context.log_fd, 0, SEEK_SET) < 0) {
2473 1.2 christos krb5_data_free(&entries);
2474 1.2 christos return errno;
2475 1.2 christos }
2476 1.2 christos
2477 1.2 christos /*
2478 1.2 christos * Write the uber record and then the records loaded. Confirm the entries
2479 1.2 christos * after writing them.
2480 1.2 christos *
2481 1.2 christos * If we crash then the log may not have all the entries we want, and
2482 1.2 christos * replaying only some of the entries will leave us in a bad state.
2483 1.2 christos * Additionally, we don't have mathematical proof that replaying the last
2484 1.2 christos * N>1 entries is always idempotent. And though we believe we can make
2485 1.2 christos * such replays idempotent, they would still leave the HDB with
2486 1.2 christos * intermediate states that would not have occurred on the master.
2487 1.2 christos *
2488 1.2 christos * By initially setting the offset in the uber record to 0, the log will be
2489 1.2 christos * seen as invalid should we crash here, thus the only
2490 1.2 christos * harm will be that we'll reinitialize the log and force full props.
2491 1.2 christos *
2492 1.2 christos * We can't use the normal kadm5_log_*() machinery for this because
2493 1.2 christos * we must set specific version numbers and timestamps. To keep
2494 1.2 christos * things simple we don't try to do a single atomic write here as we
2495 1.2 christos * do in kadm5_log_flush().
2496 1.2 christos *
2497 1.2 christos * We really do want to keep the new first entry's version and
2498 1.2 christos * timestamp so we don't trip up iprop.
2499 1.2 christos *
2500 1.2 christos * Keep this in sync with kadm5_log_nop().
2501 1.2 christos */
2502 1.2 christos sp = krb5_storage_from_fd(context->log_context.log_fd);
2503 1.2 christos if (sp == NULL) {
2504 1.2 christos ret = errno;
2505 1.2 christos krb5_warn(context->context, ret, "Unable to keep entries");
2506 1.2 christos krb5_data_free(&entries);
2507 1.2 christos return errno;
2508 1.2 christos }
2509 1.2 christos ret = krb5_store_uint32(sp, 0);
2510 1.2 christos if (ret == 0)
2511 1.2 christos ret = krb5_store_uint32(sp, now);
2512 1.2 christos if (ret == 0)
2513 1.2 christos ret = krb5_store_uint32(sp, kadm_nop); /* end of preamble */
2514 1.2 christos if (ret == 0)
2515 1.2 christos ret = krb5_store_uint32(sp, LOG_UBER_LEN); /* end of header */
2516 1.2 christos if (ret == 0)
2517 1.2 christos ret = krb5_store_uint64(sp, LOG_UBER_SZ);
2518 1.2 christos if (ret == 0)
2519 1.2 christos ret = krb5_store_uint32(sp, now);
2520 1.2 christos if (ret == 0)
2521 1.2 christos ret = krb5_store_uint32(sp, last);
2522 1.2 christos if (ret == 0)
2523 1.2 christos ret = krb5_store_uint32(sp, LOG_UBER_LEN);
2524 1.2 christos if (ret == 0)
2525 1.2 christos ret = krb5_store_uint32(sp, 0); /* end of trailer */
2526 1.2 christos if (ret == 0) {
2527 1.2 christos bytes = krb5_storage_write(sp, entries.data, entries.length);
2528 1.2 christos if (bytes == -1)
2529 1.2 christos ret = errno;
2530 1.2 christos }
2531 1.2 christos if (ret == 0)
2532 1.2 christos ret = krb5_storage_fsync(sp);
2533 1.2 christos /* Confirm all the records now */
2534 1.2 christos if (ret == 0) {
2535 1.2 christos if (krb5_storage_seek(sp, LOG_HEADER_SZ, SEEK_SET) == -1)
2536 1.2 christos ret = errno;
2537 1.2 christos }
2538 1.2 christos if (ret == 0)
2539 1.2 christos ret = krb5_store_uint64(sp, off);
2540 1.2 christos krb5_data_free(&entries);
2541 1.2 christos krb5_storage_free(sp);
2542 1.1 elric
2543 1.2 christos if (ret) {
2544 1.2 christos krb5_warn(context->context, ret, "Unable to keep entries");
2545 1.2 christos (void) ftruncate(context->log_context.log_fd, LOG_UBER_SZ);
2546 1.2 christos (void) lseek(context->log_context.log_fd, 0, SEEK_SET);
2547 1.2 christos return ret;
2548 1.2 christos }
2549 1.1 elric
2550 1.2 christos /* Done. Now rebuild the log_context state. */
2551 1.2 christos (void) lseek(context->log_context.log_fd, off, SEEK_SET);
2552 1.2 christos sp = kadm5_log_goto_end(context, context->log_context.log_fd);
2553 1.2 christos if (sp == NULL)
2554 1.2 christos return ENOMEM;
2555 1.2 christos ret = get_version_prev(sp, &context->log_context.version, &last_tstamp);
2556 1.2 christos context->log_context.last_time = last_tstamp;
2557 1.2 christos krb5_storage_free(sp);
2558 1.2 christos return ret;
2559 1.2 christos }
2560 1.1 elric
2561 1.2 christos /*
2562 1.2 christos * "Truncate" the log if not read only and over the desired maximum size. We
2563 1.2 christos * attempt to retain 1/4 of the existing storage.
2564 1.2 christos *
2565 1.2 christos * Called after successful log recovery, so at this point we must have no
2566 1.2 christos * unconfirmed entries in the log.
2567 1.2 christos */
2568 1.2 christos static kadm5_ret_t
2569 1.2 christos truncate_if_needed(kadm5_server_context *context)
2570 1.2 christos {
2571 1.2 christos kadm5_ret_t ret = 0;
2572 1.2 christos kadm5_log_context *log_context = &context->log_context;
2573 1.2 christos size_t maxbytes;
2574 1.2 christos struct stat st;
2575 1.1 elric
2576 1.2 christos if (log_context->log_fd == -1 || log_context->read_only)
2577 1.2 christos return 0;
2578 1.2 christos if (strcmp(context->log_context.log_file, "/dev/null") == 0)
2579 1.2 christos return 0;
2580 1.2 christos
2581 1.2 christos maxbytes = get_max_log_size(context->context);
2582 1.2 christos if (maxbytes <= 0)
2583 1.2 christos return 0;
2584 1.2 christos
2585 1.2 christos if (fstat(log_context->log_fd, &st) == -1)
2586 1.2 christos return errno;
2587 1.2 christos if (st.st_size == (size_t)st.st_size && (size_t)st.st_size <= maxbytes)
2588 1.2 christos return 0;
2589 1.2 christos
2590 1.2 christos /* Shrink the log by a factor of 4 */
2591 1.2 christos ret = kadm5_log_truncate(context, 0, maxbytes/4);
2592 1.2 christos return ret == EINVAL ? 0 : ret;
2593 1.1 elric }
2594 1.1 elric
2595 1.1 elric #ifndef NO_UNIX_SOCKETS
2596 1.1 elric
2597 1.1 elric static char *default_signal = NULL;
2598 1.1 elric static HEIMDAL_MUTEX signal_mutex = HEIMDAL_MUTEX_INITIALIZER;
2599 1.1 elric
2600 1.1 elric const char *
2601 1.1 elric kadm5_log_signal_socket(krb5_context context)
2602 1.1 elric {
2603 1.2 christos int ret = 0;
2604 1.2 christos
2605 1.1 elric HEIMDAL_MUTEX_lock(&signal_mutex);
2606 1.1 elric if (!default_signal)
2607 1.2 christos ret = asprintf(&default_signal, "%s/signal", hdb_db_dir(context));
2608 1.2 christos if (ret == -1)
2609 1.2 christos default_signal = NULL;
2610 1.1 elric HEIMDAL_MUTEX_unlock(&signal_mutex);
2611 1.1 elric
2612 1.1 elric return krb5_config_get_string_default(context,
2613 1.1 elric NULL,
2614 1.1 elric default_signal,
2615 1.1 elric "kdc",
2616 1.1 elric "signal_socket",
2617 1.1 elric NULL);
2618 1.1 elric }
2619 1.1 elric
2620 1.1 elric #else /* NO_UNIX_SOCKETS */
2621 1.1 elric
2622 1.1 elric #define SIGNAL_SOCKET_HOST "127.0.0.1"
2623 1.1 elric #define SIGNAL_SOCKET_PORT "12701"
2624 1.1 elric
2625 1.1 elric kadm5_ret_t
2626 1.1 elric kadm5_log_signal_socket_info(krb5_context context,
2627 1.1 elric int server_end,
2628 1.1 elric struct addrinfo **ret_addrs)
2629 1.1 elric {
2630 1.1 elric struct addrinfo hints;
2631 1.1 elric struct addrinfo *addrs = NULL;
2632 1.1 elric kadm5_ret_t ret = KADM5_FAILURE;
2633 1.1 elric int wsret;
2634 1.1 elric
2635 1.1 elric memset(&hints, 0, sizeof(hints));
2636 1.1 elric
2637 1.1 elric hints.ai_flags = AI_NUMERICHOST;
2638 1.1 elric if (server_end)
2639 1.1 elric hints.ai_flags |= AI_PASSIVE;
2640 1.1 elric hints.ai_family = AF_INET;
2641 1.1 elric hints.ai_socktype = SOCK_STREAM;
2642 1.1 elric hints.ai_protocol = IPPROTO_TCP;
2643 1.1 elric
2644 1.1 elric wsret = getaddrinfo(SIGNAL_SOCKET_HOST,
2645 1.1 elric SIGNAL_SOCKET_PORT,
2646 1.1 elric &hints, &addrs);
2647 1.1 elric
2648 1.1 elric if (wsret != 0) {
2649 1.1 elric krb5_set_error_message(context, KADM5_FAILURE,
2650 1.1 elric "%s", gai_strerror(wsret));
2651 1.1 elric goto done;
2652 1.1 elric }
2653 1.1 elric
2654 1.1 elric if (addrs == NULL) {
2655 1.1 elric krb5_set_error_message(context, KADM5_FAILURE,
2656 1.1 elric "getaddrinfo() failed to return address list");
2657 1.1 elric goto done;
2658 1.1 elric }
2659 1.1 elric
2660 1.1 elric *ret_addrs = addrs;
2661 1.1 elric addrs = NULL;
2662 1.1 elric ret = 0;
2663 1.1 elric
2664 1.1 elric done:
2665 1.1 elric if (addrs)
2666 1.1 elric freeaddrinfo(addrs);
2667 1.1 elric return ret;
2668 1.1 elric }
2669 1.1 elric
2670 1.1 elric #endif
2671