xref: /netbsd-src/crypto/external/bsd/heimdal/dist/lib/kadm5/ipropd_master.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /*	$NetBSD: ipropd_master.c,v 1.2 2017/01/28 21:31:49 christos Exp $	*/
2 
3 /*
4  * Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan
5  * (Royal Institute of Technology, Stockholm, Sweden).
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  *
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * 3. Neither the name of the Institute nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #include "iprop.h"
37 #include <krb5/rtbl.h>
38 
39 static krb5_log_facility *log_facility;
40 
41 static int verbose;
42 
43 const char *slave_stats_file;
44 const char *slave_time_missing = "2 min";
45 const char *slave_time_gone = "5 min";
46 
47 static int time_before_missing;
48 static int time_before_gone;
49 
50 const char *master_hostname;
51 
52 static krb5_socket_t
53 make_signal_socket (krb5_context context)
54 {
55 #ifndef NO_UNIX_SOCKETS
56     struct sockaddr_un addr;
57     const char *fn;
58     krb5_socket_t fd;
59 
60     fn = kadm5_log_signal_socket(context);
61 
62     fd = socket (AF_UNIX, SOCK_DGRAM, 0);
63     if (fd < 0)
64 	krb5_err (context, 1, errno, "socket AF_UNIX");
65     memset (&addr, 0, sizeof(addr));
66     addr.sun_family = AF_UNIX;
67     strlcpy (addr.sun_path, fn, sizeof(addr.sun_path));
68     unlink (addr.sun_path);
69     if (bind (fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
70 	krb5_err (context, 1, errno, "bind %s", addr.sun_path);
71     return fd;
72 #else
73     struct addrinfo *ai = NULL;
74     krb5_socket_t fd;
75 
76     kadm5_log_signal_socket_info(context, 1, &ai);
77 
78     fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
79     if (rk_IS_BAD_SOCKET(fd))
80 	krb5_err (context, 1, rk_SOCK_ERRNO, "socket AF=%d", ai->ai_family);
81 
82     if (rk_IS_SOCKET_ERROR( bind (fd, ai->ai_addr, ai->ai_addrlen) ))
83 	krb5_err (context, 1, rk_SOCK_ERRNO, "bind");
84     return fd;
85 #endif
86 }
87 
88 static krb5_socket_t
89 make_listen_socket (krb5_context context, const char *port_str)
90 {
91     krb5_socket_t fd;
92     int one = 1;
93     struct sockaddr_in addr;
94 
95     fd = socket (AF_INET, SOCK_STREAM, 0);
96     if (rk_IS_BAD_SOCKET(fd))
97 	krb5_err (context, 1, rk_SOCK_ERRNO, "socket AF_INET");
98     setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one));
99     memset (&addr, 0, sizeof(addr));
100     addr.sin_family = AF_INET;
101 
102     if (port_str) {
103 	addr.sin_port = krb5_getportbyname (context,
104 					      port_str, "tcp",
105 					      0);
106 	if (addr.sin_port == 0) {
107 	    char *ptr;
108 	    long port;
109 
110 	    port = strtol (port_str, &ptr, 10);
111 	    if (port == 0 && ptr == port_str)
112 		krb5_errx (context, 1, "bad port `%s'", port_str);
113 	    addr.sin_port = htons(port);
114 	}
115     } else {
116 	addr.sin_port = krb5_getportbyname (context, IPROP_SERVICE,
117 					    "tcp", IPROP_PORT);
118     }
119     if(bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
120 	krb5_err (context, 1, errno, "bind");
121     if (listen(fd, SOMAXCONN) < 0)
122 	krb5_err (context, 1, errno, "listen");
123     return fd;
124 }
125 
126 struct slave {
127     krb5_socket_t fd;
128     struct sockaddr_in addr;
129     char *name;
130     krb5_auth_context ac;
131     uint32_t version;
132     uint32_t version_tstamp;
133     time_t seen;
134     unsigned long flags;
135 #define SLAVE_F_DEAD	0x1
136 #define SLAVE_F_AYT	0x2
137     struct slave *next;
138 };
139 
140 typedef struct slave slave;
141 
142 static int
143 check_acl (krb5_context context, const char *name)
144 {
145     const char *fn;
146     FILE *fp;
147     char buf[256];
148     int ret = 1;
149     char *slavefile = NULL;
150 
151     if (asprintf(&slavefile, "%s/slaves", hdb_db_dir(context)) == -1
152 	|| slavefile == NULL)
153 	errx(1, "out of memory");
154 
155     fn = krb5_config_get_string_default(context,
156 					NULL,
157 					slavefile,
158 					"kdc",
159 					"iprop-acl",
160 					NULL);
161 
162     fp = fopen (fn, "r");
163     free(slavefile);
164     if (fp == NULL)
165 	return 1;
166     while (fgets(buf, sizeof(buf), fp) != NULL) {
167 	buf[strcspn(buf, "\r\n")] = '\0';
168 	if (strcmp (buf, name) == 0) {
169 	    ret = 0;
170 	    break;
171 	}
172     }
173     fclose (fp);
174     return ret;
175 }
176 
177 static void
178 slave_seen(slave *s)
179 {
180     s->flags &= ~SLAVE_F_AYT;
181     s->seen = time(NULL);
182 }
183 
184 static int
185 slave_missing_p (slave *s)
186 {
187     if (time(NULL) > s->seen + time_before_missing)
188 	return 1;
189     return 0;
190 }
191 
192 static int
193 slave_gone_p (slave *s)
194 {
195     if (time(NULL) > s->seen + time_before_gone)
196 	return 1;
197     return 0;
198 }
199 
200 static void
201 slave_dead(krb5_context context, slave *s)
202 {
203     krb5_warnx(context, "slave %s dead", s->name);
204 
205     if (!rk_IS_BAD_SOCKET(s->fd)) {
206 	rk_closesocket (s->fd);
207 	s->fd = rk_INVALID_SOCKET;
208     }
209     s->flags |= SLAVE_F_DEAD;
210     slave_seen(s);
211 }
212 
213 static void
214 remove_slave (krb5_context context, slave *s, slave **root)
215 {
216     slave **p;
217 
218     if (!rk_IS_BAD_SOCKET(s->fd))
219 	rk_closesocket (s->fd);
220     if (s->name)
221 	free (s->name);
222     if (s->ac)
223 	krb5_auth_con_free (context, s->ac);
224 
225     for (p = root; *p; p = &(*p)->next)
226 	if (*p == s) {
227 	    *p = s->next;
228 	    break;
229 	}
230     free (s);
231 }
232 
233 static void
234 add_slave (krb5_context context, krb5_keytab keytab, slave **root,
235 	   krb5_socket_t fd)
236 {
237     krb5_principal server;
238     krb5_error_code ret;
239     slave *s;
240     socklen_t addr_len;
241     krb5_ticket *ticket = NULL;
242     char hostname[128];
243 
244     s = malloc(sizeof(*s));
245     if (s == NULL) {
246 	krb5_warnx (context, "add_slave: no memory");
247 	return;
248     }
249     s->name = NULL;
250     s->ac = NULL;
251 
252     addr_len = sizeof(s->addr);
253     s->fd = accept (fd, (struct sockaddr *)&s->addr, &addr_len);
254     if (rk_IS_BAD_SOCKET(s->fd)) {
255 	krb5_warn (context, rk_SOCK_ERRNO, "accept");
256 	goto error;
257     }
258     if (master_hostname)
259 	strlcpy(hostname, master_hostname, sizeof(hostname));
260     else
261 	gethostname(hostname, sizeof(hostname));
262 
263     ret = krb5_sname_to_principal (context, hostname, IPROP_NAME,
264 				   KRB5_NT_SRV_HST, &server);
265     if (ret) {
266 	krb5_warn (context, ret, "krb5_sname_to_principal");
267 	goto error;
268     }
269 
270     ret = krb5_recvauth (context, &s->ac, &s->fd,
271 			 IPROP_VERSION, server, 0, keytab, &ticket);
272     krb5_free_principal (context, server);
273     if (ret) {
274 	krb5_warn (context, ret, "krb5_recvauth");
275 	goto error;
276     }
277     ret = krb5_unparse_name (context, ticket->client, &s->name);
278     krb5_free_ticket (context, ticket);
279     if (ret) {
280 	krb5_warn (context, ret, "krb5_unparse_name");
281 	goto error;
282     }
283     if (check_acl (context, s->name)) {
284 	krb5_warnx (context, "%s not in acl", s->name);
285 	goto error;
286     }
287 
288     {
289 	slave *l = *root;
290 
291 	while (l) {
292 	    if (strcmp(l->name, s->name) == 0)
293 		break;
294 	    l = l->next;
295 	}
296 	if (l) {
297 	    if (l->flags & SLAVE_F_DEAD) {
298 		remove_slave(context, l, root);
299 	    } else {
300 		krb5_warnx (context, "second connection from %s", s->name);
301 		goto error;
302 	    }
303 	}
304     }
305 
306     krb5_warnx (context, "connection from %s", s->name);
307 
308     s->version = 0;
309     s->flags = 0;
310     slave_seen(s);
311     s->next = *root;
312     *root = s;
313     return;
314 error:
315     remove_slave(context, s, root);
316 }
317 
318 static int
319 dump_one (krb5_context context, HDB *db, hdb_entry_ex *entry, void *v)
320 {
321     krb5_error_code ret;
322     krb5_storage *dump = (krb5_storage *)v;
323     krb5_storage *sp;
324     krb5_data data;
325 
326     ret = hdb_entry2value (context, &entry->entry, &data);
327     if (ret)
328 	return ret;
329     ret = krb5_data_realloc (&data, data.length + 4);
330     if (ret)
331 	goto done;
332     memmove ((char *)data.data + 4, data.data, data.length - 4);
333     sp = krb5_storage_from_data(&data);
334     if (sp == NULL) {
335 	ret = ENOMEM;
336 	goto done;
337     }
338     ret = krb5_store_uint32(sp, ONE_PRINC);
339     krb5_storage_free(sp);
340 
341     if (ret == 0)
342         ret = krb5_store_data(dump, data);
343 
344 done:
345     krb5_data_free (&data);
346     return ret;
347 }
348 
349 static int
350 write_dump (krb5_context context, krb5_storage *dump,
351 	    const char *database, uint32_t current_version)
352 {
353     krb5_error_code ret;
354     krb5_storage *sp;
355     HDB *db;
356     krb5_data data;
357     char buf[8];
358 
359     /* we assume that the caller has obtained an exclusive lock */
360 
361     ret = krb5_storage_truncate(dump, 0);
362     if (ret)
363 	return ret;
364 
365     if (krb5_storage_seek(dump, 0, SEEK_SET) != 0)
366         return errno;
367 
368     /*
369      * First we store zero as the HDB version, this will indicate to a
370      * later reader that the dumpfile is invalid.  We later write the
371      * correct version in the file after we have written all of the
372      * messages.  A dump with a zero version will not be considered
373      * to be valid.
374      */
375 
376     ret = krb5_store_uint32(dump, 0);
377 
378     ret = hdb_create (context, &db, database);
379     if (ret)
380 	krb5_err (context, IPROPD_RESTART, ret, "hdb_create: %s", database);
381     ret = db->hdb_open (context, db, O_RDONLY, 0);
382     if (ret)
383 	krb5_err (context, IPROPD_RESTART, ret, "db->open");
384 
385     sp = krb5_storage_from_mem (buf, 4);
386     if (sp == NULL)
387 	krb5_errx (context, IPROPD_RESTART, "krb5_storage_from_mem");
388     krb5_store_uint32 (sp, TELL_YOU_EVERYTHING);
389     krb5_storage_free (sp);
390 
391     data.data   = buf;
392     data.length = 4;
393 
394     ret = krb5_store_data(dump, data);
395     if (ret) {
396 	krb5_warn (context, ret, "write_dump");
397 	return ret;
398     }
399 
400     ret = hdb_foreach (context, db, HDB_F_ADMIN_DATA, dump_one, dump);
401     if (ret) {
402 	krb5_warn (context, ret, "write_dump: hdb_foreach");
403 	return ret;
404     }
405 
406     (*db->hdb_close)(context, db);
407     (*db->hdb_destroy)(context, db);
408 
409     sp = krb5_storage_from_mem (buf, 8);
410     if (sp == NULL)
411 	krb5_errx (context, IPROPD_RESTART, "krb5_storage_from_mem");
412     ret = krb5_store_uint32(sp, NOW_YOU_HAVE);
413     if (ret == 0)
414       krb5_store_uint32(sp, current_version);
415     krb5_storage_free (sp);
416 
417     data.length = 8;
418 
419     if (ret == 0)
420         ret = krb5_store_data(dump, data);
421 
422     /*
423      * We must ensure that the entire valid dump is written to disk
424      * before we write the current version at the front thus making
425      * it a valid dump file.  If we crash around here, this can be
426      * important upon reboot.
427      */
428 
429     if (ret == 0)
430         ret = krb5_storage_fsync(dump);
431 
432     if (ret == 0 && krb5_storage_seek(dump, 0, SEEK_SET) == -1)
433 	ret = errno;
434 
435     /* Write current version at the front making the dump valid */
436 
437     if (ret == 0)
438         ret = krb5_store_uint32(dump, current_version);
439 
440     /*
441      * We don't need to fsync(2) after the real version is written as
442      * it is not a disaster if it doesn't make it to disk if we crash.
443      * After all, we'll just create a new dumpfile.
444      */
445 
446     if (ret == 0)
447         krb5_warnx(context, "wrote new dumpfile (version %u)",
448                    current_version);
449     else
450         krb5_warn(context, ret, "failed to write new dumpfile (version %u)",
451                   current_version);
452 
453     return ret;
454 }
455 
456 static int
457 send_complete (krb5_context context, slave *s, const char *database,
458 	       uint32_t current_version, uint32_t oldest_version,
459 	       uint32_t initial_log_tstamp)
460 {
461     krb5_error_code ret;
462     krb5_storage *dump = NULL;
463     uint32_t vno = 0;
464     krb5_data data;
465     int fd = -1;
466     struct stat st;
467     char *dfn;
468 
469     ret = asprintf(&dfn, "%s/ipropd.dumpfile", hdb_db_dir(context));
470     if (ret == -1 || !dfn) {
471 	krb5_warn(context, ENOMEM, "Cannot allocate memory");
472 	return ENOMEM;
473     }
474 
475     fd = open(dfn, O_CREAT|O_RDWR, 0600);
476     if (fd == -1) {
477 	ret = errno;
478 	krb5_warn(context, ret, "Cannot open/create iprop dumpfile %s", dfn);
479 	free(dfn);
480         return ret;
481     }
482     free(dfn);
483 
484     dump = krb5_storage_from_fd(fd);
485     if (!dump) {
486 	ret = errno;
487 	krb5_warn(context, ret, "krb5_storage_from_fd");
488 	goto done;
489     }
490 
491     for (;;) {
492 	ret = flock(fd, LOCK_SH);
493 	if (ret == -1) {
494 	    ret = errno;
495 	    krb5_warn(context, ret, "flock(fd, LOCK_SH)");
496 	    goto done;
497 	}
498 
499 	if (krb5_storage_seek(dump, 0, SEEK_SET) == (off_t)-1) {
500 	    ret = errno;
501 	    krb5_warn(context, ret, "krb5_storage_seek(dump, 0, SEEK_SET)");
502 	    goto done;
503 	}
504 
505 	vno = 0;
506 	ret = krb5_ret_uint32(dump, &vno);
507 	if (ret && ret != HEIM_ERR_EOF) {
508 	    krb5_warn(context, ret, "krb5_ret_uint32(dump, &vno)");
509 	    goto done;
510 	}
511 
512         if (fstat(fd, &st) == -1) {
513             ret = errno;
514             krb5_warn(context, ret, "send_complete: could not stat dump file");
515             goto done;
516         }
517 
518 	/*
519 	 * If the current dump has an appropriate version, then we can
520 	 * break out of the loop and send the file below.
521 	 */
522 
523 	if (ret == 0 && vno != 0 && st.st_mtime > initial_log_tstamp &&
524             vno >= oldest_version && vno <= current_version)
525 	    break;
526 
527         if (verbose)
528             krb5_warnx(context, "send_complete: dumping HDB");
529 
530 	/*
531 	 * Otherwise, we may need to write a new dump file.  We
532 	 * obtain an exclusive lock on the fd.  Because this is
533 	 * not guaranteed to be an upgrade of our existing shared
534 	 * lock, someone else may have written a new dumpfile while
535 	 * we were waiting and so we must first check the vno of
536 	 * the dump to see if that happened.  If it did, we need
537 	 * to go back to the top of the loop so that we can downgrade
538 	 * our lock to a shared one.
539 	 */
540 
541 	ret = flock(fd, LOCK_EX);
542 	if (ret == -1) {
543 	    ret = errno;
544 	    krb5_warn(context, ret, "flock(fd, LOCK_EX)");
545 	    goto done;
546 	}
547 
548 	ret = krb5_storage_seek(dump, 0, SEEK_SET);
549 	if (ret == -1) {
550 	    ret = errno;
551 	    krb5_warn(context, ret, "krb5_storage_seek(dump, 0, SEEK_SET)");
552 	    goto done;
553 	}
554 
555 	vno = 0;
556 	ret = krb5_ret_uint32(dump, &vno);
557 	if (ret && ret != HEIM_ERR_EOF) {
558 	    krb5_warn(context, ret, "krb5_ret_uint32(dump, &vno)");
559 	    goto done;
560 	}
561 
562         if (fstat(fd, &st) == -1) {
563             ret = errno;
564             krb5_warn(context, ret, "send_complete: could not stat dump file");
565             goto done;
566         }
567 
568 	/* check if someone wrote a better version for us */
569         if (ret == 0 && vno != 0 && st.st_mtime > initial_log_tstamp &&
570             vno >= oldest_version && vno <= current_version)
571 	    continue;
572 
573 	/* Now, we know that we must write a new dump file.  */
574 
575 	ret = write_dump(context, dump, database, current_version);
576 	if (ret)
577 	    goto done;
578 
579 	/*
580 	 * And we must continue to the top of the loop so that we can
581 	 * downgrade to a shared lock.
582 	 */
583     }
584 
585     /*
586      * Leaving the above loop, dump should have a ptr right after the initial
587      * 4 byte DB version number and we should have a shared lock on the file
588      * (which we may have just created), so we are reading to simply blast
589      * the data down the wire.
590      */
591 
592     for (;;) {
593 	ret = krb5_ret_data(dump, &data);
594 	if (ret == HEIM_ERR_EOF) {
595 	    ret = 0;	/* EOF is not an error, it's success */
596 	    goto done;
597 	}
598 
599 	if (ret) {
600 	    krb5_warn(context, ret, "krb5_ret_data(dump, &data)");
601 	    slave_dead(context, s);
602 	    goto done;
603 	}
604 
605 	ret = krb5_write_priv_message(context, s->ac, &s->fd, &data);
606 	krb5_data_free(&data);
607 
608 	if (ret) {
609 	    krb5_warn (context, ret, "krb5_write_priv_message");
610 	    slave_dead(context, s);
611 	    goto done;
612 	}
613     }
614 
615 done:
616     if (!ret) {
617 	s->version = vno;
618 	slave_seen(s);
619     }
620     if (fd != -1)
621 	close(fd);
622     if (dump)
623 	krb5_storage_free(dump);
624     return ret;
625 }
626 
627 static int
628 send_are_you_there (krb5_context context, slave *s)
629 {
630     krb5_storage *sp;
631     krb5_data data;
632     char buf[4];
633     int ret;
634 
635     if (s->flags & (SLAVE_F_DEAD|SLAVE_F_AYT))
636 	return 0;
637 
638     krb5_warnx(context, "slave %s missing, sending AYT", s->name);
639 
640     s->flags |= SLAVE_F_AYT;
641 
642     data.data = buf;
643     data.length = 4;
644 
645     sp = krb5_storage_from_mem (buf, 4);
646     if (sp == NULL) {
647 	krb5_warnx (context, "are_you_there: krb5_data_alloc");
648 	slave_dead(context, s);
649 	return 1;
650     }
651     ret = krb5_store_uint32(sp, ARE_YOU_THERE);
652     krb5_storage_free (sp);
653 
654     if (ret == 0) {
655         ret = krb5_write_priv_message(context, s->ac, &s->fd, &data);
656 
657         if (ret) {
658             krb5_warn(context, ret, "are_you_there: krb5_write_priv_message");
659             slave_dead(context, s);
660             return 1;
661         }
662     }
663 
664     return 0;
665 }
666 
667 static int
668 send_diffs (kadm5_server_context *server_context, slave *s, int log_fd,
669 	    const char *database, uint32_t current_version,
670 	    uint32_t current_tstamp)
671 {
672     krb5_context context = server_context->context;
673     krb5_storage *sp;
674     uint32_t ver, initial_version, initial_version2;
675     uint32_t initial_tstamp, initial_tstamp2;
676     enum kadm_ops op;
677     uint32_t len;
678     off_t right, left;
679     krb5_ssize_t bytes;
680     krb5_data data;
681     int ret = 0;
682 
683     if (s->flags & SLAVE_F_DEAD) {
684         krb5_warnx(context, "not sending diffs to dead slave %s", s->name);
685         return 0;
686     }
687 
688     if (s->version == current_version) {
689 	char buf[4];
690 
691 	sp = krb5_storage_from_mem(buf, 4);
692 	if (sp == NULL)
693 	    krb5_errx(context, IPROPD_RESTART, "krb5_storage_from_mem");
694 	ret = krb5_store_uint32(sp, YOU_HAVE_LAST_VERSION);
695 	krb5_storage_free(sp);
696 	data.data   = buf;
697 	data.length = 4;
698         if (ret == 0) {
699             ret = krb5_write_priv_message(context, s->ac, &s->fd, &data);
700             if (ret) {
701                 krb5_warn(context, ret, "send_diffs: failed to send to slave");
702                 slave_dead(context, s);
703             }
704             krb5_warnx(context, "slave %s in sync already at version %ld",
705                        s->name, (long)s->version);
706         }
707 	return ret;
708     }
709 
710     if (verbose)
711         krb5_warnx(context, "sending diffs to live-seeming slave %s", s->name);
712 
713     /*
714      * XXX The code that makes the diffs should be made a separate function,
715      * then error handling (send_are_you_there() or slave_dead()) can be done
716      * here.
717      */
718 
719     if (flock(log_fd, LOCK_SH) == -1) {
720         krb5_warn(context, errno, "could not obtain shared lock on log file");
721         send_are_you_there(context, s);
722         return errno;
723     }
724     ret = kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_FIRST,
725                                    &initial_version, &initial_tstamp);
726     sp = kadm5_log_goto_end(server_context, log_fd);
727     flock(log_fd, LOCK_UN);
728     if (ret) {
729         if (sp != NULL)
730             krb5_storage_free(sp);
731         krb5_warn(context, ret, "send_diffs: failed to read log");
732         send_are_you_there(context, s);
733         return ret;
734     }
735     if (sp == NULL) {
736         send_are_you_there(context, s);
737         krb5_warn(context, errno ? errno : EINVAL,
738                   "send_diffs: failed to read log");
739         return errno ? errno : EINVAL;
740     }
741     /*
742      * We're not holding any locks here, so we can't prevent truncations.
743      *
744      * We protect against this by re-checking that the initial version and
745      * timestamp are the same before and after this loop.
746      */
747     right = krb5_storage_seek(sp, 0, SEEK_CUR);
748     if (right == (off_t)-1) {
749         krb5_storage_free(sp);
750         send_are_you_there(context, s);
751         return errno;
752     }
753     for (;;) {
754 	ret = kadm5_log_previous (context, sp, &ver, NULL, &op, &len);
755 	if (ret)
756 	    krb5_err(context, IPROPD_RESTART, ret,
757 		     "send_diffs: failed to find previous entry");
758 	left = krb5_storage_seek(sp, -16, SEEK_CUR);
759         if (left == (off_t)-1) {
760             krb5_storage_free(sp);
761             send_are_you_there(context, s);
762             return errno;
763         }
764 	if (ver == s->version + 1)
765 	    break;
766 
767         /*
768          * We don't expect to reach the slave's version, except when it is
769          * starting empty with the uber record.
770          */
771 	if (ver == s->version && !(ver == 0 && op == kadm_nop)) {
772             /*
773              * This shouldn't happen, but recall we're not holding a lock on
774              * the log.
775              */
776             krb5_storage_free(sp);
777             krb5_warnx(context, "iprop log truncated while sending diffs to "
778                        "slave??  ver = %lu", (unsigned long)ver);
779             send_are_you_there(context, s);
780             return 0;
781         }
782 
783         /* If we've reached the uber record, send the complete database */
784 	if (left == 0 || (ver == 0 && op == kadm_nop)) {
785 	    krb5_storage_free(sp);
786 	    krb5_warnx(context,
787 		       "slave %s (version %lu) out of sync with master "
788 		       "(first version in log %lu), sending complete database",
789 		       s->name, (unsigned long)s->version, (unsigned long)ver);
790 	    return send_complete (context, s, database, current_version, ver,
791                                   initial_tstamp);
792 	}
793     }
794 
795     assert(ver == s->version + 1);
796 
797     krb5_warnx(context,
798 	       "syncing slave %s from version %lu to version %lu",
799 	       s->name, (unsigned long)s->version,
800 	       (unsigned long)current_version);
801 
802     ret = krb5_data_alloc (&data, right - left + 4);
803     if (ret) {
804 	krb5_storage_free(sp);
805 	krb5_warn (context, ret, "send_diffs: krb5_data_alloc");
806         send_are_you_there(context, s);
807 	return 1;
808     }
809     bytes = krb5_storage_read(sp, (char *)data.data + 4, data.length - 4);
810     krb5_storage_free(sp);
811     if (bytes != data.length - 4) {
812         krb5_warnx(context, "iprop log truncated while sending diffs to "
813                    "slave??  ver = %lu", (unsigned long)ver);
814         send_are_you_there(context, s);
815         return 1;
816     }
817 
818     /*
819      * Check that we have the same log initial version and timestamp now as
820      * when we dropped the shared lock on the log file!  Else we could be
821      * sending garbage to the slave.
822      */
823     if (flock(log_fd, LOCK_SH) == -1) {
824         krb5_warn(context, errno, "could not obtain shared lock on log file");
825         send_are_you_there(context, s);
826         return 1;
827     }
828     ret = kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_FIRST,
829                                    &initial_version2, &initial_tstamp2);
830     flock(log_fd, LOCK_UN);
831     if (ret) {
832         krb5_warn(context, ret,
833                    "send_diffs: failed to read log while producing diffs");
834         send_are_you_there(context, s);
835         return 1;
836     }
837     if (initial_version != initial_version2 ||
838         initial_tstamp != initial_tstamp2) {
839         krb5_warn(context, ret,
840                    "send_diffs: log truncated while producing diffs");
841         send_are_you_there(context, s);
842         return 1;
843     }
844 
845     sp = krb5_storage_from_data (&data);
846     if (sp == NULL) {
847 	krb5_warnx (context, "send_diffs: krb5_storage_from_data");
848         send_are_you_there(context, s);
849 	return 1;
850     }
851     krb5_store_uint32 (sp, FOR_YOU);
852     krb5_storage_free(sp);
853 
854     ret = krb5_write_priv_message(context, s->ac, &s->fd, &data);
855     krb5_data_free(&data);
856 
857     if (ret) {
858 	krb5_warn (context, ret, "send_diffs: krb5_write_priv_message");
859 	slave_dead(context, s);
860 	return 1;
861     }
862     slave_seen(s);
863 
864     s->version = current_version;
865 
866     krb5_warnx(context, "slave %s is now up to date (%u)", s->name, s->version);
867 
868     return 0;
869 }
870 
871 static int
872 process_msg (kadm5_server_context *server_context, slave *s, int log_fd,
873 	     const char *database, uint32_t current_version,
874              uint32_t current_tstamp)
875 {
876     krb5_context context = server_context->context;
877     int ret = 0;
878     krb5_data out;
879     krb5_storage *sp;
880     uint32_t tmp;
881 
882     ret = krb5_read_priv_message(context, s->ac, &s->fd, &out);
883     if(ret) {
884 	krb5_warn(context, ret, "error reading message from %s", s->name);
885 	return 1;
886     }
887 
888     sp = krb5_storage_from_mem(out.data, out.length);
889     if (sp == NULL) {
890 	krb5_warnx(context, "process_msg: no memory");
891 	krb5_data_free(&out);
892 	return 1;
893     }
894     if (krb5_ret_uint32(sp, &tmp) != 0) {
895 	krb5_warnx(context, "process_msg: client send too short command");
896 	krb5_data_free(&out);
897 	return 1;
898     }
899     switch (tmp) {
900     case I_HAVE :
901 	ret = krb5_ret_uint32(sp, &tmp);
902 	if (ret != 0) {
903 	    krb5_warnx(context, "process_msg: client send too little I_HAVE data");
904 	    break;
905 	}
906 	/* new started slave that have old log */
907 	if (s->version == 0 && tmp != 0) {
908 	    if (current_version < tmp) {
909 		krb5_warnx(context, "Slave %s (version %u) have later version "
910 			   "the master (version %u) OUT OF SYNC",
911 			   s->name, tmp, current_version);
912 	    }
913             if (verbose)
914                 krb5_warnx(context, "slave %s updated from %u to %u",
915                            s->name, s->version, tmp);
916 	    s->version = tmp;
917 	}
918 	if (tmp < s->version) {
919 	    krb5_warnx(context, "Slave %s claims to not have "
920                        "version we already sent to it", s->name);
921             s->version = tmp;
922 	}
923         ret = send_diffs(server_context, s, log_fd, database, current_version,
924                          current_tstamp);
925         break;
926     case I_AM_HERE :
927         if (verbose)
928             krb5_warnx(context, "slave %s is there", s->name);
929 	break;
930     case ARE_YOU_THERE:
931     case FOR_YOU :
932     default :
933 	krb5_warnx(context, "Ignoring command %d", tmp);
934 	break;
935     }
936 
937     krb5_data_free(&out);
938     krb5_storage_free(sp);
939 
940     slave_seen(s);
941 
942     return ret;
943 }
944 
945 #define SLAVE_NAME	"Name"
946 #define SLAVE_ADDRESS	"Address"
947 #define SLAVE_VERSION	"Version"
948 #define SLAVE_STATUS	"Status"
949 #define SLAVE_SEEN	"Last Seen"
950 
951 static FILE *
952 open_stats(krb5_context context)
953 {
954     char *statfile = NULL;
955     const char *fn = NULL;
956     FILE *out = NULL;
957 
958     /*
959      * krb5_config_get_string_default() returs default value as-is,
960      * delay free() of "statfile" until we're done with "fn".
961      */
962     if (slave_stats_file)
963 	fn = slave_stats_file;
964     else if (asprintf(&statfile,  "%s/slaves-stats", hdb_db_dir(context)) != -1
965 	     && statfile != NULL)
966 	fn = krb5_config_get_string_default(context,
967 					    NULL,
968 					    statfile,
969 					    "kdc",
970 					    "iprop-stats",
971 					    NULL);
972     if (fn != NULL)
973 	out = fopen(fn, "w");
974     if (statfile != NULL)
975 	free(statfile);
976     return out;
977 }
978 
979 static void
980 write_master_down(krb5_context context)
981 {
982     char str[100];
983     time_t t = time(NULL);
984     FILE *fp;
985 
986     fp = open_stats(context);
987     if (fp == NULL)
988 	return;
989     krb5_format_time(context, t, str, sizeof(str), TRUE);
990     fprintf(fp, "master down at %s\n", str);
991 
992     fclose(fp);
993 }
994 
995 static void
996 write_stats(krb5_context context, slave *slaves, uint32_t current_version)
997 {
998     char str[100];
999     rtbl_t tbl;
1000     time_t t = time(NULL);
1001     FILE *fp;
1002 
1003     fp = open_stats(context);
1004     if (fp == NULL)
1005 	return;
1006 
1007     krb5_format_time(context, t, str, sizeof(str), TRUE);
1008     fprintf(fp, "Status for slaves, last updated: %s\n\n", str);
1009 
1010     fprintf(fp, "Master version: %lu\n\n", (unsigned long)current_version);
1011 
1012     tbl = rtbl_create();
1013     if (tbl == NULL) {
1014 	fclose(fp);
1015 	return;
1016     }
1017 
1018     rtbl_add_column(tbl, SLAVE_NAME, 0);
1019     rtbl_add_column(tbl, SLAVE_ADDRESS, 0);
1020     rtbl_add_column(tbl, SLAVE_VERSION, RTBL_ALIGN_RIGHT);
1021     rtbl_add_column(tbl, SLAVE_STATUS, 0);
1022     rtbl_add_column(tbl, SLAVE_SEEN, 0);
1023 
1024     rtbl_set_prefix(tbl, "  ");
1025     rtbl_set_column_prefix(tbl, SLAVE_NAME, "");
1026 
1027     while (slaves) {
1028 	krb5_address addr;
1029 	krb5_error_code ret;
1030 	rtbl_add_column_entry(tbl, SLAVE_NAME, slaves->name);
1031 	ret = krb5_sockaddr2address (context,
1032 				     (struct sockaddr*)&slaves->addr, &addr);
1033 	if(ret == 0) {
1034 	    krb5_print_address(&addr, str, sizeof(str), NULL);
1035 	    krb5_free_address(context, &addr);
1036 	    rtbl_add_column_entry(tbl, SLAVE_ADDRESS, str);
1037 	} else
1038 	    rtbl_add_column_entry(tbl, SLAVE_ADDRESS, "<unknown>");
1039 
1040 	snprintf(str, sizeof(str), "%u", (unsigned)slaves->version);
1041 	rtbl_add_column_entry(tbl, SLAVE_VERSION, str);
1042 
1043 	if (slaves->flags & SLAVE_F_DEAD)
1044 	    rtbl_add_column_entry(tbl, SLAVE_STATUS, "Down");
1045 	else
1046 	    rtbl_add_column_entry(tbl, SLAVE_STATUS, "Up");
1047 
1048 	ret = krb5_format_time(context, slaves->seen, str, sizeof(str), TRUE);
1049 	rtbl_add_column_entry(tbl, SLAVE_SEEN, str);
1050 
1051 	slaves = slaves->next;
1052     }
1053 
1054     rtbl_format(tbl, fp);
1055     rtbl_destroy(tbl);
1056 
1057     fclose(fp);
1058 }
1059 
1060 
1061 static char sHDB[] = "HDBGET:";
1062 static char *realm;
1063 static int version_flag;
1064 static int help_flag;
1065 static char *keytab_str = sHDB;
1066 static char *database;
1067 static char *config_file;
1068 static char *port_str;
1069 static int detach_from_console;
1070 static int daemon_child = -1;
1071 
1072 static struct getargs args[] = {
1073     { "config-file", 'c', arg_string, &config_file, NULL, NULL },
1074     { "realm", 'r', arg_string, &realm, NULL, NULL },
1075     { "keytab", 'k', arg_string, &keytab_str,
1076       "keytab to get authentication from", "kspec" },
1077     { "database", 'd', arg_string, &database, "database", "file"},
1078     { "slave-stats-file", 0, arg_string, rk_UNCONST(&slave_stats_file),
1079       "file for slave status information", "file"},
1080     { "time-missing", 0, arg_string, rk_UNCONST(&slave_time_missing),
1081       "time before slave is polled for presence", "time"},
1082     { "time-gone", 0, arg_string, rk_UNCONST(&slave_time_gone),
1083       "time of inactivity after which a slave is considered gone", "time"},
1084     { "port", 0, arg_string, &port_str,
1085       "port ipropd will listen to", "port"},
1086     { "detach", 0, arg_flag, &detach_from_console,
1087       "detach from console", NULL },
1088     { "daemon-child",       0 ,      arg_integer, &daemon_child,
1089       "private argument, do not use", NULL },
1090     { "hostname", 0, arg_string, rk_UNCONST(&master_hostname),
1091       "hostname of master (if not same as hostname)", "hostname" },
1092     { "verbose", 0, arg_flag, &verbose, NULL, NULL },
1093     { "version", 0, arg_flag, &version_flag, NULL, NULL },
1094     { "help", 0, arg_flag, &help_flag, NULL, NULL }
1095 };
1096 static int num_args = sizeof(args) / sizeof(args[0]);
1097 
1098 int
1099 main(int argc, char **argv)
1100 {
1101     krb5_error_code ret;
1102     krb5_context context;
1103     void *kadm_handle;
1104     kadm5_server_context *server_context;
1105     kadm5_config_params conf;
1106     krb5_socket_t signal_fd, listen_fd;
1107     int log_fd;
1108     slave *slaves = NULL;
1109     uint32_t current_version = 0, old_version = 0;
1110     uint32_t current_tstamp = 0;
1111     krb5_keytab keytab;
1112     char **files;
1113     int aret;
1114     int optidx = 0;
1115     int restarter_fd = -1;
1116     struct stat st;
1117 
1118     setprogname(argv[0]);
1119 
1120     if (getarg(args, num_args, argc, argv, &optidx))
1121         krb5_std_usage(1, args, num_args);
1122 
1123     if (help_flag)
1124 	krb5_std_usage(0, args, num_args);
1125 
1126     if (version_flag) {
1127 	print_version(NULL);
1128 	exit(0);
1129     }
1130 
1131     if (detach_from_console && daemon_child == -1)
1132         roken_detach_prep(argc, argv, "--daemon-child");
1133     rk_pidfile(NULL);
1134 
1135     ret = krb5_init_context(&context);
1136     if (ret)
1137         errx(1, "krb5_init_context failed: %d", ret);
1138 
1139     setup_signal();
1140 
1141     if (config_file == NULL) {
1142 	aret = asprintf(&config_file, "%s/kdc.conf", hdb_db_dir(context));
1143 	if (aret == -1 || config_file == NULL)
1144 	    errx(1, "out of memory");
1145     }
1146 
1147     ret = krb5_prepend_config_files_default(config_file, &files);
1148     if (ret)
1149 	krb5_err(context, 1, ret, "getting configuration files");
1150 
1151     ret = krb5_set_config_files(context, files);
1152     krb5_free_config_files(files);
1153     if (ret)
1154 	krb5_err(context, 1, ret, "reading configuration files");
1155 
1156     time_before_gone = parse_time (slave_time_gone,  "s");
1157     if (time_before_gone < 0)
1158 	krb5_errx (context, 1, "couldn't parse time: %s", slave_time_gone);
1159     time_before_missing = parse_time (slave_time_missing,  "s");
1160     if (time_before_missing < 0)
1161 	krb5_errx (context, 1, "couldn't parse time: %s", slave_time_missing);
1162 
1163     krb5_openlog(context, "ipropd-master", &log_facility);
1164     krb5_set_warn_dest(context, log_facility);
1165 
1166     ret = krb5_kt_register(context, &hdb_get_kt_ops);
1167     if(ret)
1168 	krb5_err(context, 1, ret, "krb5_kt_register");
1169 
1170     ret = krb5_kt_resolve(context, keytab_str, &keytab);
1171     if(ret)
1172 	krb5_err(context, 1, ret, "krb5_kt_resolve: %s", keytab_str);
1173 
1174     memset(&conf, 0, sizeof(conf));
1175     if(realm) {
1176 	conf.mask |= KADM5_CONFIG_REALM;
1177 	conf.realm = realm;
1178     }
1179     ret = kadm5_init_with_skey_ctx (context,
1180 				    KADM5_ADMIN_SERVICE,
1181 				    NULL,
1182 				    KADM5_ADMIN_SERVICE,
1183 				    &conf, 0, 0,
1184 				    &kadm_handle);
1185     if (ret)
1186 	krb5_err (context, 1, ret, "kadm5_init_with_password_ctx");
1187 
1188     server_context = (kadm5_server_context *)kadm_handle;
1189 
1190     log_fd = open (server_context->log_context.log_file, O_RDONLY, 0);
1191     if (log_fd < 0)
1192 	krb5_err (context, 1, errno, "open %s",
1193 		  server_context->log_context.log_file);
1194 
1195     if (fstat(log_fd, &st) == -1)
1196         krb5_err(context, 1, errno, "stat %s",
1197                  server_context->log_context.log_file);
1198 
1199     if (flock(log_fd, LOCK_SH) == -1)
1200         krb5_err(context, 1, errno, "shared flock %s",
1201                  server_context->log_context.log_file);
1202     kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_LAST,
1203                              &current_version, &current_tstamp);
1204     flock(log_fd, LOCK_UN);
1205 
1206     signal_fd = make_signal_socket (context);
1207     listen_fd = make_listen_socket (context, port_str);
1208 
1209     krb5_warnx(context, "ipropd-master started at version: %lu",
1210 	       (unsigned long)current_version);
1211 
1212     roken_detach_finish(NULL, daemon_child);
1213     restarter_fd = restarter(context, NULL);
1214 
1215     while (exit_flag == 0){
1216 	slave *p;
1217 	fd_set readset;
1218 	int max_fd = 0;
1219 	struct timeval to = {30, 0};
1220 	uint32_t vers;
1221         struct stat st2;;
1222 
1223 #ifndef NO_LIMIT_FD_SETSIZE
1224 	if (signal_fd >= FD_SETSIZE || listen_fd >= FD_SETSIZE ||
1225             restarter_fd >= FD_SETSIZE)
1226 	    krb5_errx (context, IPROPD_RESTART, "fd too large");
1227 #endif
1228 
1229 	FD_ZERO(&readset);
1230 	FD_SET(signal_fd, &readset);
1231 	max_fd = max(max_fd, signal_fd);
1232 	FD_SET(listen_fd, &readset);
1233 	max_fd = max(max_fd, listen_fd);
1234         if (restarter_fd > -1) {
1235             FD_SET(restarter_fd, &readset);
1236             max_fd = max(max_fd, restarter_fd);
1237         }
1238 
1239 	for (p = slaves; p != NULL; p = p->next) {
1240 	    if (p->flags & SLAVE_F_DEAD)
1241 		continue;
1242 	    FD_SET(p->fd, &readset);
1243 	    max_fd = max(max_fd, p->fd);
1244 	}
1245 
1246 	ret = select (max_fd + 1,
1247 		      &readset, NULL, NULL, &to);
1248 	if (ret < 0) {
1249 	    if (errno == EINTR)
1250 		continue;
1251 	    else
1252 		krb5_err (context, IPROPD_RESTART, errno, "select");
1253 	}
1254 
1255         if (stat(server_context->log_context.log_file, &st2) == -1) {
1256             krb5_warn(context, errno, "could not stat log file by path");
1257             st2 = st;
1258         }
1259 
1260         if (st2.st_dev != st.st_dev || st2.st_ino != st.st_ino) {
1261             (void) close(log_fd);
1262 
1263             log_fd = open(server_context->log_context.log_file, O_RDONLY, 0);
1264             if (log_fd < 0)
1265                 krb5_err(context, 1, IPROPD_RESTART_SLOW, "open %s",
1266                           server_context->log_context.log_file);
1267 
1268             if (fstat(log_fd, &st) == -1)
1269                 krb5_err(context, IPROPD_RESTART_SLOW, errno, "stat %s",
1270                          server_context->log_context.log_file);
1271 
1272             if (flock(log_fd, LOCK_SH) == -1)
1273                 krb5_err(context, IPROPD_RESTART, errno, "shared flock %s",
1274                          server_context->log_context.log_file);
1275             kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_LAST,
1276                                      &current_version, &current_tstamp);
1277             flock(log_fd, LOCK_UN);
1278         }
1279 
1280 	if (ret == 0) {
1281             /* Recover from failed transactions */
1282             if (kadm5_log_init_nb(server_context) == 0)
1283                 kadm5_log_end(server_context);
1284 
1285 	    if (flock(log_fd, LOCK_SH) == -1)
1286                 krb5_err(context, IPROPD_RESTART, errno,
1287                          "could not lock log file");
1288 	    kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_LAST,
1289                                      &current_version, &current_tstamp);
1290 	    flock(log_fd, LOCK_UN);
1291 
1292 	    if (current_version > old_version) {
1293 		krb5_warnx(context,
1294 			   "Missed a signal, updating slaves %lu to %lu",
1295 			   (unsigned long)old_version,
1296 			   (unsigned long)current_version);
1297 		for (p = slaves; p != NULL; p = p->next) {
1298 		    if (p->flags & SLAVE_F_DEAD)
1299 			continue;
1300 		    send_diffs (server_context, p, log_fd, database,
1301                                 current_version, current_tstamp);
1302 		}
1303                 old_version = current_version;
1304 	    }
1305 	}
1306 
1307         if (ret && FD_ISSET(restarter_fd, &readset)) {
1308             exit_flag = SIGTERM;
1309             break;
1310         }
1311 
1312 	if (ret && FD_ISSET(signal_fd, &readset)) {
1313 #ifndef NO_UNIX_SOCKETS
1314 	    struct sockaddr_un peer_addr;
1315 #else
1316 	    struct sockaddr_storage peer_addr;
1317 #endif
1318 	    socklen_t peer_len = sizeof(peer_addr);
1319 
1320 	    if(recvfrom(signal_fd, (void *)&vers, sizeof(vers), 0,
1321 			(struct sockaddr *)&peer_addr, &peer_len) < 0) {
1322 		krb5_warn (context, errno, "recvfrom");
1323 		continue;
1324 	    }
1325 	    --ret;
1326 	    assert(ret >= 0);
1327 	    old_version = current_version;
1328 	    if (flock(log_fd, LOCK_SH) == -1)
1329                 krb5_err(context, IPROPD_RESTART, errno, "shared flock %s",
1330                          server_context->log_context.log_file);
1331 	    kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_LAST,
1332                                      &current_version, &current_tstamp);
1333 	    flock(log_fd, LOCK_UN);
1334 	    if (current_version != old_version) {
1335                 /*
1336                  * If current_version < old_version then the log got
1337                  * truncated and we'll end up doing full propagations.
1338                  *
1339                  * Truncating the log when the current version is
1340                  * numerically small can lead to race conditions.
1341                  * Ideally we should identify log versions as
1342                  * {init_or_trunc_time, vno}, then we could not have any
1343                  * such race conditions, but this would either require
1344                  * breaking backwards compatibility for the protocol or
1345                  * adding new messages to it.
1346                  */
1347 		krb5_warnx(context,
1348 			   "Got a signal, updating slaves %lu to %lu",
1349 			   (unsigned long)old_version,
1350 			   (unsigned long)current_version);
1351 		for (p = slaves; p != NULL; p = p->next) {
1352 		    if (p->flags & SLAVE_F_DEAD)
1353 			continue;
1354 		    send_diffs (server_context, p, log_fd, database,
1355                                 current_version, current_tstamp);
1356 		}
1357 	    } else {
1358 		krb5_warnx(context,
1359 			   "Got a signal, but no update in log version %lu",
1360 			   (unsigned long)current_version);
1361 	    }
1362         }
1363 
1364 	for(p = slaves; p != NULL; p = p->next) {
1365 	    if (p->flags & SLAVE_F_DEAD)
1366 	        continue;
1367 	    if (ret && FD_ISSET(p->fd, &readset)) {
1368 		--ret;
1369 		assert(ret >= 0);
1370 		if(process_msg (server_context, p, log_fd, database,
1371 				current_version, current_tstamp))
1372 		    slave_dead(context, p);
1373 	    } else if (slave_gone_p (p))
1374 		slave_dead(context, p);
1375 	    else if (slave_missing_p (p))
1376 		send_are_you_there (context, p);
1377 	}
1378 
1379 	if (ret && FD_ISSET(listen_fd, &readset)) {
1380 	    add_slave (context, keytab, &slaves, listen_fd);
1381 	    --ret;
1382 	    assert(ret >= 0);
1383 	}
1384 	write_stats(context, slaves, current_version);
1385     }
1386 
1387     if(exit_flag == SIGINT || exit_flag == SIGTERM)
1388 	krb5_warnx(context, "%s terminated", getprogname());
1389 #ifdef SIGXCPU
1390     else if(exit_flag == SIGXCPU)
1391 	krb5_warnx(context, "%s CPU time limit exceeded", getprogname());
1392 #endif
1393     else
1394 	krb5_warnx(context, "%s unexpected exit reason: %ld",
1395 		   getprogname(), (long)exit_flag);
1396 
1397     write_master_down(context);
1398 
1399     return 0;
1400 }
1401