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