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