1 /*
2  * Copyright (c) 1997 - 2008 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 "iprop.h"
35 #include <rtbl.h>
36 
37 static krb5_log_facility *log_facility;
38 
39 const char *slave_stats_file;
40 const char *slave_time_missing = "2 min";
41 const char *slave_time_gone = "5 min";
42 
43 static int time_before_missing;
44 static int time_before_gone;
45 
46 const char *master_hostname;
47 
48 static krb5_socket_t
49 make_signal_socket (krb5_context context)
50 {
51 #ifndef NO_UNIX_SOCKETS
52     struct sockaddr_un addr;
53     const char *fn;
54     krb5_socket_t fd;
55 
56     fn = kadm5_log_signal_socket(context);
57 
58     fd = socket (AF_UNIX, SOCK_DGRAM, 0);
59     if (fd < 0)
60 	krb5_err (context, 1, errno, "socket AF_UNIX");
61     memset (&addr, 0, sizeof(addr));
62     addr.sun_family = AF_UNIX;
63     strlcpy (addr.sun_path, fn, sizeof(addr.sun_path));
64     unlink (addr.sun_path);
65     if (bind (fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
66 	krb5_err (context, 1, errno, "bind %s", addr.sun_path);
67     return fd;
68 #else
69     struct addrinfo *ai = NULL;
70     krb5_socket_t fd;
71 
72     kadm5_log_signal_socket_info(context, 1, &ai);
73 
74     fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
75     if (rk_IS_BAD_SOCKET(fd))
76 	krb5_err (context, 1, rk_SOCK_ERRNO, "socket AF=%d", ai->ai_family);
77 
78     if (rk_IS_SOCKET_ERROR( bind (fd, ai->ai_addr, ai->ai_addrlen) ))
79 	krb5_err (context, 1, rk_SOCK_ERRNO, "bind");
80     return fd;
81 #endif
82 }
83 
84 static krb5_socket_t
85 make_listen_socket (krb5_context context, const char *port_str)
86 {
87     krb5_socket_t fd;
88     int one = 1;
89     struct sockaddr_in addr;
90 
91     fd = socket (AF_INET, SOCK_STREAM, 0);
92     if (rk_IS_BAD_SOCKET(fd))
93 	krb5_err (context, 1, rk_SOCK_ERRNO, "socket AF_INET");
94     setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one));
95     memset (&addr, 0, sizeof(addr));
96     addr.sin_family = AF_INET;
97 
98     if (port_str) {
99 	addr.sin_port = krb5_getportbyname (context,
100 					      port_str, "tcp",
101 					      0);
102 	if (addr.sin_port == 0) {
103 	    char *ptr;
104 	    long port;
105 
106 	    port = strtol (port_str, &ptr, 10);
107 	    if (port == 0 && ptr == port_str)
108 		krb5_errx (context, 1, "bad port `%s'", port_str);
109 	    addr.sin_port = htons(port);
110 	}
111     } else {
112 	addr.sin_port = krb5_getportbyname (context, IPROP_SERVICE,
113 					    "tcp", IPROP_PORT);
114     }
115     if(bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
116 	krb5_err (context, 1, errno, "bind");
117     if (listen(fd, SOMAXCONN) < 0)
118 	krb5_err (context, 1, errno, "listen");
119     return fd;
120 }
121 
122 static krb5_socket_t
123 make_listen6_socket (krb5_context context, const char *port_str)
124 {
125     krb5_socket_t fd;
126     int one = 1;
127     struct sockaddr_in6 addr;
128 
129     fd = socket (AF_INET6, SOCK_STREAM, 0);
130     if (rk_IS_BAD_SOCKET(fd))
131 	krb5_err (context, 1, rk_SOCK_ERRNO, "socket AF_INET6");
132     setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one));
133     memset (&addr, 0, sizeof(addr));
134     addr.sin6_family = AF_INET6;
135 
136     if (port_str) {
137 	addr.sin6_port = krb5_getportbyname (context,
138 					      port_str, "tcp",
139 					      0);
140 	if (addr.sin6_port == 0) {
141 	    char *ptr;
142 	    long port;
143 
144 	    port = strtol (port_str, &ptr, 10);
145 	    if (port == 0 && ptr == port_str)
146 		krb5_errx (context, 1, "bad port `%s'", port_str);
147 	    addr.sin6_port = htons(port);
148 	}
149     } else {
150 	addr.sin6_port = krb5_getportbyname (context, IPROP_SERVICE,
151 					    "tcp", IPROP_PORT);
152     }
153     if(bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
154 	krb5_err (context, 1, errno, "bind6");
155     if (listen(fd, SOMAXCONN) < 0)
156 	krb5_err (context, 1, errno, "listen6");
157     return fd;
158 }
159 
160 #ifndef _SOCKADDR_UNION
161 #define _SOCKADDR_UNION
162 union sockaddr_union {
163 	struct sockaddr		sa;
164 	struct sockaddr_in	sin;
165 	struct sockaddr_in6     sin6;
166 };
167 #endif /* _SOCKADDR_UNION */
168 
169 struct slave {
170     krb5_socket_t fd;
171     union sockaddr_union addr;
172     char *name;
173     krb5_auth_context ac;
174     uint32_t version;
175     time_t seen;
176     unsigned long flags;
177 #define SLAVE_F_DEAD	0x1
178 #define SLAVE_F_AYT	0x2
179     struct slave *next;
180 };
181 
182 typedef struct slave slave;
183 
184 static int
185 check_acl (krb5_context context, const char *name)
186 {
187     const char *fn;
188     FILE *fp;
189     char buf[256];
190     int ret = 1;
191     char *slavefile = NULL;
192 
193     if (asprintf(&slavefile, "%s/slaves", hdb_db_dir(context)) == -1
194 	|| slavefile == NULL)
195 	errx(1, "out of memory");
196 
197     fn = krb5_config_get_string_default(context,
198 					NULL,
199 					slavefile,
200 					"kdc",
201 					"iprop-acl",
202 					NULL);
203 
204     fp = fopen (fn, "r");
205     free(slavefile);
206     if (fp == NULL)
207 	return 1;
208     while (fgets(buf, sizeof(buf), fp) != NULL) {
209 	buf[strcspn(buf, "\r\n")] = '\0';
210 	if (strcmp (buf, name) == 0) {
211 	    ret = 0;
212 	    break;
213 	}
214     }
215     fclose (fp);
216     return ret;
217 }
218 
219 static void
220 slave_seen(slave *s)
221 {
222     s->flags &= ~SLAVE_F_AYT;
223     s->seen = time(NULL);
224 }
225 
226 static int
227 slave_missing_p (slave *s)
228 {
229     if (time(NULL) > s->seen + time_before_missing)
230 	return 1;
231     return 0;
232 }
233 
234 static int
235 slave_gone_p (slave *s)
236 {
237     if (time(NULL) > s->seen + time_before_gone)
238 	return 1;
239     return 0;
240 }
241 
242 static void
243 slave_dead(krb5_context context, slave *s)
244 {
245     krb5_warnx(context, "slave %s dead", s->name);
246 
247     if (!rk_IS_BAD_SOCKET(s->fd)) {
248 	rk_closesocket (s->fd);
249 	s->fd = rk_INVALID_SOCKET;
250     }
251     s->flags |= SLAVE_F_DEAD;
252     slave_seen(s);
253 }
254 
255 static void
256 remove_slave (krb5_context context, slave *s, slave **root)
257 {
258     slave **p;
259 
260     if (!rk_IS_BAD_SOCKET(s->fd))
261 	rk_closesocket (s->fd);
262     if (s->name)
263 	free (s->name);
264     if (s->ac)
265 	krb5_auth_con_free (context, s->ac);
266 
267     for (p = root; *p; p = &(*p)->next)
268 	if (*p == s) {
269 	    *p = s->next;
270 	    break;
271 	}
272     free (s);
273 }
274 
275 static void
276 add_slave (krb5_context context, krb5_keytab keytab, slave **root,
277 	   krb5_socket_t fd)
278 {
279     krb5_principal server;
280     krb5_error_code ret;
281     slave *s;
282     socklen_t addr_len;
283     krb5_ticket *ticket = NULL;
284     char hostname[128];
285 
286     s = malloc(sizeof(*s));
287     if (s == NULL) {
288 	krb5_warnx (context, "add_slave: no memory");
289 	return;
290     }
291     s->name = NULL;
292     s->ac = NULL;
293 
294     addr_len = sizeof(s->addr.sin6);
295     s->fd = accept (fd, (struct sockaddr *)&s->addr.sa, &addr_len);
296     if (rk_IS_BAD_SOCKET(s->fd)) {
297 	krb5_warn (context, rk_SOCK_ERRNO, "accept");
298 	goto error;
299     }
300     if (master_hostname)
301 	strlcpy(hostname, master_hostname, sizeof(hostname));
302     else
303 	gethostname(hostname, sizeof(hostname));
304 
305     ret = krb5_sname_to_principal (context, hostname, IPROP_NAME,
306 				   KRB5_NT_SRV_HST, &server);
307     if (ret) {
308 	krb5_warn (context, ret, "krb5_sname_to_principal");
309 	goto error;
310     }
311 
312     ret = krb5_recvauth (context, &s->ac, &s->fd,
313 			 IPROP_VERSION, server, 0, keytab, &ticket);
314     krb5_free_principal (context, server);
315     if (ret) {
316 	krb5_warn (context, ret, "krb5_recvauth");
317 	goto error;
318     }
319     ret = krb5_unparse_name (context, ticket->client, &s->name);
320     krb5_free_ticket (context, ticket);
321     if (ret) {
322 	krb5_warn (context, ret, "krb5_unparse_name");
323 	goto error;
324     }
325     if (check_acl (context, s->name)) {
326 	krb5_warnx (context, "%s not in acl", s->name);
327 	goto error;
328     }
329 
330     {
331 	slave *l = *root;
332 
333 	while (l) {
334 	    if (strcmp(l->name, s->name) == 0)
335 		break;
336 	    l = l->next;
337 	}
338 	if (l) {
339 	    if (l->flags & SLAVE_F_DEAD) {
340 		remove_slave(context, l, root);
341 	    } else {
342 		krb5_warnx (context, "second connection from %s", s->name);
343 		goto error;
344 	    }
345 	}
346     }
347 
348     krb5_warnx (context, "connection from %s", s->name);
349 
350     s->version = 0;
351     s->flags = 0;
352     slave_seen(s);
353     s->next = *root;
354     *root = s;
355     return;
356 error:
357     remove_slave(context, s, root);
358 }
359 
360 struct prop_context {
361     krb5_auth_context auth_context;
362     krb5_socket_t fd;
363 };
364 
365 static int
366 prop_one (krb5_context context, HDB *db, hdb_entry_ex *entry, void *v)
367 {
368     krb5_error_code ret;
369     krb5_storage *sp;
370     krb5_data data;
371     struct slave *s = (struct slave *)v;
372 
373     ret = hdb_entry2value (context, &entry->entry, &data);
374     if (ret)
375 	return ret;
376     ret = krb5_data_realloc (&data, data.length + 4);
377     if (ret) {
378 	krb5_data_free (&data);
379 	return ret;
380     }
381     memmove ((char *)data.data + 4, data.data, data.length - 4);
382     sp = krb5_storage_from_data(&data);
383     if (sp == NULL) {
384 	krb5_data_free (&data);
385 	return ENOMEM;
386     }
387     krb5_store_int32(sp, ONE_PRINC);
388     krb5_storage_free(sp);
389 
390     ret = krb5_write_priv_message (context, s->ac, &s->fd, &data);
391     krb5_data_free (&data);
392     return ret;
393 }
394 
395 static int
396 send_complete (krb5_context context, slave *s,
397 	       const char *database, uint32_t current_version)
398 {
399     krb5_error_code ret;
400     krb5_storage *sp;
401     HDB *db;
402     krb5_data data;
403     char buf[8];
404 
405     ret = hdb_create (context, &db, database);
406     if (ret)
407 	krb5_err (context, 1, ret, "hdb_create: %s", database);
408     ret = db->hdb_open (context, db, O_RDONLY, 0);
409     if (ret)
410 	krb5_err (context, 1, ret, "db->open");
411 
412     sp = krb5_storage_from_mem (buf, 4);
413     if (sp == NULL)
414 	krb5_errx (context, 1, "krb5_storage_from_mem");
415     krb5_store_int32 (sp, TELL_YOU_EVERYTHING);
416     krb5_storage_free (sp);
417 
418     data.data   = buf;
419     data.length = 4;
420 
421     ret = krb5_write_priv_message(context, s->ac, &s->fd, &data);
422 
423     if (ret) {
424 	krb5_warn (context, ret, "krb5_write_priv_message");
425 	slave_dead(context, s);
426 	return ret;
427     }
428 
429     ret = hdb_foreach (context, db, HDB_F_ADMIN_DATA, prop_one, s);
430     if (ret) {
431 	krb5_warn (context, ret, "hdb_foreach");
432 	slave_dead(context, s);
433 	return ret;
434     }
435 
436     (*db->hdb_close)(context, db);
437     (*db->hdb_destroy)(context, db);
438 
439     sp = krb5_storage_from_mem (buf, 8);
440     if (sp == NULL)
441 	krb5_errx (context, 1, "krb5_storage_from_mem");
442     krb5_store_int32 (sp, NOW_YOU_HAVE);
443     krb5_store_int32 (sp, current_version);
444     krb5_storage_free (sp);
445 
446     data.length = 8;
447 
448     s->version = current_version;
449 
450     ret = krb5_write_priv_message(context, s->ac, &s->fd, &data);
451     if (ret) {
452 	slave_dead(context, s);
453 	krb5_warn (context, ret, "krb5_write_priv_message");
454 	return ret;
455     }
456 
457     slave_seen(s);
458 
459     return 0;
460 }
461 
462 static int
463 send_are_you_there (krb5_context context, slave *s)
464 {
465     krb5_storage *sp;
466     krb5_data data;
467     char buf[4];
468     int ret;
469 
470     if (s->flags & (SLAVE_F_DEAD|SLAVE_F_AYT))
471 	return 0;
472 
473     krb5_warnx(context, "slave %s missing, sending AYT", s->name);
474 
475     s->flags |= SLAVE_F_AYT;
476 
477     data.data = buf;
478     data.length = 4;
479 
480     sp = krb5_storage_from_mem (buf, 4);
481     if (sp == NULL) {
482 	krb5_warnx (context, "are_you_there: krb5_data_alloc");
483 	slave_dead(context, s);
484 	return 1;
485     }
486     krb5_store_int32 (sp, ARE_YOU_THERE);
487     krb5_storage_free (sp);
488 
489     ret = krb5_write_priv_message(context, s->ac, &s->fd, &data);
490 
491     if (ret) {
492 	krb5_warn (context, ret, "are_you_there: krb5_write_priv_message");
493 	slave_dead(context, s);
494 	return 1;
495     }
496 
497     return 0;
498 }
499 
500 static int
501 send_diffs (krb5_context context, slave *s, int log_fd,
502 	    const char *database, uint32_t current_version)
503 {
504     krb5_storage *sp;
505     uint32_t ver;
506     time_t timestamp;
507     enum kadm_ops op;
508     uint32_t len;
509     off_t right, left;
510     krb5_data data;
511     int ret = 0;
512 
513     if (s->version == current_version) {
514 	krb5_warnx(context, "slave %s in sync already at version %ld",
515 		   s->name, (long)s->version);
516 	return 0;
517     }
518 
519     if (s->flags & SLAVE_F_DEAD)
520 	return 0;
521 
522     /* if slave is a fresh client, starting over */
523     if (s->version == 0) {
524 	krb5_warnx(context, "sending complete log to fresh slave %s",
525 		   s->name);
526 	return send_complete (context, s, database, current_version);
527     }
528 
529     sp = kadm5_log_goto_end (log_fd);
530     right = krb5_storage_seek(sp, 0, SEEK_CUR);
531     for (;;) {
532 	ret = kadm5_log_previous (context, sp, &ver, &timestamp, &op, &len);
533 	if (ret)
534 	    krb5_err(context, 1, ret,
535 		     "send_diffs: failed to find previous entry");
536 	left = krb5_storage_seek(sp, -16, SEEK_CUR);
537 	if (ver == s->version)
538 	    return 0;
539 	if (ver == s->version + 1)
540 	    break;
541 	if (left == 0) {
542 	    krb5_storage_free(sp);
543 	    krb5_warnx(context,
544 		       "slave %s (version %lu) out of sync with master "
545 		       "(first version in log %lu), sending complete database",
546 		       s->name, (unsigned long)s->version, (unsigned long)ver);
547 	    return send_complete (context, s, database, current_version);
548 	}
549     }
550 
551     krb5_warnx(context,
552 	       "syncing slave %s from version %lu to version %lu",
553 	       s->name, (unsigned long)s->version,
554 	       (unsigned long)current_version);
555 
556     ret = krb5_data_alloc (&data, right - left + 4);
557     if (ret) {
558 	krb5_storage_free(sp);
559 	krb5_warn (context, ret, "send_diffs: krb5_data_alloc");
560 	slave_dead(context, s);
561 	return 1;
562     }
563     krb5_storage_read (sp, (char *)data.data + 4, data.length - 4);
564     krb5_storage_free(sp);
565 
566     sp = krb5_storage_from_data (&data);
567     if (sp == NULL) {
568 	krb5_warnx (context, "send_diffs: krb5_storage_from_data");
569 	slave_dead(context, s);
570 	return 1;
571     }
572     krb5_store_int32 (sp, FOR_YOU);
573     krb5_storage_free(sp);
574 
575     ret = krb5_write_priv_message(context, s->ac, &s->fd, &data);
576     krb5_data_free(&data);
577 
578     if (ret) {
579 	krb5_warn (context, ret, "send_diffs: krb5_write_priv_message");
580 	slave_dead(context, s);
581 	return 1;
582     }
583     slave_seen(s);
584 
585     s->version = current_version;
586 
587     return 0;
588 }
589 
590 static int
591 process_msg (krb5_context context, slave *s, int log_fd,
592 	     const char *database, uint32_t current_version)
593 {
594     int ret = 0;
595     krb5_data out;
596     krb5_storage *sp;
597     int32_t tmp;
598 
599     ret = krb5_read_priv_message(context, s->ac, &s->fd, &out);
600     if(ret) {
601 	krb5_warn (context, ret, "error reading message from %s", s->name);
602 	return 1;
603     }
604 
605     sp = krb5_storage_from_mem (out.data, out.length);
606     if (sp == NULL) {
607 	krb5_warnx (context, "process_msg: no memory");
608 	krb5_data_free (&out);
609 	return 1;
610     }
611     if (krb5_ret_int32 (sp, &tmp) != 0) {
612 	krb5_warnx (context, "process_msg: client send too short command");
613 	krb5_data_free (&out);
614 	return 1;
615     }
616     switch (tmp) {
617     case I_HAVE :
618 	ret = krb5_ret_int32 (sp, &tmp);
619 	if (ret != 0) {
620 	    krb5_warnx (context, "process_msg: client send too I_HAVE data");
621 	    break;
622 	}
623 	/* new started slave that have old log */
624 	if (s->version == 0 && tmp != 0) {
625 	    if (current_version < (uint32_t)tmp) {
626 		krb5_warnx (context, "Slave %s (version %lu) have later version "
627 			    "the master (version %lu) OUT OF SYNC",
628 			    s->name, (unsigned long)tmp,
629 			    (unsigned long)current_version);
630 	    }
631 	    s->version = tmp;
632 	}
633 	if ((uint32_t)tmp < s->version) {
634 	    krb5_warnx (context, "Slave claims to not have "
635 			"version we already sent to it");
636 	} else {
637 	    ret = send_diffs (context, s, log_fd, database, current_version);
638 	}
639 	break;
640     case I_AM_HERE :
641 	break;
642     case ARE_YOU_THERE:
643     case FOR_YOU :
644     default :
645 	krb5_warnx (context, "Ignoring command %d", tmp);
646 	break;
647     }
648 
649     krb5_data_free (&out);
650     krb5_storage_free (sp);
651 
652     slave_seen(s);
653 
654     return ret;
655 }
656 
657 #define SLAVE_NAME	"Name"
658 #define SLAVE_ADDRESS	"Address"
659 #define SLAVE_VERSION	"Version"
660 #define SLAVE_STATUS	"Status"
661 #define SLAVE_SEEN	"Last Seen"
662 
663 static FILE *
664 open_stats(krb5_context context)
665 {
666     char *statfile = NULL;
667     const char *fn;
668     FILE *f;
669 
670     if (slave_stats_file)
671 	fn = slave_stats_file;
672     else {
673 	asprintf(&statfile,  "%s/slaves-stats", hdb_db_dir(context));
674 	fn = krb5_config_get_string_default(context,
675 					    NULL,
676 					    statfile,
677 					    "kdc",
678 					    "iprop-stats",
679 					    NULL);
680     }
681     f = fopen(fn, "w");
682     if (statfile)
683 	free(statfile);
684 
685     return f;
686 }
687 
688 static void
689 write_master_down(krb5_context context)
690 {
691     char str[100];
692     time_t t = time(NULL);
693     FILE *fp;
694 
695     fp = open_stats(context);
696     if (fp == NULL)
697 	return;
698     krb5_format_time(context, t, str, sizeof(str), TRUE);
699     fprintf(fp, "master down at %s\n", str);
700 
701     fclose(fp);
702 }
703 
704 static void
705 write_stats(krb5_context context, slave *slaves, uint32_t current_version)
706 {
707     char str[100];
708     rtbl_t tbl;
709     time_t t = time(NULL);
710     FILE *fp;
711 
712     fp = open_stats(context);
713     if (fp == NULL)
714 	return;
715 
716     krb5_format_time(context, t, str, sizeof(str), TRUE);
717     fprintf(fp, "Status for slaves, last updated: %s\n\n", str);
718 
719     fprintf(fp, "Master version: %lu\n\n", (unsigned long)current_version);
720 
721     tbl = rtbl_create();
722     if (tbl == NULL) {
723 	fclose(fp);
724 	return;
725     }
726 
727     rtbl_add_column(tbl, SLAVE_NAME, 0);
728     rtbl_add_column(tbl, SLAVE_ADDRESS, 0);
729     rtbl_add_column(tbl, SLAVE_VERSION, RTBL_ALIGN_RIGHT);
730     rtbl_add_column(tbl, SLAVE_STATUS, 0);
731     rtbl_add_column(tbl, SLAVE_SEEN, 0);
732 
733     rtbl_set_prefix(tbl, "  ");
734     rtbl_set_column_prefix(tbl, SLAVE_NAME, "");
735 
736     while (slaves) {
737 	krb5_address addr;
738 	krb5_error_code ret;
739 	rtbl_add_column_entry(tbl, SLAVE_NAME, slaves->name);
740 	ret = krb5_sockaddr2address (context,
741 				     (struct sockaddr*)&slaves->addr.sa, &addr);
742 	if(ret == 0) {
743 	    krb5_print_address(&addr, str, sizeof(str), NULL);
744 	    krb5_free_address(context, &addr);
745 	    rtbl_add_column_entry(tbl, SLAVE_ADDRESS, str);
746 	} else
747 	    rtbl_add_column_entry(tbl, SLAVE_ADDRESS, "<unknown>");
748 
749 	snprintf(str, sizeof(str), "%u", (unsigned)slaves->version);
750 	rtbl_add_column_entry(tbl, SLAVE_VERSION, str);
751 
752 	if (slaves->flags & SLAVE_F_DEAD)
753 	    rtbl_add_column_entry(tbl, SLAVE_STATUS, "Down");
754 	else
755 	    rtbl_add_column_entry(tbl, SLAVE_STATUS, "Up");
756 
757 	ret = krb5_format_time(context, slaves->seen, str, sizeof(str), TRUE);
758         if (ret)
759             rtbl_add_column_entry(tbl, SLAVE_SEEN, "<error-formatting-time>");
760         else
761             rtbl_add_column_entry(tbl, SLAVE_SEEN, str);
762 
763 	slaves = slaves->next;
764     }
765 
766     rtbl_format(tbl, fp);
767     rtbl_destroy(tbl);
768 
769     fclose(fp);
770 }
771 
772 
773 static char sHDB[] = "HDB:";
774 static char *realm;
775 static int version_flag;
776 static int help_flag;
777 static char *keytab_str = sHDB;
778 static char *database;
779 static char *config_file;
780 static char *port_str;
781 #ifdef SUPPORT_DETACH
782 static int detach_from_console = 0;
783 #endif
784 
785 static struct getargs args[] = {
786     { "config-file", 'c', arg_string, &config_file, NULL, NULL },
787     { "realm", 'r', arg_string, &realm, NULL, NULL },
788     { "keytab", 'k', arg_string, &keytab_str,
789       "keytab to get authentication from", "kspec" },
790     { "database", 'd', arg_string, &database, "database", "file"},
791     { "slave-stats-file", 0, arg_string, rk_UNCONST(&slave_stats_file),
792       "file for slave status information", "file"},
793     { "time-missing", 0, arg_string, rk_UNCONST(&slave_time_missing),
794       "time before slave is polled for presence", "time"},
795     { "time-gone", 0, arg_string, rk_UNCONST(&slave_time_gone),
796       "time of inactivity after which a slave is considered gone", "time"},
797     { "port", 0, arg_string, &port_str,
798       "port ipropd will listen to", "port"},
799 #ifdef SUPPORT_DETACH
800     { "detach", 0, arg_flag, &detach_from_console,
801       "detach from console", NULL },
802 #endif
803     { "hostname", 0, arg_string, rk_UNCONST(&master_hostname),
804       "hostname of master (if not same as hostname)", "hostname" },
805     { "version", 0, arg_flag, &version_flag, NULL, NULL },
806     { "help", 0, arg_flag, &help_flag, NULL, NULL }
807 };
808 static int num_args = sizeof(args) / sizeof(args[0]);
809 
810 int
811 main(int argc, char **argv)
812 {
813     krb5_error_code ret;
814     krb5_context context;
815     void *kadm_handle;
816     kadm5_server_context *server_context;
817     kadm5_config_params conf;
818     krb5_socket_t signal_fd, listen_fd, listen6_fd;
819     int log_fd;
820     slave *slaves = NULL;
821     uint32_t current_version = 0, old_version = 0;
822     krb5_keytab keytab;
823     int optidx;
824     char **files;
825 
826     optidx = krb5_program_setup(&context, argc, argv, args, num_args, NULL);
827 
828     if(help_flag)
829 	krb5_std_usage(0, args, num_args);
830     if(version_flag) {
831 	print_version(NULL);
832 	exit(0);
833     }
834 
835     setup_signal();
836 
837     if (config_file == NULL) {
838 	asprintf(&config_file, "%s/kdc.conf", hdb_db_dir(context));
839 	if (config_file == NULL)
840 	    errx(1, "out of memory");
841     }
842 
843     ret = krb5_prepend_config_files_default(config_file, &files);
844     if (ret)
845 	krb5_err(context, 1, ret, "getting configuration files");
846 
847     ret = krb5_set_config_files(context, files);
848     krb5_free_config_files(files);
849     if (ret)
850 	krb5_err(context, 1, ret, "reading configuration files");
851 
852     time_before_gone = parse_time (slave_time_gone,  "s");
853     if (time_before_gone < 0)
854 	krb5_errx (context, 1, "couldn't parse time: %s", slave_time_gone);
855     time_before_missing = parse_time (slave_time_missing,  "s");
856     if (time_before_missing < 0)
857 	krb5_errx (context, 1, "couldn't parse time: %s", slave_time_missing);
858 
859 #ifdef SUPPORT_DETACH
860     if (detach_from_console)
861 	daemon(0, 0);
862 #endif
863     pidfile (NULL);
864     krb5_openlog (context, "ipropd-master", &log_facility);
865     krb5_set_warn_dest(context, log_facility);
866 
867     ret = krb5_kt_register(context, &hdb_kt_ops);
868     if(ret)
869 	krb5_err(context, 1, ret, "krb5_kt_register");
870 
871     ret = krb5_kt_resolve(context, keytab_str, &keytab);
872     if(ret)
873 	krb5_err(context, 1, ret, "krb5_kt_resolve: %s", keytab_str);
874 
875     memset(&conf, 0, sizeof(conf));
876     if(realm) {
877 	conf.mask |= KADM5_CONFIG_REALM;
878 	conf.realm = realm;
879     }
880     ret = kadm5_init_with_skey_ctx (context,
881 				    KADM5_ADMIN_SERVICE,
882 				    NULL,
883 				    KADM5_ADMIN_SERVICE,
884 				    &conf, 0, 0,
885 				    &kadm_handle);
886     if (ret)
887 	krb5_err (context, 1, ret, "kadm5_init_with_password_ctx");
888 
889     server_context = (kadm5_server_context *)kadm_handle;
890 
891     log_fd = open (server_context->log_context.log_file, O_RDONLY, 0);
892     if (log_fd < 0)
893 	krb5_err (context, 1, errno, "open %s",
894 		  server_context->log_context.log_file);
895 
896     signal_fd = make_signal_socket (context);
897     listen_fd = make_listen_socket (context, port_str);
898     listen6_fd = make_listen6_socket (context, port_str);
899 
900     kadm5_log_get_version_fd (log_fd, &current_version);
901 
902     krb5_warnx(context, "ipropd-master started at version: %lu",
903 	       (unsigned long)current_version);
904 
905     while(exit_flag == 0){
906 	slave *p;
907 	fd_set readset;
908 	int max_fd = 0;
909 	struct timeval to = {30, 0};
910 	uint32_t vers;
911 
912 #ifndef NO_LIMIT_FD_SETSIZE
913 	if (signal_fd >= FD_SETSIZE || listen_fd >= FD_SETSIZE ||
914 	    listen6_fd >= FD_SETSIZE)
915 	    krb5_errx (context, 1, "fd too large");
916 #endif
917 
918 	FD_ZERO(&readset);
919 	FD_SET(signal_fd, &readset);
920 	max_fd = max(max_fd, signal_fd);
921 	FD_SET(listen_fd, &readset);
922 	max_fd = max(max_fd, listen_fd);
923 	FD_SET(listen6_fd, &readset);
924 	max_fd = max(max_fd, listen6_fd);
925 
926 	for (p = slaves; p != NULL; p = p->next) {
927 	    if (p->flags & SLAVE_F_DEAD)
928 		continue;
929 	    FD_SET(p->fd, &readset);
930 	    max_fd = max(max_fd, p->fd);
931 	}
932 
933 	ret = select (max_fd + 1,
934 		      &readset, NULL, NULL, &to);
935 	if (ret < 0) {
936 	    if (errno == EINTR)
937 		continue;
938 	    else
939 		krb5_err (context, 1, errno, "select");
940 	}
941 
942 	if (ret == 0) {
943 	    old_version = current_version;
944 	    kadm5_log_get_version_fd (log_fd, &current_version);
945 
946 	    if (current_version > old_version) {
947 		krb5_warnx(context,
948 			   "Missed a signal, updating slaves %lu to %lu",
949 			   (unsigned long)old_version,
950 			   (unsigned long)current_version);
951 		for (p = slaves; p != NULL; p = p->next) {
952 		    if (p->flags & SLAVE_F_DEAD)
953 			continue;
954 		    send_diffs (context, p, log_fd, database, current_version);
955 		}
956 	    }
957 	}
958 
959 	if (ret && FD_ISSET(signal_fd, &readset)) {
960 #ifndef NO_UNIX_SOCKETS
961 	    struct sockaddr_un peer_addr;
962 #else
963 	    struct sockaddr_storage peer_addr;
964 #endif
965 	    socklen_t peer_len = sizeof(peer_addr);
966 
967 	    if(recvfrom(signal_fd, (void *)&vers, sizeof(vers), 0,
968 			(struct sockaddr *)&peer_addr, &peer_len) < 0) {
969 		krb5_warn (context, errno, "recvfrom");
970 		continue;
971 	    }
972 	    --ret;
973 	    assert(ret >= 0);
974 	    old_version = current_version;
975 	    kadm5_log_get_version_fd (log_fd, &current_version);
976 	    if (current_version > old_version) {
977 		krb5_warnx(context,
978 			   "Got a signal, updating slaves %lu to %lu",
979 			   (unsigned long)old_version,
980 			   (unsigned long)current_version);
981 		for (p = slaves; p != NULL; p = p->next) {
982 		    if (p->flags & SLAVE_F_DEAD)
983 			continue;
984 		    send_diffs (context, p, log_fd, database, current_version);
985 		}
986 	    } else {
987 		krb5_warnx(context,
988 			   "Got a signal, but no update in log version %lu",
989 			   (unsigned long)current_version);
990 	    }
991         }
992 
993 	for(p = slaves; p != NULL; p = p->next) {
994 	    if (p->flags & SLAVE_F_DEAD)
995 	        continue;
996 	    if (ret && FD_ISSET(p->fd, &readset)) {
997 		--ret;
998 		assert(ret >= 0);
999 		if(process_msg (context, p, log_fd, database, current_version))
1000 		    slave_dead(context, p);
1001 	    } else if (slave_gone_p (p))
1002 		slave_dead(context, p);
1003 	    else if (slave_missing_p (p))
1004 		send_are_you_there (context, p);
1005 	}
1006 
1007 	if (ret && FD_ISSET(listen6_fd, &readset)) {
1008 	    add_slave (context, keytab, &slaves, listen6_fd);
1009 	    --ret;
1010 	    assert(ret >= 0);
1011 	}
1012 	if (ret && FD_ISSET(listen_fd, &readset)) {
1013 	    add_slave (context, keytab, &slaves, listen_fd);
1014 	    --ret;
1015 	    assert(ret >= 0);
1016 	}
1017 	write_stats(context, slaves, current_version);
1018     }
1019 
1020     if(exit_flag == SIGINT || exit_flag == SIGTERM)
1021 	krb5_warnx(context, "%s terminated", getprogname());
1022 #ifdef SIGXCPU
1023     else if(exit_flag == SIGXCPU)
1024 	krb5_warnx(context, "%s CPU time limit exceeded", getprogname());
1025 #endif
1026     else
1027 	krb5_warnx(context, "%s unexpected exit reason: %ld",
1028 		   getprogname(), (long)exit_flag);
1029 
1030     write_master_down(context);
1031 
1032     return 0;
1033 }
1034