1/*
2 * Copyright (c) 1997 - 2007 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * 3. Neither the name of the Institute nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#include "kadm5_locl.h"
35#include "heim_threads.h"
36
37RCSID("$Id$");
38
39/*
40 * A log record consists of:
41 *
42 * version number		4 bytes
43 * time in seconds		4 bytes
44 * operation (enum kadm_ops)	4 bytes
45 * length of record		4 bytes
46 * data...			n bytes
47 * length of record		4 bytes
48 * version number		4 bytes
49 *
50 */
51
52kadm5_ret_t
53kadm5_log_get_version_fd (int fd,
54			  uint32_t *ver)
55{
56    int ret;
57    off_t oret;
58    krb5_storage *sp;
59    int32_t old_version;
60
61    oret = lseek (fd, 0, SEEK_END);
62    if(oret < 0)
63	return errno;
64    if(oret == 0) {
65	*ver = 0;
66	return 0;
67    }
68    sp = krb5_storage_from_fd (fd);
69    krb5_storage_seek(sp, -4, SEEK_CUR);
70    ret = krb5_ret_int32 (sp, &old_version);
71    krb5_storage_free(sp);
72    lseek (fd, 0, SEEK_END);
73    if (ret)
74	return ret;
75    *ver = old_version;
76    return 0;
77}
78
79kadm5_ret_t
80kadm5_log_get_version (kadm5_server_context *context, uint32_t *ver)
81{
82    return kadm5_log_get_version_fd (context->log_context.log_fd, ver);
83}
84
85kadm5_ret_t
86kadm5_log_set_version (kadm5_server_context *context, uint32_t vno)
87{
88    kadm5_log_context *log_context = &context->log_context;
89
90    log_context->version = vno;
91    return 0;
92}
93
94kadm5_ret_t
95kadm5_log_init (kadm5_server_context *context)
96{
97    int fd;
98    kadm5_ret_t ret;
99    kadm5_log_context *log_context = &context->log_context;
100
101    if (log_context->log_fd != -1)
102	return 0;
103    fd = open (log_context->log_file, O_RDWR | O_CREAT, 0600);
104    if (fd < 0) {
105	ret = errno;
106	krb5_set_error_message(context->context, ret, "kadm5_log_init: open %s",
107			      log_context->log_file);
108	return ret;
109    }
110    if (flock (fd, LOCK_EX) < 0) {
111	ret = errno;
112	krb5_set_error_message(context->context, ret, "kadm5_log_init: flock %s",
113			       log_context->log_file);
114	close (fd);
115	return errno;
116    }
117
118    ret = kadm5_log_get_version_fd (fd, &log_context->version);
119    if (ret)
120	return ret;
121
122    log_context->log_fd  = fd;
123    return 0;
124}
125
126kadm5_ret_t
127kadm5_log_reinit (kadm5_server_context *context)
128{
129    int fd;
130    kadm5_log_context *log_context = &context->log_context;
131
132    if (log_context->log_fd != -1) {
133	flock (log_context->log_fd, LOCK_UN);
134	close (log_context->log_fd);
135	log_context->log_fd = -1;
136    }
137    fd = open (log_context->log_file, O_RDWR | O_CREAT | O_TRUNC, 0600);
138    if (fd < 0)
139	return errno;
140    if (flock (fd, LOCK_EX) < 0) {
141	close (fd);
142	return errno;
143    }
144
145    log_context->version = 0;
146    log_context->log_fd  = fd;
147    return 0;
148}
149
150
151kadm5_ret_t
152kadm5_log_end (kadm5_server_context *context)
153{
154    kadm5_log_context *log_context = &context->log_context;
155    int fd = log_context->log_fd;
156
157    flock (fd, LOCK_UN);
158    close(fd);
159    log_context->log_fd = -1;
160    return 0;
161}
162
163static kadm5_ret_t
164kadm5_log_preamble (kadm5_server_context *context,
165		    krb5_storage *sp,
166		    enum kadm_ops op)
167{
168    kadm5_log_context *log_context = &context->log_context;
169    kadm5_ret_t kadm_ret;
170
171    kadm_ret = kadm5_log_init (context);
172    if (kadm_ret)
173	return kadm_ret;
174
175    krb5_store_int32 (sp, ++log_context->version);
176    krb5_store_int32 (sp, (int32_t)time(NULL));
177    krb5_store_int32 (sp, op);
178    return 0;
179}
180
181static kadm5_ret_t
182kadm5_log_postamble (kadm5_log_context *context,
183		     krb5_storage *sp)
184{
185    krb5_store_int32 (sp, context->version);
186    return 0;
187}
188
189/*
190 * flush the log record in `sp'.
191 */
192
193static kadm5_ret_t
194kadm5_log_flush (kadm5_log_context *log_context,
195		 krb5_storage *sp)
196{
197    krb5_data data;
198    size_t len;
199    ssize_t ret;
200
201    krb5_storage_to_data(sp, &data);
202    len = data.length;
203    ret = write (log_context->log_fd, data.data, len);
204    if (ret < 0 || (size_t)ret != len) {
205	krb5_data_free(&data);
206	return errno;
207    }
208    if (fsync (log_context->log_fd) < 0) {
209	krb5_data_free(&data);
210	return errno;
211    }
212
213    /*
214     * Try to send a signal to any running `ipropd-master'
215     */
216#ifndef NO_UNIX_SOCKETS
217    sendto (log_context->socket_fd,
218	    (void *)&log_context->version,
219	    sizeof(log_context->version),
220	    0,
221	    (struct sockaddr *)&log_context->socket_name,
222	    sizeof(log_context->socket_name));
223#else
224    sendto (log_context->socket_fd,
225	    (void *)&log_context->version,
226	    sizeof(log_context->version),
227	    0,
228	    log_context->socket_info->ai_addr,
229	    log_context->socket_info->ai_addrlen);
230#endif
231
232    krb5_data_free(&data);
233    return 0;
234}
235
236/*
237 * Add a `create' operation to the log.
238 */
239
240kadm5_ret_t
241kadm5_log_create (kadm5_server_context *context,
242		  hdb_entry *ent)
243{
244    krb5_storage *sp;
245    kadm5_ret_t ret;
246    krb5_data value;
247    kadm5_log_context *log_context = &context->log_context;
248
249    sp = krb5_storage_emem();
250    ret = hdb_entry2value (context->context, ent, &value);
251    if (ret) {
252	krb5_storage_free(sp);
253	return ret;
254    }
255    ret = kadm5_log_preamble (context, sp, kadm_create);
256    if (ret) {
257	krb5_data_free (&value);
258	krb5_storage_free(sp);
259	return ret;
260    }
261    krb5_store_int32 (sp, (int32_t)value.length);
262    krb5_storage_write(sp, value.data, value.length);
263    krb5_store_int32 (sp, (int32_t)value.length);
264    krb5_data_free (&value);
265    ret = kadm5_log_postamble (log_context, sp);
266    if (ret) {
267	krb5_storage_free (sp);
268	return ret;
269    }
270    ret = kadm5_log_flush (log_context, sp);
271    krb5_storage_free (sp);
272    if (ret)
273	return ret;
274    ret = kadm5_log_end (context);
275    return ret;
276}
277
278/*
279 * Read the data of a create log record from `sp' and change the
280 * database.
281 */
282
283static kadm5_ret_t
284kadm5_log_replay_create (kadm5_server_context *context,
285			 uint32_t ver,
286			 uint32_t len,
287			 krb5_storage *sp)
288{
289    krb5_error_code ret;
290    krb5_data data;
291    hdb_entry_ex ent;
292
293    memset(&ent, 0, sizeof(ent));
294
295    ret = krb5_data_alloc (&data, len);
296    if (ret) {
297	krb5_set_error_message(context->context, ret, "out of memory");
298	return ret;
299    }
300    krb5_storage_read (sp, data.data, len);
301    ret = hdb_value2entry (context->context, &data, &ent.entry);
302    krb5_data_free(&data);
303    if (ret) {
304	krb5_set_error_message(context->context, ret,
305			       "Unmarshaling hdb entry failed");
306	return ret;
307    }
308    ret = context->db->hdb_store(context->context, context->db, 0, &ent);
309    hdb_free_entry (context->context, &ent);
310    return ret;
311}
312
313/*
314 * Add a `delete' operation to the log.
315 */
316
317kadm5_ret_t
318kadm5_log_delete (kadm5_server_context *context,
319		  krb5_principal princ)
320{
321    krb5_storage *sp;
322    kadm5_ret_t ret;
323    off_t off;
324    off_t len;
325    kadm5_log_context *log_context = &context->log_context;
326
327    sp = krb5_storage_emem();
328    if (sp == NULL)
329	return ENOMEM;
330    ret = kadm5_log_preamble (context, sp, kadm_delete);
331    if (ret)
332	goto out;
333    ret = krb5_store_int32 (sp, 0);
334    if (ret)
335	goto out;
336    off = krb5_storage_seek (sp, 0, SEEK_CUR);
337    ret = krb5_store_principal (sp, princ);
338    if (ret)
339	goto out;
340    len = krb5_storage_seek (sp, 0, SEEK_CUR) - off;
341    krb5_storage_seek(sp, -(len + 4), SEEK_CUR);
342    ret = krb5_store_int32 (sp, (int32_t)len);
343    if (ret)
344	goto out;
345    krb5_storage_seek(sp, len, SEEK_CUR);
346    ret = krb5_store_int32 (sp, (int32_t)len);
347    if (ret)
348	goto out;
349    ret = kadm5_log_postamble (log_context, sp);
350    if (ret)
351	goto out;
352    ret = kadm5_log_flush (log_context, sp);
353    if (ret)
354	goto out;
355    ret = kadm5_log_end (context);
356out:
357    krb5_storage_free (sp);
358    return ret;
359}
360
361/*
362 * Read a `delete' log operation from `sp' and apply it.
363 */
364
365static kadm5_ret_t
366kadm5_log_replay_delete (kadm5_server_context *context,
367			 uint32_t ver,
368			 uint32_t len,
369			 krb5_storage *sp)
370{
371    krb5_error_code ret;
372    krb5_principal principal;
373
374    ret = krb5_ret_principal (sp, &principal);
375    if (ret) {
376	krb5_set_error_message(context->context,  ret, "Failed to read deleted "
377			       "principal from log version: %ld",  (long)ver);
378	return ret;
379    }
380
381    ret = context->db->hdb_remove(context->context, context->db, principal);
382    krb5_free_principal (context->context, principal);
383    return ret;
384}
385
386/*
387 * Add a `rename' operation to the log.
388 */
389
390kadm5_ret_t
391kadm5_log_rename (kadm5_server_context *context,
392		  krb5_principal source,
393		  hdb_entry *ent)
394{
395    krb5_storage *sp;
396    kadm5_ret_t ret;
397    off_t off;
398    off_t len;
399    krb5_data value;
400    kadm5_log_context *log_context = &context->log_context;
401
402    krb5_data_zero(&value);
403
404    sp = krb5_storage_emem();
405    ret = hdb_entry2value (context->context, ent, &value);
406    if (ret)
407	goto failed;
408
409    ret = kadm5_log_preamble (context, sp, kadm_rename);
410    if (ret)
411	goto failed;
412
413    ret = krb5_store_int32 (sp, 0);
414    if (ret)
415	goto failed;
416    off = krb5_storage_seek (sp, 0, SEEK_CUR);
417    ret = krb5_store_principal (sp, source);
418    if (ret)
419	goto failed;
420
421    krb5_storage_write(sp, value.data, value.length);
422    len = krb5_storage_seek (sp, 0, SEEK_CUR) - off;
423
424    krb5_storage_seek(sp, -(len + 4), SEEK_CUR);
425    ret = krb5_store_int32 (sp, (int32_t)len);
426    if (ret)
427	goto failed;
428
429    krb5_storage_seek(sp, len, SEEK_CUR);
430    ret = krb5_store_int32 (sp, (int32_t)len);
431    if (ret)
432	goto failed;
433
434    ret = kadm5_log_postamble (log_context, sp);
435    if (ret)
436	goto failed;
437
438    ret = kadm5_log_flush (log_context, sp);
439    if (ret)
440	goto failed;
441    krb5_storage_free (sp);
442    krb5_data_free (&value);
443
444    return kadm5_log_end (context);
445
446failed:
447    krb5_data_free(&value);
448    krb5_storage_free(sp);
449    return ret;
450}
451
452/*
453 * Read a `rename' log operation from `sp' and apply it.
454 */
455
456static kadm5_ret_t
457kadm5_log_replay_rename (kadm5_server_context *context,
458			 uint32_t ver,
459			 uint32_t len,
460			 krb5_storage *sp)
461{
462    krb5_error_code ret;
463    krb5_principal source;
464    hdb_entry_ex target_ent;
465    krb5_data value;
466    off_t off;
467    size_t princ_len, data_len;
468
469    memset(&target_ent, 0, sizeof(target_ent));
470
471    off = krb5_storage_seek(sp, 0, SEEK_CUR);
472    ret = krb5_ret_principal (sp, &source);
473    if (ret) {
474	krb5_set_error_message(context->context, ret, "Failed to read renamed "
475			       "principal in log, version: %ld", (long)ver);
476	return ret;
477    }
478    princ_len = (size_t)(krb5_storage_seek(sp, 0, SEEK_CUR) - off);
479    data_len = len - princ_len;
480    ret = krb5_data_alloc (&value, data_len);
481    if (ret) {
482	krb5_free_principal (context->context, source);
483	return ret;
484    }
485    krb5_storage_read (sp, value.data, data_len);
486    ret = hdb_value2entry (context->context, &value, &target_ent.entry);
487    krb5_data_free(&value);
488    if (ret) {
489	krb5_free_principal (context->context, source);
490	return ret;
491    }
492    ret = context->db->hdb_store (context->context, context->db,
493				  0, &target_ent);
494    hdb_free_entry (context->context, &target_ent);
495    if (ret) {
496	krb5_free_principal (context->context, source);
497	return ret;
498    }
499    ret = context->db->hdb_remove (context->context, context->db, source);
500    krb5_free_principal (context->context, source);
501    return ret;
502}
503
504
505/*
506 * Add a `modify' operation to the log.
507 */
508
509kadm5_ret_t
510kadm5_log_modify (kadm5_server_context *context,
511		  hdb_entry *ent,
512		  uint32_t mask)
513{
514    krb5_storage *sp;
515    kadm5_ret_t ret;
516    krb5_data value;
517    size_t len;
518    kadm5_log_context *log_context = &context->log_context;
519
520    krb5_data_zero(&value);
521
522    sp = krb5_storage_emem();
523    ret = hdb_entry2value (context->context, ent, &value);
524    if (ret)
525	goto failed;
526
527    ret = kadm5_log_preamble (context, sp, kadm_modify);
528    if (ret)
529	goto failed;
530
531    len = value.length + 4;
532    ret = krb5_store_int32 (sp, (int32_t)len);
533    if (ret)
534	goto failed;
535    ret = krb5_store_int32 (sp, mask);
536    if (ret)
537	goto failed;
538    krb5_storage_write (sp, value.data, value.length);
539
540    ret = krb5_store_int32 (sp, (int32_t)len);
541    if (ret)
542	goto failed;
543    ret = kadm5_log_postamble (log_context, sp);
544    if (ret)
545	goto failed;
546    ret = kadm5_log_flush (log_context, sp);
547    if (ret)
548	goto failed;
549    krb5_data_free(&value);
550    krb5_storage_free (sp);
551    return kadm5_log_end (context);
552failed:
553    krb5_data_free(&value);
554    krb5_storage_free(sp);
555    return ret;
556}
557
558/*
559 * Read a `modify' log operation from `sp' and apply it.
560 */
561
562static kadm5_ret_t
563kadm5_log_replay_modify (kadm5_server_context *context,
564			 uint32_t ver,
565			 uint32_t len,
566			 krb5_storage *sp)
567{
568    krb5_error_code ret;
569    int32_t mask;
570    krb5_data value;
571    hdb_entry_ex ent, log_ent;
572
573    memset(&log_ent, 0, sizeof(log_ent));
574
575    krb5_ret_int32 (sp, &mask);
576    len -= 4;
577    ret = krb5_data_alloc (&value, len);
578    if (ret) {
579	krb5_set_error_message(context->context, ret, "out of memory");
580	return ret;
581    }
582    krb5_storage_read (sp, value.data, len);
583    ret = hdb_value2entry (context->context, &value, &log_ent.entry);
584    krb5_data_free(&value);
585    if (ret)
586	return ret;
587
588    memset(&ent, 0, sizeof(ent));
589    ret = context->db->hdb_fetch_kvno(context->context, context->db,
590				      log_ent.entry.principal,
591				      HDB_F_DECRYPT|HDB_F_ALL_KVNOS|
592				      HDB_F_GET_ANY|HDB_F_ADMIN_DATA, 0, &ent);
593    if (ret)
594	goto out;
595    if (mask & KADM5_PRINC_EXPIRE_TIME) {
596	if (log_ent.entry.valid_end == NULL) {
597	    ent.entry.valid_end = NULL;
598	} else {
599	    if (ent.entry.valid_end == NULL) {
600		ent.entry.valid_end = malloc(sizeof(*ent.entry.valid_end));
601		if (ent.entry.valid_end == NULL) {
602		    ret = ENOMEM;
603		    krb5_set_error_message(context->context, ret, "out of memory");
604		    goto out;
605		}
606	    }
607	    *ent.entry.valid_end = *log_ent.entry.valid_end;
608	}
609    }
610    if (mask & KADM5_PW_EXPIRATION) {
611	if (log_ent.entry.pw_end == NULL) {
612	    ent.entry.pw_end = NULL;
613	} else {
614	    if (ent.entry.pw_end == NULL) {
615		ent.entry.pw_end = malloc(sizeof(*ent.entry.pw_end));
616		if (ent.entry.pw_end == NULL) {
617		    ret = ENOMEM;
618		    krb5_set_error_message(context->context, ret, "out of memory");
619		    goto out;
620		}
621	    }
622	    *ent.entry.pw_end = *log_ent.entry.pw_end;
623	}
624    }
625    if (mask & KADM5_LAST_PWD_CHANGE) {
626	abort ();		/* XXX */
627    }
628    if (mask & KADM5_ATTRIBUTES) {
629	ent.entry.flags = log_ent.entry.flags;
630    }
631    if (mask & KADM5_MAX_LIFE) {
632	if (log_ent.entry.max_life == NULL) {
633	    ent.entry.max_life = NULL;
634	} else {
635	    if (ent.entry.max_life == NULL) {
636		ent.entry.max_life = malloc (sizeof(*ent.entry.max_life));
637		if (ent.entry.max_life == NULL) {
638		    ret = ENOMEM;
639		    krb5_set_error_message(context->context, ret, "out of memory");
640		    goto out;
641		}
642	    }
643	    *ent.entry.max_life = *log_ent.entry.max_life;
644	}
645    }
646    if ((mask & KADM5_MOD_TIME) && (mask & KADM5_MOD_NAME)) {
647	if (ent.entry.modified_by == NULL) {
648	    ent.entry.modified_by = malloc(sizeof(*ent.entry.modified_by));
649	    if (ent.entry.modified_by == NULL) {
650		ret = ENOMEM;
651		krb5_set_error_message(context->context, ret, "out of memory");
652		goto out;
653	    }
654	} else
655	    free_Event(ent.entry.modified_by);
656	ret = copy_Event(log_ent.entry.modified_by, ent.entry.modified_by);
657	if (ret) {
658	    krb5_set_error_message(context->context, ret, "out of memory");
659	    goto out;
660	}
661    }
662    if (mask & KADM5_KVNO) {
663	ent.entry.kvno = log_ent.entry.kvno;
664    }
665    if (mask & KADM5_MKVNO) {
666	abort ();		/* XXX */
667    }
668    if (mask & KADM5_AUX_ATTRIBUTES) {
669	abort ();		/* XXX */
670    }
671    if (mask & KADM5_POLICY) {
672	abort ();		/* XXX */
673    }
674    if (mask & KADM5_POLICY_CLR) {
675	abort ();		/* XXX */
676    }
677    if (mask & KADM5_MAX_RLIFE) {
678	if (log_ent.entry.max_renew == NULL) {
679	    ent.entry.max_renew = NULL;
680	} else {
681	    if (ent.entry.max_renew == NULL) {
682		ent.entry.max_renew = malloc (sizeof(*ent.entry.max_renew));
683		if (ent.entry.max_renew == NULL) {
684		    ret = ENOMEM;
685		    krb5_set_error_message(context->context, ret, "out of memory");
686		    goto out;
687		}
688	    }
689	    *ent.entry.max_renew = *log_ent.entry.max_renew;
690	}
691    }
692    if (mask & KADM5_LAST_SUCCESS) {
693	abort ();		/* XXX */
694    }
695    if (mask & KADM5_LAST_FAILED) {
696	abort ();		/* XXX */
697    }
698    if (mask & KADM5_FAIL_AUTH_COUNT) {
699	abort ();		/* XXX */
700    }
701    if (mask & KADM5_KEY_DATA) {
702	size_t num;
703	size_t i;
704
705	/*
706	 * We don't need to do anything about key history here because
707	 * we always log KADM5_TL_DATA when we change keys/passwords, so
708	 * the code below this will handle key history implicitly.
709	 * However, if we had to, the code to handle key history here
710	 * would look like this:
711	 *
712	 * HDB_extension *ext;
713	 * ...
714	 * ext = hdb_find_extension(&log_ent.entry,
715	 *                          choice_HDB_extension_data_hist_keys);
716	 * if (ext);
717	 *    ret = hdb_replace_extension(context->context, &ent.entry, ext);
718	 * else
719	 *    ret = hdb_clear_extension(context->context, &ent.entry,
720	 *                              choice_HDB_extension_data_hist_keys);
721	 *
722	 * Maybe we should do this here anyways, wasteful as it would
723	 * be, as a defensive programming measure?  For now we heim_assert().
724	 */
725	heim_assert((mask & KADM5_TL_DATA),
726		    "Wouldn't log and replay key history");
727
728	for (i = 0; i < ent.entry.keys.len; ++i)
729	    free_Key(&ent.entry.keys.val[i]);
730	free (ent.entry.keys.val);
731
732	num = log_ent.entry.keys.len;
733
734	ent.entry.keys.len = (int)num;
735	ent.entry.keys.val = malloc(len * sizeof(*ent.entry.keys.val));
736	if (ent.entry.keys.val == NULL) {
737	    krb5_set_error_message(context->context, ENOMEM, "out of memory");
738	    return ENOMEM;
739	}
740	for (i = 0; i < ent.entry.keys.len; ++i) {
741	    ret = copy_Key(&log_ent.entry.keys.val[i],
742			   &ent.entry.keys.val[i]);
743	    if (ret) {
744		krb5_set_error_message(context->context, ret, "out of memory");
745		goto out;
746	    }
747	}
748    }
749    if ((mask & KADM5_TL_DATA) && log_ent.entry.extensions) {
750	HDB_extensions *es = ent.entry.extensions;
751
752	ent.entry.extensions = calloc(1, sizeof(*ent.entry.extensions));
753	if (ent.entry.extensions == NULL)
754	    goto out;
755
756	ret = copy_HDB_extensions(log_ent.entry.extensions,
757				  ent.entry.extensions);
758	if (ret) {
759	    krb5_set_error_message(context->context, ret, "out of memory");
760	    free(ent.entry.extensions);
761	    ent.entry.extensions = es;
762	    goto out;
763	}
764	if (es) {
765	    free_HDB_extensions(es);
766	    free(es);
767	}
768    }
769    ret = context->db->hdb_store(context->context, context->db,
770				 HDB_F_REPLACE, &ent);
771 out:
772    hdb_free_entry (context->context, &ent);
773    hdb_free_entry (context->context, &log_ent);
774    return ret;
775}
776
777/*
778 * Add a `nop' operation to the log. Does not close the log.
779 */
780
781kadm5_ret_t
782kadm5_log_nop (kadm5_server_context *context)
783{
784    krb5_storage *sp;
785    kadm5_ret_t ret;
786    kadm5_log_context *log_context = &context->log_context;
787
788    sp = krb5_storage_emem();
789    ret = kadm5_log_preamble (context, sp, kadm_nop);
790    if (ret) {
791	krb5_storage_free (sp);
792	return ret;
793    }
794    krb5_store_int32 (sp, 0);
795    krb5_store_int32 (sp, 0);
796    ret = kadm5_log_postamble (log_context, sp);
797    if (ret) {
798	krb5_storage_free (sp);
799	return ret;
800    }
801    ret = kadm5_log_flush (log_context, sp);
802    krb5_storage_free (sp);
803
804    return ret;
805}
806
807/*
808 * Read a `nop' log operation from `sp' and apply it.
809 */
810
811static kadm5_ret_t
812kadm5_log_replay_nop (kadm5_server_context *context,
813		      uint32_t ver,
814		      uint32_t len,
815		      krb5_storage *sp)
816{
817    return 0;
818}
819
820/*
821 * Call `func' for each log record in the log in `context'
822 */
823
824kadm5_ret_t
825kadm5_log_foreach (kadm5_server_context *context,
826		   void (*func)(kadm5_server_context *server_context,
827				uint32_t ver,
828				time_t timestamp,
829				enum kadm_ops op,
830				uint32_t len,
831				krb5_storage *,
832				void *),
833		   void *ctx)
834{
835    int fd = context->log_context.log_fd;
836    krb5_storage *sp;
837
838    lseek (fd, 0, SEEK_SET);
839    sp = krb5_storage_from_fd (fd);
840    for (;;) {
841	int32_t ver, timestamp, op, len, len2, ver2;
842
843	if(krb5_ret_int32 (sp, &ver) != 0)
844	    break;
845	krb5_ret_int32 (sp, &timestamp);
846	krb5_ret_int32 (sp, &op);
847	krb5_ret_int32 (sp, &len);
848	(*func)(context, ver, timestamp, op, len, sp, ctx);
849	krb5_ret_int32 (sp, &len2);
850	krb5_ret_int32 (sp, &ver2);
851	if (len != len2)
852	    abort();
853	if (ver != ver2)
854	    abort();
855    }
856    krb5_storage_free(sp);
857    return 0;
858}
859
860/*
861 * Go to end of log.
862 */
863
864krb5_storage *
865kadm5_log_goto_end (int fd)
866{
867    krb5_storage *sp;
868
869    sp = krb5_storage_from_fd (fd);
870    krb5_storage_seek(sp, 0, SEEK_END);
871    return sp;
872}
873
874/*
875 * Return previous log entry.
876 *
877 * The pointer in `sp´ is assumed to be at the top of the entry before
878 * previous entry. On success, the `sp´ pointer is set to data portion
879 * of previous entry. In case of error, it's not changed at all.
880 */
881
882kadm5_ret_t
883kadm5_log_previous (krb5_context context,
884		    krb5_storage *sp,
885		    uint32_t *ver,
886		    time_t *timestamp,
887		    enum kadm_ops *op,
888		    uint32_t *len)
889{
890    krb5_error_code ret;
891    off_t off, oldoff;
892    int32_t tmp;
893
894    oldoff = krb5_storage_seek(sp, 0, SEEK_CUR);
895
896    krb5_storage_seek(sp, -8, SEEK_CUR);
897    ret = krb5_ret_int32 (sp, &tmp);
898    if (ret)
899	goto end_of_storage;
900    *len = tmp;
901    ret = krb5_ret_int32 (sp, &tmp);
902    if (ret)
903	goto end_of_storage;
904    *ver = tmp;
905    off = 24 + *len;
906    krb5_storage_seek(sp, -off, SEEK_CUR);
907    ret = krb5_ret_int32 (sp, &tmp);
908    if (ret)
909	goto end_of_storage;
910    if ((uint32_t)tmp != *ver) {
911	krb5_storage_seek(sp, oldoff, SEEK_SET);
912	krb5_set_error_message(context, KADM5_BAD_DB,
913			       "kadm5_log_previous: log entry "
914			       "have consistency failure, version number wrong "
915			       "(tmp %lu ver %lu)",
916			       (unsigned long)tmp,
917			       (unsigned long)*ver);
918	return KADM5_BAD_DB;
919    }
920    ret = krb5_ret_int32 (sp, &tmp);
921    if (ret)
922	goto end_of_storage;
923    *timestamp = tmp;
924    ret = krb5_ret_int32 (sp, &tmp);
925    if (ret)
926	goto end_of_storage;
927    *op = tmp;
928    ret = krb5_ret_int32 (sp, &tmp);
929    if (ret)
930	goto end_of_storage;
931    if ((uint32_t)tmp != *len) {
932	krb5_storage_seek(sp, oldoff, SEEK_SET);
933	krb5_set_error_message(context, KADM5_BAD_DB,
934			       "kadm5_log_previous: log entry "
935			       "have consistency failure, length wrong");
936	return KADM5_BAD_DB;
937    }
938    return 0;
939
940 end_of_storage:
941    krb5_storage_seek(sp, oldoff, SEEK_SET);
942    krb5_set_error_message(context, ret, "kadm5_log_previous: end of storage "
943			   "reached before end");
944    return ret;
945}
946
947/*
948 * Replay a record from the log
949 */
950
951kadm5_ret_t
952kadm5_log_replay (kadm5_server_context *context,
953		  enum kadm_ops op,
954		  uint32_t ver,
955		  uint32_t len,
956		  krb5_storage *sp)
957{
958    switch (op) {
959    case kadm_create :
960	return kadm5_log_replay_create (context, ver, len, sp);
961    case kadm_delete :
962	return kadm5_log_replay_delete (context, ver, len, sp);
963    case kadm_rename :
964	return kadm5_log_replay_rename (context, ver, len, sp);
965    case kadm_modify :
966	return kadm5_log_replay_modify (context, ver, len, sp);
967    case kadm_nop :
968	return kadm5_log_replay_nop (context, ver, len, sp);
969    default :
970	krb5_set_error_message(context->context, KADM5_FAILURE,
971			       "Unsupported replay op %d", (int)op);
972	return KADM5_FAILURE;
973    }
974}
975
976/*
977 * truncate the log - i.e. create an empty file with just (nop vno + 2)
978 */
979
980kadm5_ret_t
981kadm5_log_truncate (kadm5_server_context *server_context)
982{
983    kadm5_ret_t ret;
984    uint32_t vno;
985
986    ret = kadm5_log_init (server_context);
987    if (ret)
988	return ret;
989
990    ret = kadm5_log_get_version (server_context, &vno);
991    if (ret)
992	return ret;
993
994    ret = kadm5_log_reinit (server_context);
995    if (ret)
996	return ret;
997
998    ret = kadm5_log_set_version (server_context, vno);
999    if (ret)
1000	return ret;
1001
1002    ret = kadm5_log_nop (server_context);
1003    if (ret)
1004	return ret;
1005
1006    ret = kadm5_log_end (server_context);
1007    if (ret)
1008	return ret;
1009    return 0;
1010
1011}
1012
1013#ifndef NO_UNIX_SOCKETS
1014
1015static char *default_signal = NULL;
1016static HEIMDAL_MUTEX signal_mutex = HEIMDAL_MUTEX_INITIALIZER;
1017
1018const char *
1019kadm5_log_signal_socket(krb5_context context)
1020{
1021    HEIMDAL_MUTEX_lock(&signal_mutex);
1022    if (!default_signal)
1023	asprintf(&default_signal, "%s/signal", hdb_db_dir(context));
1024    HEIMDAL_MUTEX_unlock(&signal_mutex);
1025
1026    return krb5_config_get_string_default(context,
1027					  NULL,
1028					  default_signal,
1029					  "kdc",
1030					  "signal_socket",
1031					  NULL);
1032}
1033
1034#else  /* NO_UNIX_SOCKETS */
1035
1036#define SIGNAL_SOCKET_HOST "127.0.0.1"
1037#define SIGNAL_SOCKET_PORT "12701"
1038
1039kadm5_ret_t
1040kadm5_log_signal_socket_info(krb5_context context,
1041			     int server_end,
1042			     struct addrinfo **ret_addrs)
1043{
1044    struct addrinfo hints;
1045    struct addrinfo *addrs = NULL;
1046    kadm5_ret_t ret = KADM5_FAILURE;
1047    int wsret;
1048
1049    memset(&hints, 0, sizeof(hints));
1050
1051    hints.ai_flags = AI_NUMERICHOST;
1052    if (server_end)
1053	hints.ai_flags |= AI_PASSIVE;
1054    hints.ai_family = AF_INET;
1055    hints.ai_socktype = SOCK_STREAM;
1056    hints.ai_protocol = IPPROTO_TCP;
1057
1058    wsret = getaddrinfo(SIGNAL_SOCKET_HOST,
1059			SIGNAL_SOCKET_PORT,
1060			&hints, &addrs);
1061
1062    if (wsret != 0) {
1063	krb5_set_error_message(context, KADM5_FAILURE,
1064			       "%s", gai_strerror(wsret));
1065	goto done;
1066    }
1067
1068    if (addrs == NULL) {
1069	krb5_set_error_message(context, KADM5_FAILURE,
1070			       "getaddrinfo() failed to return address list");
1071	goto done;
1072    }
1073
1074    *ret_addrs = addrs;
1075    addrs = NULL;
1076    ret = 0;
1077
1078 done:
1079    if (addrs)
1080	freeaddrinfo(addrs);
1081    return ret;
1082}
1083
1084#endif
1085