]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - crypto/heimdal/lib/kadm5/ipropd_master.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / crypto / heimdal / lib / kadm5 / ipropd_master.c
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         rtbl_add_column_entry(tbl, SLAVE_SEEN, str);
759
760         slaves = slaves->next;
761     }
762
763     rtbl_format(tbl, fp);
764     rtbl_destroy(tbl);
765
766     fclose(fp);
767 }
768
769
770 static char sHDB[] = "HDB:";
771 static char *realm;
772 static int version_flag;
773 static int help_flag;
774 static char *keytab_str = sHDB;
775 static char *database;
776 static char *config_file;
777 static char *port_str;
778 #ifdef SUPPORT_DETACH
779 static int detach_from_console = 0;
780 #endif
781
782 static struct getargs args[] = {
783     { "config-file", 'c', arg_string, &config_file, NULL, NULL },
784     { "realm", 'r', arg_string, &realm, NULL, NULL },
785     { "keytab", 'k', arg_string, &keytab_str,
786       "keytab to get authentication from", "kspec" },
787     { "database", 'd', arg_string, &database, "database", "file"},
788     { "slave-stats-file", 0, arg_string, rk_UNCONST(&slave_stats_file),
789       "file for slave status information", "file"},
790     { "time-missing", 0, arg_string, rk_UNCONST(&slave_time_missing),
791       "time before slave is polled for presence", "time"},
792     { "time-gone", 0, arg_string, rk_UNCONST(&slave_time_gone),
793       "time of inactivity after which a slave is considered gone", "time"},
794     { "port", 0, arg_string, &port_str,
795       "port ipropd will listen to", "port"},
796 #ifdef SUPPORT_DETACH
797     { "detach", 0, arg_flag, &detach_from_console,
798       "detach from console", NULL },
799 #endif
800     { "hostname", 0, arg_string, rk_UNCONST(&master_hostname),
801       "hostname of master (if not same as hostname)", "hostname" },
802     { "version", 0, arg_flag, &version_flag, NULL, NULL },
803     { "help", 0, arg_flag, &help_flag, NULL, NULL }
804 };
805 static int num_args = sizeof(args) / sizeof(args[0]);
806
807 int
808 main(int argc, char **argv)
809 {
810     krb5_error_code ret;
811     krb5_context context;
812     void *kadm_handle;
813     kadm5_server_context *server_context;
814     kadm5_config_params conf;
815     krb5_socket_t signal_fd, listen_fd, listen6_fd;
816     int log_fd;
817     slave *slaves = NULL;
818     uint32_t current_version = 0, old_version = 0;
819     krb5_keytab keytab;
820     int optidx;
821     char **files;
822
823     optidx = krb5_program_setup(&context, argc, argv, args, num_args, NULL);
824
825     if(help_flag)
826         krb5_std_usage(0, args, num_args);
827     if(version_flag) {
828         print_version(NULL);
829         exit(0);
830     }
831
832     setup_signal();
833
834     if (config_file == NULL) {
835         asprintf(&config_file, "%s/kdc.conf", hdb_db_dir(context));
836         if (config_file == NULL)
837             errx(1, "out of memory");
838     }
839
840     ret = krb5_prepend_config_files_default(config_file, &files);
841     if (ret)
842         krb5_err(context, 1, ret, "getting configuration files");
843
844     ret = krb5_set_config_files(context, files);
845     krb5_free_config_files(files);
846     if (ret)
847         krb5_err(context, 1, ret, "reading configuration files");
848
849     time_before_gone = parse_time (slave_time_gone,  "s");
850     if (time_before_gone < 0)
851         krb5_errx (context, 1, "couldn't parse time: %s", slave_time_gone);
852     time_before_missing = parse_time (slave_time_missing,  "s");
853     if (time_before_missing < 0)
854         krb5_errx (context, 1, "couldn't parse time: %s", slave_time_missing);
855
856 #ifdef SUPPORT_DETACH
857     if (detach_from_console)
858         daemon(0, 0);
859 #endif
860     pidfile (NULL);
861     krb5_openlog (context, "ipropd-master", &log_facility);
862     krb5_set_warn_dest(context, log_facility);
863
864     ret = krb5_kt_register(context, &hdb_kt_ops);
865     if(ret)
866         krb5_err(context, 1, ret, "krb5_kt_register");
867
868     ret = krb5_kt_resolve(context, keytab_str, &keytab);
869     if(ret)
870         krb5_err(context, 1, ret, "krb5_kt_resolve: %s", keytab_str);
871
872     memset(&conf, 0, sizeof(conf));
873     if(realm) {
874         conf.mask |= KADM5_CONFIG_REALM;
875         conf.realm = realm;
876     }
877     ret = kadm5_init_with_skey_ctx (context,
878                                     KADM5_ADMIN_SERVICE,
879                                     NULL,
880                                     KADM5_ADMIN_SERVICE,
881                                     &conf, 0, 0,
882                                     &kadm_handle);
883     if (ret)
884         krb5_err (context, 1, ret, "kadm5_init_with_password_ctx");
885
886     server_context = (kadm5_server_context *)kadm_handle;
887
888     log_fd = open (server_context->log_context.log_file, O_RDONLY, 0);
889     if (log_fd < 0)
890         krb5_err (context, 1, errno, "open %s",
891                   server_context->log_context.log_file);
892
893     signal_fd = make_signal_socket (context);
894     listen_fd = make_listen_socket (context, port_str);
895     listen6_fd = make_listen6_socket (context, port_str);
896
897     kadm5_log_get_version_fd (log_fd, &current_version);
898
899     krb5_warnx(context, "ipropd-master started at version: %lu",
900                (unsigned long)current_version);
901
902     while(exit_flag == 0){
903         slave *p;
904         fd_set readset;
905         int max_fd = 0;
906         struct timeval to = {30, 0};
907         uint32_t vers;
908
909 #ifndef NO_LIMIT_FD_SETSIZE
910         if (signal_fd >= FD_SETSIZE || listen_fd >= FD_SETSIZE ||
911             listen6_fd >= FD_SETSIZE)
912             krb5_errx (context, 1, "fd too large");
913 #endif
914
915         FD_ZERO(&readset);
916         FD_SET(signal_fd, &readset);
917         max_fd = max(max_fd, signal_fd);
918         FD_SET(listen_fd, &readset);
919         max_fd = max(max_fd, listen_fd);
920         FD_SET(listen6_fd, &readset);
921         max_fd = max(max_fd, listen6_fd);
922
923         for (p = slaves; p != NULL; p = p->next) {
924             if (p->flags & SLAVE_F_DEAD)
925                 continue;
926             FD_SET(p->fd, &readset);
927             max_fd = max(max_fd, p->fd);
928         }
929
930         ret = select (max_fd + 1,
931                       &readset, NULL, NULL, &to);
932         if (ret < 0) {
933             if (errno == EINTR)
934                 continue;
935             else
936                 krb5_err (context, 1, errno, "select");
937         }
938
939         if (ret == 0) {
940             old_version = current_version;
941             kadm5_log_get_version_fd (log_fd, &current_version);
942
943             if (current_version > old_version) {
944                 krb5_warnx(context,
945                            "Missed a signal, updating slaves %lu to %lu",
946                            (unsigned long)old_version,
947                            (unsigned long)current_version);
948                 for (p = slaves; p != NULL; p = p->next) {
949                     if (p->flags & SLAVE_F_DEAD)
950                         continue;
951                     send_diffs (context, p, log_fd, database, current_version);
952                 }
953             }
954         }
955
956         if (ret && FD_ISSET(signal_fd, &readset)) {
957 #ifndef NO_UNIX_SOCKETS
958             struct sockaddr_un peer_addr;
959 #else
960             struct sockaddr_storage peer_addr;
961 #endif
962             socklen_t peer_len = sizeof(peer_addr);
963
964             if(recvfrom(signal_fd, (void *)&vers, sizeof(vers), 0,
965                         (struct sockaddr *)&peer_addr, &peer_len) < 0) {
966                 krb5_warn (context, errno, "recvfrom");
967                 continue;
968             }
969             --ret;
970             assert(ret >= 0);
971             old_version = current_version;
972             kadm5_log_get_version_fd (log_fd, &current_version);
973             if (current_version > old_version) {
974                 krb5_warnx(context,
975                            "Got a signal, updating slaves %lu to %lu",
976                            (unsigned long)old_version,
977                            (unsigned long)current_version);
978                 for (p = slaves; p != NULL; p = p->next) {
979                     if (p->flags & SLAVE_F_DEAD)
980                         continue;
981                     send_diffs (context, p, log_fd, database, current_version);
982                 }
983             } else {
984                 krb5_warnx(context,
985                            "Got a signal, but no update in log version %lu",
986                            (unsigned long)current_version);
987             }
988         }
989
990         for(p = slaves; p != NULL; p = p->next) {
991             if (p->flags & SLAVE_F_DEAD)
992                 continue;
993             if (ret && FD_ISSET(p->fd, &readset)) {
994                 --ret;
995                 assert(ret >= 0);
996                 if(process_msg (context, p, log_fd, database, current_version))
997                     slave_dead(context, p);
998             } else if (slave_gone_p (p))
999                 slave_dead(context, p);
1000             else if (slave_missing_p (p))
1001                 send_are_you_there (context, p);
1002         }
1003
1004         if (ret && FD_ISSET(listen6_fd, &readset)) {
1005             add_slave (context, keytab, &slaves, listen6_fd);
1006             --ret;
1007             assert(ret >= 0);
1008         }
1009         if (ret && FD_ISSET(listen_fd, &readset)) {
1010             add_slave (context, keytab, &slaves, listen_fd);
1011             --ret;
1012             assert(ret >= 0);
1013         }
1014         write_stats(context, slaves, current_version);
1015     }
1016
1017     if(exit_flag == SIGINT || exit_flag == SIGTERM)
1018         krb5_warnx(context, "%s terminated", getprogname());
1019 #ifdef SIGXCPU
1020     else if(exit_flag == SIGXCPU)
1021         krb5_warnx(context, "%s CPU time limit exceeded", getprogname());
1022 #endif
1023     else
1024         krb5_warnx(context, "%s unexpected exit reason: %ld",
1025                    getprogname(), (long)exit_flag);
1026
1027     write_master_down(context);
1028
1029     return 0;
1030 }