kdc: per-target CPPFLAGS do not have an _AM in the variable name
[heimdal.git] / lib / kadm5 / ipropd_master.c
blob3d693bdfe0a80c62e44550649c72d2259a44d25a
1 /*
2 * Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
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.
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.
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.
34 #include "iprop.h"
35 #include <rtbl.h>
37 static krb5_log_facility *log_facility;
39 static int verbose;
41 static const char *slave_stats_file;
42 static const char *slave_stats_temp_file;
43 static const char *slave_time_missing = "2 min";
44 static const char *slave_time_gone = "5 min";
46 static int time_before_missing;
47 static int time_before_gone;
49 const char *master_hostname;
50 const char *pidfile_basename;
51 static char hostname[128];
53 static krb5_socket_t
54 make_signal_socket (krb5_context context)
56 #ifndef NO_UNIX_SOCKETS
57 struct sockaddr_un addr;
58 const char *fn;
59 krb5_socket_t fd;
61 fn = kadm5_log_signal_socket(context);
63 fd = socket (AF_UNIX, SOCK_DGRAM, 0);
64 if (fd < 0)
65 krb5_err (context, 1, errno, "socket AF_UNIX");
66 memset (&addr, 0, sizeof(addr));
67 addr.sun_family = AF_UNIX;
68 strlcpy (addr.sun_path, fn, sizeof(addr.sun_path));
69 unlink (addr.sun_path);
70 if (bind (fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
71 krb5_err (context, 1, errno, "bind %s", addr.sun_path);
72 return fd;
73 #else
74 struct addrinfo *ai = NULL;
75 krb5_socket_t fd;
77 kadm5_log_signal_socket_info(context, 1, &ai);
79 fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
80 if (rk_IS_BAD_SOCKET(fd))
81 krb5_err (context, 1, rk_SOCK_ERRNO, "socket AF=%d", ai->ai_family);
83 if (rk_IS_SOCKET_ERROR( bind (fd, ai->ai_addr, ai->ai_addrlen) ))
84 krb5_err (context, 1, rk_SOCK_ERRNO, "bind");
85 return fd;
86 #endif
89 static krb5_socket_t
90 make_listen_socket (krb5_context context, const char *port_str)
92 krb5_socket_t fd;
93 int one = 1;
94 struct sockaddr_in addr;
96 fd = socket (AF_INET, SOCK_STREAM, 0);
97 if (rk_IS_BAD_SOCKET(fd))
98 krb5_err (context, 1, rk_SOCK_ERRNO, "socket AF_INET");
99 (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one));
100 memset (&addr, 0, sizeof(addr));
101 addr.sin_family = AF_INET;
103 if (port_str) {
104 addr.sin_port = krb5_getportbyname (context,
105 port_str, "tcp",
107 if (addr.sin_port == 0) {
108 char *ptr;
109 long port;
111 port = strtol (port_str, &ptr, 10);
112 if (port == 0 && ptr == port_str)
113 krb5_errx (context, 1, "bad port `%s'", port_str);
114 addr.sin_port = htons(port);
116 } else {
117 addr.sin_port = krb5_getportbyname (context, IPROP_SERVICE,
118 "tcp", IPROP_PORT);
120 if(bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
121 krb5_err (context, 1, errno, "bind");
122 if (listen(fd, SOMAXCONN) < 0)
123 krb5_err (context, 1, errno, "listen");
124 return fd;
128 struct slave {
129 krb5_socket_t fd;
130 struct sockaddr_in addr;
131 char *name;
132 krb5_auth_context ac;
133 uint32_t version;
134 uint32_t version_tstamp;
135 uint32_t version_ack;
136 time_t seen;
137 unsigned long flags;
138 #define SLAVE_F_DEAD 0x1
139 #define SLAVE_F_AYT 0x2
140 #define SLAVE_F_READY 0x4
142 * We'll use non-blocking I/O so no slave can hold us back.
144 * We call the state left over from a partial write a "tail".
146 * The krb5_data holding an KRB-PRIV will be the write buffer.
148 struct {
149 /* Every message we send is a KRB-PRIV with a 4-byte length prefixed */
150 uint8_t header_buf[4];
151 krb5_data header;
152 krb5_data packet;
153 size_t packet_off;
154 /* For send_complete() we need an sp as part of the tail */
155 krb5_storage *dump;
156 uint32_t vno;
157 } tail;
158 struct {
159 uint8_t header_buf[4];
160 krb5_data packet;
161 size_t offset;
162 int hlen;
163 } input;
165 * Continuation for fair diff sending we send N entries at a time.
167 struct {
168 off_t off_next_version; /* offset in log of next diff */
169 uint32_t initial_version; /* at time of previous diff */
170 uint32_t initial_tstamp; /* at time of previous diff */
171 uint32_t last_version_sent;
172 int more; /* need to send more diffs */
173 } next_diff;
174 struct slave *next;
177 typedef struct slave slave;
179 static int
180 check_acl (krb5_context context, const char *name)
182 const char *fn;
183 FILE *fp;
184 char buf[256];
185 int ret = 1;
186 char *slavefile = NULL;
188 if (asprintf(&slavefile, "%s/slaves", hdb_db_dir(context)) == -1
189 || slavefile == NULL)
190 errx(1, "out of memory");
192 fn = krb5_config_get_string_default(context,
193 NULL,
194 slavefile,
195 "kdc",
196 "iprop-acl",
197 NULL);
199 fp = fopen (fn, "r");
200 free(slavefile);
201 if (fp == NULL)
202 return 1;
203 while (fgets(buf, sizeof(buf), fp) != NULL) {
204 buf[strcspn(buf, "\r\n")] = '\0';
205 if (strcmp (buf, name) == 0) {
206 ret = 0;
207 break;
210 fclose (fp);
211 return ret;
214 static void
215 slave_seen(slave *s)
217 s->flags &= ~SLAVE_F_AYT;
218 s->seen = time(NULL);
221 static int
222 slave_missing_p (slave *s)
224 if (time(NULL) > s->seen + time_before_missing)
225 return 1;
226 return 0;
229 static int
230 slave_gone_p (slave *s)
232 if (time(NULL) > s->seen + time_before_gone)
233 return 1;
234 return 0;
237 static void
238 slave_dead(krb5_context context, slave *s)
240 krb5_warnx(context, "slave %s dead", s->name);
242 if (!rk_IS_BAD_SOCKET(s->fd)) {
243 rk_closesocket (s->fd);
244 s->fd = rk_INVALID_SOCKET;
246 s->flags |= SLAVE_F_DEAD;
247 slave_seen(s);
250 static void
251 remove_slave (krb5_context context, slave *s, slave **root)
253 slave **p;
255 if (!rk_IS_BAD_SOCKET(s->fd))
256 rk_closesocket (s->fd);
257 if (s->name)
258 free (s->name);
259 if (s->ac)
260 krb5_auth_con_free (context, s->ac);
262 /* Free any pending input/output state */
263 krb5_data_free(&s->input.packet);
264 krb5_data_free(&s->tail.packet);
265 krb5_storage_free(s->tail.dump);
267 for (p = root; *p; p = &(*p)->next)
268 if (*p == s) {
269 *p = s->next;
270 break;
272 free (s);
275 static void
276 add_slave (krb5_context context, krb5_keytab keytab, slave **root,
277 krb5_socket_t fd)
279 krb5_principal server = NULL;
280 krb5_error_code ret;
281 slave *s;
282 socklen_t addr_len;
283 krb5_ticket *ticket = NULL;
285 s = calloc(1, sizeof(*s));
286 if (s == NULL) {
287 krb5_warnx (context, "add_slave: no memory");
288 return;
290 s->name = NULL;
291 s->ac = NULL;
292 s->input.packet.data = NULL;
293 s->tail.header.data = NULL;
294 s->tail.packet.data = NULL;
295 s->tail.dump = NULL;
297 addr_len = sizeof(s->addr);
298 s->fd = accept (fd, (struct sockaddr *)&s->addr, &addr_len);
299 if (rk_IS_BAD_SOCKET(s->fd)) {
300 krb5_warn (context, rk_SOCK_ERRNO, "accept");
301 goto error;
305 * We write message lengths separately from the payload, and may do
306 * back-to-back small writes when flushing pending input and then a new
307 * update. Avoid Nagle delays.
309 #if defined(IPPROTO_TCP) && defined(TCP_NODELAY)
311 int nodelay = 1;
312 (void) setsockopt(s->fd, IPPROTO_TCP, TCP_NODELAY,
313 (void *)&nodelay, sizeof(nodelay));
315 #endif
317 ret = krb5_sname_to_principal (context, hostname, IPROP_NAME,
318 KRB5_NT_SRV_HST, &server);
319 if (ret) {
320 krb5_warn (context, ret, "krb5_sname_to_principal");
321 goto error;
324 ret = krb5_recvauth (context, &s->ac, &s->fd,
325 IPROP_VERSION, server, 0, keytab, &ticket);
328 * We'll be doing non-blocking I/O only after authentication. We don't
329 * want to get stuck talking to any one slave.
331 * If we get a partial write, we'll finish writing when the socket becomes
332 * writable.
334 * Partial reads will be treated as EOF, causing the slave to be marked
335 * dead.
337 * To do non-blocking I/O for authentication we'll have to implement our
338 * own krb5_recvauth().
340 socket_set_nonblocking(s->fd, 1);
342 if (ret) {
343 krb5_warn (context, ret, "krb5_recvauth");
344 goto error;
346 ret = krb5_unparse_name (context, ticket->client, &s->name);
347 if (ret) {
348 krb5_warn (context, ret, "krb5_unparse_name");
349 goto error;
351 if (check_acl (context, s->name)) {
352 krb5_warnx (context, "%s not in acl", s->name);
353 goto error;
357 slave *l = *root;
359 while (l) {
360 if (strcmp(l->name, s->name) == 0)
361 break;
362 l = l->next;
364 if (l) {
365 if (l->flags & SLAVE_F_DEAD) {
366 remove_slave(context, l, root);
367 } else {
368 krb5_warnx (context, "second connection from %s", s->name);
369 goto error;
374 krb5_free_principal(context, server);
375 krb5_free_ticket(context, ticket);
376 krb5_warnx (context, "connection from %s", s->name);
378 s->version = 0;
379 s->version_ack = 0;
380 s->flags = 0;
381 slave_seen(s);
382 s->next = *root;
383 *root = s;
384 return;
385 error:
386 remove_slave(context, s, root);
387 krb5_free_principal(context, server);
388 if (ticket)
389 krb5_free_ticket(context, ticket);
392 static int
393 dump_one (krb5_context context, HDB *db, hdb_entry *entry, void *v)
395 krb5_error_code ret;
396 krb5_storage *dump = (krb5_storage *)v;
397 krb5_storage *sp;
398 krb5_data data;
400 ret = hdb_entry2value (context, entry, &data);
401 if (ret)
402 return ret;
403 ret = krb5_data_realloc (&data, data.length + 4);
404 if (ret)
405 goto done;
406 memmove ((char *)data.data + 4, data.data, data.length - 4);
407 sp = krb5_storage_from_data(&data);
408 if (sp == NULL) {
409 ret = krb5_enomem(context);
410 goto done;
412 ret = krb5_store_uint32(sp, ONE_PRINC);
413 krb5_storage_free(sp);
415 if (ret == 0)
416 ret = krb5_store_data(dump, data);
418 done:
419 krb5_data_free (&data);
420 return ret;
423 static int
424 write_dump (krb5_context context, krb5_storage *dump,
425 const char *database, uint32_t current_version)
427 krb5_error_code ret;
428 krb5_storage *sp;
429 HDB *db;
430 krb5_data data;
431 char buf[8];
433 /* we assume that the caller has obtained an exclusive lock */
435 ret = krb5_storage_truncate(dump, 0);
436 if (ret)
437 return ret;
439 if (krb5_storage_seek(dump, 0, SEEK_SET) != 0)
440 return errno;
443 * First we store zero as the HDB version, this will indicate to a
444 * later reader that the dumpfile is invalid. We later write the
445 * correct version in the file after we have written all of the
446 * messages. A dump with a zero version will not be considered
447 * to be valid.
450 ret = krb5_store_uint32(dump, 0);
451 if (ret)
452 return ret;
454 ret = hdb_create (context, &db, database);
455 if (ret)
456 krb5_err (context, IPROPD_RESTART, ret, "hdb_create: %s", database);
457 ret = db->hdb_open (context, db, O_RDONLY, 0);
458 if (ret)
459 krb5_err (context, IPROPD_RESTART, ret, "db->open");
461 sp = krb5_storage_from_mem (buf, 4);
462 if (sp == NULL)
463 krb5_errx (context, IPROPD_RESTART, "krb5_storage_from_mem");
464 krb5_store_uint32 (sp, TELL_YOU_EVERYTHING);
465 krb5_storage_free (sp);
467 data.data = buf;
468 data.length = 4;
470 ret = krb5_store_data(dump, data);
471 if (ret) {
472 krb5_warn (context, ret, "write_dump");
473 return ret;
476 ret = hdb_foreach (context, db, HDB_F_ADMIN_DATA, dump_one, dump);
477 if (ret) {
478 krb5_warn (context, ret, "write_dump: hdb_foreach");
479 return ret;
482 (*db->hdb_close)(context, db);
483 (*db->hdb_destroy)(context, db);
485 sp = krb5_storage_from_mem (buf, 8);
486 if (sp == NULL)
487 krb5_errx (context, IPROPD_RESTART, "krb5_storage_from_mem");
488 ret = krb5_store_uint32(sp, NOW_YOU_HAVE);
489 if (ret == 0)
490 krb5_store_uint32(sp, current_version);
491 krb5_storage_free (sp);
493 data.length = 8;
495 if (ret == 0)
496 ret = krb5_store_data(dump, data);
499 * We must ensure that the entire valid dump is written to disk
500 * before we write the current version at the front thus making
501 * it a valid dump file. If we crash around here, this can be
502 * important upon reboot.
505 if (ret == 0)
506 ret = krb5_storage_fsync(dump);
508 if (ret == 0 && krb5_storage_seek(dump, 0, SEEK_SET) == -1)
509 ret = errno;
511 /* Write current version at the front making the dump valid */
513 if (ret == 0)
514 ret = krb5_store_uint32(dump, current_version);
517 * We don't need to fsync(2) after the real version is written as
518 * it is not a disaster if it doesn't make it to disk if we crash.
519 * After all, we'll just create a new dumpfile.
522 if (ret == 0)
523 krb5_warnx(context, "wrote new dumpfile (version %u)",
524 current_version);
525 else
526 krb5_warn(context, ret, "failed to write new dumpfile (version %u)",
527 current_version);
529 return ret;
532 static int
533 mk_priv_tail(krb5_context context, slave *s, krb5_data *data)
535 uint32_t len;
536 int ret;
538 ret = krb5_mk_priv(context, s->ac, data, &s->tail.packet, NULL);
539 if (ret)
540 return ret;
542 len = s->tail.packet.length;
543 _krb5_put_int(s->tail.header_buf, len, sizeof(s->tail.header_buf));
544 s->tail.header.length = sizeof(s->tail.header_buf);
545 s->tail.header.data = s->tail.header_buf;
546 return 0;
549 static int
550 have_tail(slave *s)
552 return s->tail.header.length || s->tail.packet.length || s->tail.dump;
555 static int
556 more_diffs(slave *s)
558 return s->next_diff.more;
561 #define SEND_COMPLETE_MAX_RECORDS 50
562 #define SEND_DIFFS_MAX_RECORDS 50
564 static int
565 send_tail(krb5_context context, slave *s)
567 krb5_data data;
568 ssize_t bytes = 0;
569 size_t rem = 0;
570 size_t n;
571 int ret;
573 if (! have_tail(s))
574 return 0;
577 * For the case where we're continuing a send_complete() send up to
578 * SEND_COMPLETE_MAX_RECORDS records now, and the rest asynchronously
579 * later. This ensures that sending a complete dump to a slow-to-drain
580 * client does not prevent others from getting serviced.
582 for (n = 0; n < SEND_COMPLETE_MAX_RECORDS; n++) {
583 if (! have_tail(s))
584 return 0;
586 if (s->tail.header.length) {
587 bytes = krb5_net_write(context, &s->fd,
588 s->tail.header.data,
589 s->tail.header.length);
590 if (bytes < 0)
591 goto err;
593 s->tail.header.length -= bytes;
594 s->tail.header.data = (char *)s->tail.header.data + bytes;
595 rem = s->tail.header.length;
596 if (rem)
597 goto ewouldblock;
600 if (s->tail.packet.length) {
601 bytes = krb5_net_write(context, &s->fd,
602 (char *)s->tail.packet.data + s->tail.packet_off,
603 s->tail.packet.length - s->tail.packet_off);
604 if (bytes < 0)
605 goto err;
606 s->tail.packet_off += bytes;
607 if (bytes)
608 slave_seen(s);
609 rem = s->tail.packet.length - s->tail.packet_off;
610 if (rem)
611 goto ewouldblock;
613 krb5_data_free(&s->tail.packet);
614 s->tail.packet_off = 0;
617 if (s->tail.dump == NULL)
618 return 0;
621 * We're in the middle of a send_complete() that was interrupted by
622 * EWOULDBLOCK. Continue the sending of the dump.
624 ret = krb5_ret_data(s->tail.dump, &data);
625 if (ret == HEIM_ERR_EOF) {
626 krb5_storage_free(s->tail.dump);
627 s->tail.dump = NULL;
628 s->version = s->tail.vno;
629 return 0;
632 if (ret) {
633 krb5_warn(context, ret, "failed to read entry from dump!");
634 } else {
635 ret = mk_priv_tail(context, s, &data);
636 krb5_data_free(&data);
637 if (ret == 0)
638 continue;
639 krb5_warn(context, ret, "failed to make and send a KRB-PRIV to %s",
640 s->name);
643 slave_dead(context, s);
644 return ret;
647 if (ret == 0 && s->tail.dump != NULL)
648 return EWOULDBLOCK;
650 err:
651 if (errno != EAGAIN && errno != EWOULDBLOCK) {
652 krb5_warn(context, ret = errno,
653 "error sending diffs to now-dead slave %s", s->name);
654 slave_dead(context, s);
655 return ret;
658 ewouldblock:
659 if (verbose)
660 krb5_warnx(context, "would block writing %llu bytes to slave %s",
661 (unsigned long long)rem, s->name);
662 return EWOULDBLOCK;
665 static int
666 send_complete(krb5_context context, slave *s, const char *database,
667 uint32_t current_version, uint32_t oldest_version,
668 uint32_t initial_log_tstamp)
670 krb5_error_code ret;
671 krb5_storage *dump = NULL;
672 uint32_t vno = 0;
673 int fd = -1;
674 struct stat st;
675 char *dfn;
677 ret = asprintf(&dfn, "%s/ipropd.dumpfile", hdb_db_dir(context));
678 if (ret == -1 || !dfn)
679 return krb5_enomem(context);
681 fd = open(dfn, O_CREAT|O_RDWR, 0600);
682 if (fd == -1) {
683 ret = errno;
684 krb5_warn(context, ret, "Cannot open/create iprop dumpfile %s", dfn);
685 free(dfn);
686 return ret;
688 free(dfn);
690 dump = krb5_storage_from_fd(fd);
691 if (!dump) {
692 ret = errno;
693 krb5_warn(context, ret, "krb5_storage_from_fd");
694 goto done;
697 for (;;) {
698 ret = flock(fd, LOCK_SH);
699 if (ret == -1) {
700 ret = errno;
701 krb5_warn(context, ret, "flock(fd, LOCK_SH)");
702 goto done;
705 if (krb5_storage_seek(dump, 0, SEEK_SET) == (off_t)-1) {
706 ret = errno;
707 krb5_warn(context, ret, "krb5_storage_seek(dump, 0, SEEK_SET)");
708 goto done;
711 vno = 0;
712 ret = krb5_ret_uint32(dump, &vno);
713 if (ret && ret != HEIM_ERR_EOF) {
714 krb5_warn(context, ret, "krb5_ret_uint32(dump, &vno)");
715 goto done;
718 if (fstat(fd, &st) == -1) {
719 ret = errno;
720 krb5_warn(context, ret, "send_complete: could not stat dump file");
721 goto done;
725 * If the current dump has an appropriate version, then we can
726 * break out of the loop and send the file below.
728 if (ret == 0 && vno != 0 && st.st_mtime > initial_log_tstamp &&
729 vno >= oldest_version && vno <= current_version)
730 break;
732 if (verbose)
733 krb5_warnx(context, "send_complete: dumping HDB");
736 * Otherwise, we may need to write a new dump file. We
737 * obtain an exclusive lock on the fd. Because this is
738 * not guaranteed to be an upgrade of our existing shared
739 * lock, someone else may have written a new dumpfile while
740 * we were waiting and so we must first check the vno of
741 * the dump to see if that happened. If it did, we need
742 * to go back to the top of the loop so that we can downgrade
743 * our lock to a shared one.
746 ret = flock(fd, LOCK_EX);
747 if (ret == -1) {
748 ret = errno;
749 krb5_warn(context, ret, "flock(fd, LOCK_EX)");
750 goto done;
753 ret = krb5_storage_seek(dump, 0, SEEK_SET);
754 if (ret == -1) {
755 ret = errno;
756 krb5_warn(context, ret, "krb5_storage_seek(dump, 0, SEEK_SET)");
757 goto done;
760 vno = 0;
761 ret = krb5_ret_uint32(dump, &vno);
762 if (ret && ret != HEIM_ERR_EOF) {
763 krb5_warn(context, ret, "krb5_ret_uint32(dump, &vno)");
764 goto done;
767 if (fstat(fd, &st) == -1) {
768 ret = errno;
769 krb5_warn(context, ret, "send_complete: could not stat dump file");
770 goto done;
773 /* check if someone wrote a better version for us */
774 if (ret == 0 && vno != 0 && st.st_mtime > initial_log_tstamp &&
775 vno >= oldest_version && vno <= current_version)
776 continue;
778 /* Now, we know that we must write a new dump file. */
780 ret = write_dump(context, dump, database, current_version);
781 if (ret)
782 goto done;
785 * And we must continue to the top of the loop so that we can
786 * downgrade to a shared lock.
791 * Leaving the above loop, dump should have a ptr right after the initial
792 * 4 byte DB version number and we should have a shared lock on the file
793 * (which we may have just created), so we are reading to start sending
794 * the data down the wire.
796 * Note: (krb5_storage_from_fd() dup()'s the fd)
799 s->tail.dump = dump;
800 s->tail.vno = vno;
801 dump = NULL;
802 ret = send_tail(context, s);
804 done:
805 if (fd != -1)
806 close(fd);
807 if (dump)
808 krb5_storage_free(dump);
809 return ret;
812 static int
813 send_are_you_there (krb5_context context, slave *s)
815 krb5_storage *sp;
816 krb5_data data;
817 char buf[4];
818 int ret;
820 if (s->flags & (SLAVE_F_DEAD|SLAVE_F_AYT))
821 return 0;
824 * Write any remainder of previous write, if we can. If we'd block we'll
825 * return EWOULDBLOCK.
827 ret = send_tail(context, s);
828 if (ret)
829 return ret;
831 krb5_warnx(context, "slave %s missing, sending AYT", s->name);
833 s->flags |= SLAVE_F_AYT;
835 data.data = buf;
836 data.length = 4;
838 sp = krb5_storage_from_mem (buf, 4);
839 if (sp == NULL) {
840 krb5_warnx (context, "are_you_there: krb5_data_alloc");
841 slave_dead(context, s);
842 return ENOMEM;
844 ret = krb5_store_uint32(sp, ARE_YOU_THERE);
845 krb5_storage_free (sp);
847 if (ret == 0)
848 ret = mk_priv_tail(context, s, &data);
849 if (ret == 0)
850 ret = send_tail(context, s);
851 if (ret && ret != EWOULDBLOCK) {
852 krb5_warn(context, ret, "are_you_there");
853 slave_dead(context, s);
855 return ret;
858 static int
859 diffready(krb5_context context, slave *s)
862 * Don't send any diffs until slave has sent an I_HAVE telling us the
863 * initial version number!
865 if ((s->flags & SLAVE_F_READY) == 0)
866 return 0;
868 if (s->flags & SLAVE_F_DEAD) {
869 if (verbose)
870 krb5_warnx(context, "not sending diffs to dead slave %s", s->name);
871 return 0;
874 /* Write any remainder of previous write, if we can. */
875 if (send_tail(context, s) != 0)
876 return 0;
878 return 1;
881 static int
882 nodiffs(krb5_context context, slave *s, uint32_t current_version)
884 krb5_storage *sp;
885 krb5_data data;
886 int ret;
888 if (s->version < current_version)
889 return 0;
892 * If we had sent a partial diff, and now they're caught up, then there's
893 * no more.
895 s->next_diff.more = 0;
897 if (verbose)
898 krb5_warnx(context, "slave %s version %ld already sent", s->name,
899 (long)s->version);
900 sp = krb5_storage_emem();
901 if (sp == NULL)
902 krb5_errx(context, IPROPD_RESTART, "krb5_storage_from_mem");
904 ret = krb5_store_uint32(sp, YOU_HAVE_LAST_VERSION);
905 if (ret == 0) {
906 krb5_data_zero(&data);
907 ret = krb5_storage_to_data(sp, &data);
909 krb5_storage_free(sp);
910 if (ret == 0) {
911 ret = mk_priv_tail(context, s, &data);
912 krb5_data_free(&data);
914 if (ret == 0)
915 send_tail(context, s);
917 return 1;
921 * Lock the log and return initial version and timestamp
923 static int
924 get_first(kadm5_server_context *server_context, int log_fd,
925 uint32_t *initial_verp, uint32_t *initial_timep)
927 krb5_context context = server_context->context;
928 int ret;
931 * We don't want to perform tight retry loops on log access errors, so on
932 * error mark the slave dead. The slave reconnect after a delay...
934 if (flock(log_fd, LOCK_SH) == -1) {
935 krb5_warn(context, errno, "could not obtain shared lock on log file");
936 return -1;
939 ret = kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_FIRST,
940 initial_verp, initial_timep);
941 if (ret == HEIM_ERR_EOF)
942 ret = kadm5_log_get_version_fd(server_context, log_fd,
943 LOG_VERSION_UBER, initial_verp,
944 initial_timep);
945 if (ret != 0) {
946 flock(log_fd, LOCK_UN);
947 krb5_warn(context, ret, "could not read initial log entry");
948 return -1;
951 return 0;
955 * Find the left end of the diffs in the log we want to send.
957 * - On success, return a positive offset to the first new entry, retaining
958 * a read lock on the log file.
959 * - On error, return a negative offset, with the lock released.
960 * - If we simply find no successor entry in the log, return zero
961 * with the lock released, which indicates that fallback to send_complete()
962 * is needed.
964 static off_t
965 get_left(kadm5_server_context *server_context, slave *s, krb5_storage *sp,
966 int log_fd, uint32_t current_version,
967 uint32_t *initial_verp, uint32_t *initial_timep)
969 krb5_context context = server_context->context;
970 off_t pos;
971 off_t left;
972 int ret;
974 for (;;) {
975 uint32_t ver = s->version;
977 /* This acquires a read lock on success */
978 ret = get_first(server_context, log_fd,
979 initial_verp, initial_timep);
980 if (ret != 0)
981 return -1;
983 /* When the slave version is out of range, send the whole database. */
984 if (ver == 0 || ver < *initial_verp || ver > current_version) {
985 flock(log_fd, LOCK_UN);
986 return 0;
989 /* Avoid seeking past the last committed record */
990 if (kadm5_log_goto_end(server_context, sp) != 0 ||
991 (pos = krb5_storage_seek(sp, 0, SEEK_CUR)) < 0)
992 goto err;
995 * First try to see if we can find it quickly by seeking to the right
996 * end of the previous diff sent.
998 if (s->next_diff.last_version_sent > 0 &&
999 s->next_diff.off_next_version > 0 &&
1000 s->next_diff.off_next_version < pos &&
1001 s->next_diff.initial_version == *initial_verp &&
1002 s->next_diff.initial_tstamp == *initial_timep) {
1004 * Sanity check that the left version matches what we wanted, the
1005 * log may have been truncated since.
1007 left = s->next_diff.off_next_version;
1008 if (krb5_storage_seek(sp, left, SEEK_SET) != left)
1009 goto err;
1010 if (kadm5_log_next(context, sp, &ver, NULL, NULL, NULL) == 0 &&
1011 ver == s->next_diff.last_version_sent + 1)
1012 return left;
1015 if (krb5_storage_seek(sp, pos, SEEK_SET) != pos)
1016 goto err;
1019 * Drop the lock and try to find the left entry by seeking backward
1020 * from the end of the end of the log. If we succeed, re-acquire the
1021 * lock, update "next_diff", and retry the fast-path.
1023 flock(log_fd, LOCK_UN);
1025 /* Slow path: seek backwards, entry by entry, from the end */
1026 for (;;) {
1027 enum kadm_ops op;
1028 uint32_t len;
1030 ret = kadm5_log_previous(context, sp, &ver, NULL, &op, &len);
1031 if (ret)
1032 return -1;
1033 left = krb5_storage_seek(sp, -16, SEEK_CUR);
1034 if (left < 0)
1035 return left;
1036 if (ver == s->version + 1)
1037 break;
1040 * We don't expect to reach the slave's version, unless the log
1041 * has been modified after we released the lock.
1043 if (ver == s->version) {
1044 krb5_warnx(context, "iprop log truncated while sending diffs "
1045 "to slave?? ver = %lu", (unsigned long)ver);
1046 return -1;
1049 /* If we've reached the uber record, send the complete database */
1050 if (left == 0 || (ver == 0 && op == kadm_nop))
1051 return 0;
1053 assert(ver == s->version + 1);
1055 /* Set up the fast-path pre-conditions */
1056 s->next_diff.last_version_sent = s->version;
1057 s->next_diff.off_next_version = left;
1058 s->next_diff.initial_version = *initial_verp;
1059 s->next_diff.initial_tstamp = *initial_timep;
1062 * If we loop then we're hoping to hit the fast path so we can return a
1063 * non-zero, positive left offset with the lock held.
1065 * We just updated the fast path pre-conditions, so unless a log
1066 * truncation event happens between the point where we dropped the lock
1067 * and the point where we rearcuire it above, we will hit the fast
1068 * path.
1072 err:
1073 flock(log_fd, LOCK_UN);
1074 return -1;
1077 static off_t
1078 get_right(krb5_context context, int log_fd, krb5_storage *sp,
1079 int lastver, slave *s, off_t left, uint32_t *verp)
1081 int ret = 0;
1082 int i = 0;
1083 uint32_t ver = s->version;
1084 off_t right = krb5_storage_seek(sp, left, SEEK_SET);
1086 if (right <= 0) {
1087 flock(log_fd, LOCK_UN);
1088 return -1;
1091 /* The "lastver" bound should preclude us reaching EOF */
1092 for (; ret == 0 && i < SEND_DIFFS_MAX_RECORDS && ver < lastver; ++i) {
1093 uint32_t logver;
1095 ret = kadm5_log_next(context, sp, &logver, NULL, NULL, NULL);
1096 if (logver != ++ver)
1097 ret = KADM5_LOG_CORRUPT;
1100 if (ret == 0)
1101 right = krb5_storage_seek(sp, 0, SEEK_CUR);
1102 else
1103 right = -1;
1104 if (right <= 0) {
1105 flock(log_fd, LOCK_UN);
1106 return -1;
1108 *verp = ver;
1109 return right;
1112 static void
1113 send_diffs(kadm5_server_context *server_context, slave *s, int log_fd,
1114 const char *database, uint32_t current_version)
1116 krb5_context context = server_context->context;
1117 krb5_storage *sp;
1118 uint32_t initial_version;
1119 uint32_t initial_tstamp;
1120 uint32_t ver = 0;
1121 off_t left = 0;
1122 off_t right = 0;
1123 krb5_ssize_t bytes;
1124 krb5_data data;
1125 int ret = 0;
1127 if (!diffready(context, s) || nodiffs(context, s, current_version))
1128 return;
1130 if (verbose)
1131 krb5_warnx(context, "sending diffs to live-seeming slave %s", s->name);
1133 sp = krb5_storage_from_fd(log_fd);
1134 if (sp == NULL)
1135 krb5_err(context, IPROPD_RESTART_SLOW, ENOMEM,
1136 "send_diffs: out of memory");
1138 left = get_left(server_context, s, sp, log_fd, current_version,
1139 &initial_version, &initial_tstamp);
1140 if (left < 0) {
1141 krb5_storage_free(sp);
1142 slave_dead(context, s);
1143 return;
1146 if (left == 0) {
1147 /* Slave's version is not in the log, fall back on send_complete() */
1148 krb5_storage_free(sp);
1149 send_complete(context, s, database, current_version,
1150 initial_version, initial_tstamp);
1151 return;
1154 /* We still hold the read lock, if right > 0 */
1155 right = get_right(server_context->context, log_fd, sp, current_version,
1156 s, left, &ver);
1157 if (right == left) {
1158 flock(log_fd, LOCK_UN);
1159 krb5_storage_free(sp);
1160 return;
1162 if (right < left) {
1163 assert(right < 0);
1164 krb5_storage_free(sp);
1165 slave_dead(context, s);
1166 return;
1169 if (krb5_storage_seek(sp, left, SEEK_SET) != left) {
1170 ret = errno ? errno : EIO;
1171 flock(log_fd, LOCK_UN);
1172 krb5_warn(context, ret, "send_diffs: krb5_storage_seek");
1173 krb5_storage_free(sp);
1174 slave_dead(context, s);
1175 return;
1178 ret = krb5_data_alloc(&data, right - left + 4);
1179 if (ret) {
1180 flock(log_fd, LOCK_UN);
1181 krb5_warn(context, ret, "send_diffs: krb5_data_alloc");
1182 krb5_storage_free(sp);
1183 slave_dead(context, s);
1184 return;
1187 bytes = krb5_storage_read(sp, (char *)data.data + 4, data.length - 4);
1188 flock(log_fd, LOCK_UN);
1189 krb5_storage_free(sp);
1190 if (bytes != data.length - 4)
1191 krb5_errx(context, IPROPD_RESTART, "locked log truncated???");
1193 sp = krb5_storage_from_data(&data);
1194 if (sp == NULL) {
1195 krb5_err(context, IPROPD_RESTART_SLOW, ENOMEM, "out of memory");
1196 return;
1198 ret = krb5_store_uint32(sp, FOR_YOU);
1199 krb5_storage_free(sp);
1201 if (ret == 0)
1202 ret = mk_priv_tail(context, s, &data);
1203 krb5_data_free(&data);
1204 if (ret == 0) {
1205 /* Save the fast-path continuation */
1206 s->next_diff.last_version_sent = ver;
1207 s->next_diff.off_next_version = right;
1208 s->next_diff.initial_version = initial_version;
1209 s->next_diff.initial_tstamp = initial_tstamp;
1210 s->next_diff.more = ver < current_version;
1211 ret = send_tail(context, s);
1213 krb5_warnx(context,
1214 "syncing slave %s from version %lu to version %lu",
1215 s->name, (unsigned long)s->version,
1216 (unsigned long)ver);
1217 s->version = ver;
1220 if (ret && ret != EWOULDBLOCK) {
1221 krb5_warn(context, ret, "send_diffs: making or sending "
1222 "KRB-PRIV message");
1223 slave_dead(context, s);
1224 return;
1226 slave_seen(s);
1227 return;
1230 /* Sensible bound on slave message size */
1231 #define SLAVE_MSG_MAX 65536
1233 static int
1234 fill_input(krb5_context context, slave *s)
1236 krb5_error_code ret;
1238 if (s->input.hlen < 4) {
1239 uint8_t *buf = s->input.header_buf + s->input.hlen;
1240 size_t len = 4 - s->input.hlen;
1241 krb5_ssize_t bytes = krb5_net_read(context, &s->fd, buf, len);
1243 if (bytes == 0)
1244 return HEIM_ERR_EOF;
1245 if (bytes < 0) {
1246 if (errno == EWOULDBLOCK || errno == EAGAIN)
1247 return EWOULDBLOCK;
1248 return errno ? errno : EIO;
1250 s->input.hlen += bytes;
1251 if (bytes < len)
1252 return EWOULDBLOCK;
1254 buf = s->input.header_buf;
1255 len = ((unsigned long)buf[0] << 24) | (buf[1] << 16)
1256 | (buf[2] << 8) | buf[3];
1257 if (len > SLAVE_MSG_MAX)
1258 return EINVAL;
1259 ret = krb5_data_alloc(&s->input.packet, len);
1260 if (ret != 0)
1261 return ret;
1264 if (s->input.offset < s->input.packet.length) {
1265 u_char *buf = (u_char *)s->input.packet.data + s->input.offset;
1266 size_t len = s->input.packet.length - s->input.offset;
1267 krb5_ssize_t bytes = krb5_net_read(context, &s->fd, buf, len);
1269 if (bytes == 0)
1270 return HEIM_ERR_EOF;
1271 if (bytes < 0) {
1272 if (errno == EWOULDBLOCK || errno == EAGAIN)
1273 return EWOULDBLOCK;
1274 return errno ? errno : EIO;
1276 s->input.offset += bytes;
1277 if (bytes != len)
1278 return EWOULDBLOCK;
1280 return 0;
1283 static int
1284 read_msg(krb5_context context, slave *s, krb5_data *out)
1286 int ret = fill_input(context, s);
1288 if (ret != 0)
1289 return ret;
1291 ret = krb5_rd_priv(context, s->ac, &s->input.packet, out, NULL);
1293 /* Prepare for next packet */
1294 krb5_data_free(&s->input.packet);
1295 s->input.offset = 0;
1296 s->input.hlen = 0;
1298 return ret;
1301 static int
1302 process_msg(kadm5_server_context *server_context, slave *s, int log_fd,
1303 const char *database, uint32_t current_version)
1305 krb5_context context = server_context->context;
1306 int ret = 0;
1307 krb5_data out;
1308 krb5_storage *sp;
1309 uint32_t tmp;
1311 ret = read_msg(context, s, &out);
1312 if (ret) {
1313 if (ret != EWOULDBLOCK)
1314 krb5_warn(context, ret, "error reading message from %s", s->name);
1315 return ret;
1318 sp = krb5_storage_from_mem(out.data, out.length);
1319 if (sp == NULL) {
1320 krb5_warnx(context, "process_msg: no memory");
1321 krb5_data_free(&out);
1322 return 1;
1324 if (krb5_ret_uint32(sp, &tmp) != 0) {
1325 krb5_warnx(context, "process_msg: client send too short command");
1326 krb5_data_free(&out);
1327 return 1;
1329 switch (tmp) {
1330 case I_HAVE :
1331 ret = krb5_ret_uint32(sp, &tmp);
1332 if (ret != 0) {
1333 krb5_warnx(context, "process_msg: client send too little I_HAVE data");
1334 break;
1337 * XXX Make the slave send the timestamp as well, and try to get it
1338 * here, and pass it to send_diffs().
1341 * New slave whose version number we've not yet seen. If the version
1342 * number is zero, the slave has no data, and we'll send a complete
1343 * database (that happens in send_diffs()). Otherwise, we'll record a
1344 * non-zero initial version and attempt an incremental update.
1346 * NOTE!: Once the slave is "ready" (its first I_HAVE has conveyed its
1347 * initial version), we MUST NOT update s->version to the slave's
1348 * I_HAVE version, since we may already have sent later updates, and
1349 * MUST NOT send them again, otherwise we can get further and further
1350 * out of sync resending larger and larger diffs. The "not yet ready"
1351 * is an essential precondition for setting s->version to the value
1352 * in the I_HAVE message. This happens only once when the slave
1353 * first connects.
1355 if (!(s->flags & SLAVE_F_READY)) {
1356 if (current_version < tmp) {
1357 krb5_warnx(context, "Slave %s (version %u) has later version "
1358 "than the master (version %u) OUT OF SYNC",
1359 s->name, tmp, current_version);
1360 /* Force send_complete() */
1361 tmp = 0;
1364 * Mark the slave as ready for updates based on incoming signals.
1365 * Prior to the initial I_HAVE, we don't know the slave's version
1366 * number, and MUST not send it anything, since we'll needlessly
1367 * attempt to send the whole database!
1369 s->version = tmp;
1370 s->flags |= SLAVE_F_READY;
1371 if (verbose)
1372 krb5_warnx(context, "slave %s ready for updates from version %u",
1373 s->name, tmp);
1375 if ((s->version_ack = tmp) < s->version)
1376 break;
1377 send_diffs(server_context, s, log_fd, database, current_version);
1378 break;
1379 case I_AM_HERE :
1380 if (verbose)
1381 krb5_warnx(context, "slave %s is there", s->name);
1382 break;
1383 case ARE_YOU_THERE:
1384 case FOR_YOU :
1385 default :
1386 krb5_warnx(context, "Ignoring command %d", tmp);
1387 break;
1390 krb5_data_free(&out);
1391 krb5_storage_free(sp);
1393 slave_seen(s);
1395 return ret;
1398 #define SLAVE_NAME "Name"
1399 #define SLAVE_ADDRESS "Address"
1400 #define SLAVE_VERSION "Version"
1401 #define SLAVE_STATUS "Status"
1402 #define SLAVE_SEEN "Last Seen"
1404 static void
1405 init_stats_names(krb5_context context)
1407 const char *fn = NULL;
1408 char *buf = NULL;
1410 if (slave_stats_file)
1411 fn = slave_stats_file;
1412 else if ((fn = krb5_config_get_string(context, NULL, "kdc",
1413 "iprop-stats", NULL)) == NULL) {
1414 if (asprintf(&buf, "%s/slaves-stats", hdb_db_dir(context)) != -1
1415 && buf != NULL)
1416 fn = buf;
1417 buf = NULL;
1419 if (fn != NULL) {
1420 slave_stats_file = fn;
1421 if (asprintf(&buf, "%s.tmp", fn) != -1 && buf != NULL)
1422 slave_stats_temp_file = buf;
1426 static void
1427 write_master_down(krb5_context context)
1429 char str[100];
1430 time_t t = time(NULL);
1431 FILE *fp = NULL;
1433 if (slave_stats_temp_file != NULL)
1434 fp = fopen(slave_stats_temp_file, "w");
1435 if (fp == NULL)
1436 return;
1437 if (krb5_format_time(context, t, str, sizeof(str), TRUE) == 0)
1438 fprintf(fp, "master down at %s\n", str);
1439 else
1440 fprintf(fp, "master down\n");
1442 if (fclose(fp) != EOF)
1443 (void) rk_rename(slave_stats_temp_file, slave_stats_file);
1446 static void
1447 write_stats(krb5_context context, slave *slaves, uint32_t current_version)
1449 char str[100];
1450 rtbl_t tbl;
1451 time_t t = time(NULL);
1452 FILE *fp = NULL;
1454 if (slave_stats_temp_file != NULL)
1455 fp = fopen(slave_stats_temp_file, "w");
1456 if (fp == NULL)
1457 return;
1459 if (krb5_format_time(context, t, str, sizeof(str), TRUE))
1460 snprintf(str, sizeof(str), "<unknown-time>");
1461 fprintf(fp, "Status for slaves, last updated: %s\n\n", str);
1463 fprintf(fp, "Master version: %lu\n\n", (unsigned long)current_version);
1465 tbl = rtbl_create();
1466 if (tbl == NULL) {
1467 fclose(fp);
1468 return;
1471 rtbl_add_column(tbl, SLAVE_NAME, 0);
1472 rtbl_add_column(tbl, SLAVE_ADDRESS, 0);
1473 rtbl_add_column(tbl, SLAVE_VERSION, RTBL_ALIGN_RIGHT);
1474 rtbl_add_column(tbl, SLAVE_STATUS, 0);
1475 rtbl_add_column(tbl, SLAVE_SEEN, 0);
1477 rtbl_set_prefix(tbl, " ");
1478 rtbl_set_column_prefix(tbl, SLAVE_NAME, "");
1480 while (slaves) {
1481 krb5_address addr;
1482 krb5_error_code ret;
1483 rtbl_add_column_entry(tbl, SLAVE_NAME, slaves->name);
1484 ret = krb5_sockaddr2address (context,
1485 (struct sockaddr*)&slaves->addr, &addr);
1486 if(ret == 0) {
1487 krb5_print_address(&addr, str, sizeof(str), NULL);
1488 krb5_free_address(context, &addr);
1489 rtbl_add_column_entry(tbl, SLAVE_ADDRESS, str);
1490 } else
1491 rtbl_add_column_entry(tbl, SLAVE_ADDRESS, "<unknown>");
1493 snprintf(str, sizeof(str), "%u", (unsigned)slaves->version_ack);
1494 rtbl_add_column_entry(tbl, SLAVE_VERSION, str);
1496 if (slaves->flags & SLAVE_F_DEAD)
1497 rtbl_add_column_entry(tbl, SLAVE_STATUS, "Down");
1498 else
1499 rtbl_add_column_entry(tbl, SLAVE_STATUS, "Up");
1501 ret = krb5_format_time(context, slaves->seen, str, sizeof(str), TRUE);
1502 if (ret)
1503 rtbl_add_column_entry(tbl, SLAVE_SEEN, "<error-formatting-time>");
1504 else
1505 rtbl_add_column_entry(tbl, SLAVE_SEEN, str);
1507 slaves = slaves->next;
1510 rtbl_format(tbl, fp);
1511 rtbl_destroy(tbl);
1513 if (fclose(fp) != EOF)
1514 (void) rk_rename(slave_stats_temp_file, slave_stats_file);
1518 static char sHDB[] = "HDBGET:";
1519 static char *realm;
1520 static int version_flag;
1521 static int help_flag;
1522 static char *keytab_str = sHDB;
1523 static char *database;
1524 static char *config_file;
1525 static char *port_str;
1526 static int detach_from_console;
1527 static int daemon_child = -1;
1529 static struct getargs args[] = {
1530 { "config-file", 'c', arg_string, &config_file, NULL, NULL },
1531 { "realm", 'r', arg_string, &realm, NULL, NULL },
1532 { "keytab", 'k', arg_string, &keytab_str,
1533 "keytab to get authentication from", "kspec" },
1534 { "database", 'd', arg_string, &database, "database", "file"},
1535 { "slave-stats-file", 0, arg_string, rk_UNCONST(&slave_stats_file),
1536 "file for slave status information", "file"},
1537 { "time-missing", 0, arg_string, rk_UNCONST(&slave_time_missing),
1538 "time before slave is polled for presence", "time"},
1539 { "time-gone", 0, arg_string, rk_UNCONST(&slave_time_gone),
1540 "time of inactivity after which a slave is considered gone", "time"},
1541 { "port", 0, arg_string, &port_str,
1542 "port ipropd will listen to", "port"},
1543 { "detach", 0, arg_flag, &detach_from_console,
1544 "detach from console", NULL },
1545 { "daemon-child", 0, arg_integer, &daemon_child,
1546 "private argument, do not use", NULL },
1547 { "pidfile-basename", 0, arg_string, &pidfile_basename,
1548 "basename of pidfile; private argument for testing", "NAME" },
1549 { "hostname", 0, arg_string, rk_UNCONST(&master_hostname),
1550 "hostname of master (if not same as hostname)", "hostname" },
1551 { "verbose", 0, arg_flag, &verbose, NULL, NULL },
1552 { "version", 0, arg_flag, &version_flag, NULL, NULL },
1553 { "help", 0, arg_flag, &help_flag, NULL, NULL }
1555 static int num_args = sizeof(args) / sizeof(args[0]);
1558 main(int argc, char **argv)
1560 krb5_error_code ret;
1561 krb5_context context;
1562 void *kadm_handle;
1563 kadm5_server_context *server_context;
1564 kadm5_config_params conf;
1565 krb5_socket_t signal_fd, listen_fd;
1566 int log_fd;
1567 slave *slaves = NULL;
1568 uint32_t current_version = 0, old_version = 0;
1569 krb5_keytab keytab;
1570 char **files;
1571 int aret;
1572 int optidx = 0;
1573 int restarter_fd = -1;
1574 struct stat st;
1576 setprogname(argv[0]);
1578 if (getarg(args, num_args, argc, argv, &optidx))
1579 krb5_std_usage(1, args, num_args);
1581 if (help_flag)
1582 krb5_std_usage(0, args, num_args);
1584 if (version_flag) {
1585 print_version(NULL);
1586 exit(0);
1589 memset(hostname, 0, sizeof(hostname));
1591 if (master_hostname &&
1592 strlcpy(hostname, master_hostname,
1593 sizeof(hostname)) >= sizeof(hostname)) {
1594 errx(1, "Hostname too long: %s", master_hostname);
1595 } else if (master_hostname == NULL) {
1596 if (gethostname(hostname, sizeof(hostname)) == -1)
1597 err(1, "Could not get hostname");
1598 if (hostname[sizeof(hostname) - 1] != '\0')
1599 errx(1, "Hostname too long %.*s...",
1600 (int)sizeof(hostname), hostname);
1603 if (detach_from_console && daemon_child == -1)
1604 daemon_child = roken_detach_prep(argc, argv, "--daemon-child");
1605 rk_pidfile(pidfile_basename);
1607 ret = krb5_init_context(&context);
1608 if (ret)
1609 errx(1, "krb5_init_context failed: %d", ret);
1611 setup_signal();
1613 if (config_file == NULL) {
1614 aret = asprintf(&config_file, "%s/kdc.conf", hdb_db_dir(context));
1615 if (aret == -1 || config_file == NULL)
1616 errx(1, "out of memory");
1619 ret = krb5_prepend_config_files_default(config_file, &files);
1620 if (ret)
1621 krb5_err(context, 1, ret, "getting configuration files");
1623 ret = krb5_set_config_files(context, files);
1624 krb5_free_config_files(files);
1625 if (ret)
1626 krb5_err(context, 1, ret, "reading configuration files");
1628 init_stats_names(context);
1630 time_before_gone = parse_time (slave_time_gone, "s");
1631 if (time_before_gone < 0)
1632 krb5_errx (context, 1, "couldn't parse time: %s", slave_time_gone);
1633 time_before_missing = parse_time (slave_time_missing, "s");
1634 if (time_before_missing < 0)
1635 krb5_errx (context, 1, "couldn't parse time: %s", slave_time_missing);
1637 krb5_openlog(context, "ipropd-master", &log_facility);
1638 krb5_set_warn_dest(context, log_facility);
1640 ret = krb5_kt_register(context, &hdb_get_kt_ops);
1641 if(ret)
1642 krb5_err(context, 1, ret, "krb5_kt_register");
1644 ret = krb5_kt_resolve(context, keytab_str, &keytab);
1645 if(ret)
1646 krb5_err(context, 1, ret, "krb5_kt_resolve: %s", keytab_str);
1648 memset(&conf, 0, sizeof(conf));
1649 if(realm) {
1650 conf.mask |= KADM5_CONFIG_REALM;
1651 conf.realm = realm;
1653 ret = kadm5_init_with_skey_ctx (context,
1654 KADM5_ADMIN_SERVICE,
1655 NULL,
1656 KADM5_ADMIN_SERVICE,
1657 &conf, 0, 0,
1658 &kadm_handle);
1659 if (ret)
1660 krb5_err (context, 1, ret, "kadm5_init_with_password_ctx");
1662 server_context = (kadm5_server_context *)kadm_handle;
1664 log_fd = open (server_context->log_context.log_file, O_RDONLY, 0);
1665 if (log_fd < 0)
1666 krb5_err (context, 1, errno, "open %s",
1667 server_context->log_context.log_file);
1669 if (fstat(log_fd, &st) == -1)
1670 krb5_err(context, 1, errno, "stat %s",
1671 server_context->log_context.log_file);
1673 if (flock(log_fd, LOCK_SH) == -1)
1674 krb5_err(context, 1, errno, "shared flock %s",
1675 server_context->log_context.log_file);
1676 kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_LAST,
1677 &current_version, NULL);
1678 flock(log_fd, LOCK_UN);
1680 signal_fd = make_signal_socket (context);
1681 listen_fd = make_listen_socket (context, port_str);
1683 krb5_warnx(context, "ipropd-master started at version: %lu",
1684 (unsigned long)current_version);
1686 roken_detach_finish(NULL, daemon_child);
1687 restarter_fd = restarter(context, NULL);
1689 while (exit_flag == 0){
1690 slave *p;
1691 fd_set readset, writeset;
1692 int max_fd = 0;
1693 struct timeval to = {30, 0};
1694 uint32_t vers;
1695 struct stat st2;;
1697 #ifndef NO_LIMIT_FD_SETSIZE
1698 if (signal_fd >= FD_SETSIZE || listen_fd >= FD_SETSIZE ||
1699 restarter_fd >= FD_SETSIZE)
1700 krb5_errx (context, IPROPD_RESTART, "fd too large");
1701 #endif
1703 FD_ZERO(&readset);
1704 FD_ZERO(&writeset);
1705 FD_SET(signal_fd, &readset);
1706 max_fd = max(max_fd, signal_fd);
1707 FD_SET(listen_fd, &readset);
1708 max_fd = max(max_fd, listen_fd);
1709 if (restarter_fd > -1) {
1710 FD_SET(restarter_fd, &readset);
1711 max_fd = max(max_fd, restarter_fd);
1714 for (p = slaves; p != NULL; p = p->next) {
1715 if (p->flags & SLAVE_F_DEAD)
1716 continue;
1717 FD_SET(p->fd, &readset);
1718 if (have_tail(p) || more_diffs(p))
1719 FD_SET(p->fd, &writeset);
1720 max_fd = max(max_fd, p->fd);
1723 ret = select(max_fd + 1, &readset, &writeset, NULL, &to);
1724 if (ret < 0) {
1725 if (errno == EINTR)
1726 continue;
1727 else
1728 krb5_err (context, IPROPD_RESTART, errno, "select");
1731 if (stat(server_context->log_context.log_file, &st2) == -1) {
1732 krb5_warn(context, errno, "could not stat log file by path");
1733 st2 = st;
1736 if (st2.st_dev != st.st_dev || st2.st_ino != st.st_ino) {
1737 (void) close(log_fd);
1739 log_fd = open(server_context->log_context.log_file, O_RDONLY, 0);
1740 if (log_fd < 0)
1741 krb5_err(context, IPROPD_RESTART_SLOW, errno, "open %s",
1742 server_context->log_context.log_file);
1744 if (fstat(log_fd, &st) == -1)
1745 krb5_err(context, IPROPD_RESTART_SLOW, errno, "stat %s",
1746 server_context->log_context.log_file);
1748 if (flock(log_fd, LOCK_SH) == -1)
1749 krb5_err(context, IPROPD_RESTART, errno, "shared flock %s",
1750 server_context->log_context.log_file);
1751 kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_LAST,
1752 &current_version, NULL);
1753 flock(log_fd, LOCK_UN);
1756 if (ret == 0) {
1757 /* Recover from failed transactions */
1758 if (kadm5_log_init_nb(server_context) == 0)
1759 kadm5_log_end(server_context);
1761 if (flock(log_fd, LOCK_SH) == -1)
1762 krb5_err(context, IPROPD_RESTART, errno,
1763 "could not lock log file");
1764 kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_LAST,
1765 &current_version, NULL);
1766 flock(log_fd, LOCK_UN);
1768 if (current_version > old_version) {
1769 if (verbose)
1770 krb5_warnx(context,
1771 "Missed a signal, updating slaves %lu to %lu",
1772 (unsigned long)old_version,
1773 (unsigned long)current_version);
1774 for (p = slaves; p != NULL; p = p->next) {
1775 if (p->flags & SLAVE_F_DEAD)
1776 continue;
1777 send_diffs(server_context, p, log_fd, database,
1778 current_version);
1780 old_version = current_version;
1784 if (ret && FD_ISSET(restarter_fd, &readset)) {
1785 exit_flag = SIGTERM;
1786 break;
1789 if (ret && FD_ISSET(signal_fd, &readset)) {
1790 #ifndef NO_UNIX_SOCKETS
1791 struct sockaddr_un peer_addr;
1792 #else
1793 struct sockaddr_storage peer_addr;
1794 #endif
1795 socklen_t peer_len = sizeof(peer_addr);
1797 if(recvfrom(signal_fd, (void *)&vers, sizeof(vers), 0,
1798 (struct sockaddr *)&peer_addr, &peer_len) < 0) {
1799 krb5_warn (context, errno, "recvfrom");
1800 continue;
1802 --ret;
1803 assert(ret >= 0);
1804 old_version = current_version;
1805 if (flock(log_fd, LOCK_SH) == -1)
1806 krb5_err(context, IPROPD_RESTART, errno, "shared flock %s",
1807 server_context->log_context.log_file);
1808 kadm5_log_get_version_fd(server_context, log_fd, LOG_VERSION_LAST,
1809 &current_version, NULL);
1810 flock(log_fd, LOCK_UN);
1811 if (current_version != old_version) {
1813 * If current_version < old_version then the log got
1814 * truncated and we'll end up doing full propagations.
1816 * Truncating the log when the current version is
1817 * numerically small can lead to race conditions.
1818 * Ideally we should identify log versions as
1819 * {init_or_trunc_time, vno}, then we could not have any
1820 * such race conditions, but this would either require
1821 * breaking backwards compatibility for the protocol or
1822 * adding new messages to it.
1824 if (verbose)
1825 krb5_warnx(context,
1826 "Got a signal, updating slaves %lu to %lu",
1827 (unsigned long)old_version,
1828 (unsigned long)current_version);
1829 for (p = slaves; p != NULL; p = p->next) {
1830 if (p->flags & SLAVE_F_DEAD)
1831 continue;
1832 send_diffs(server_context, p, log_fd, database,
1833 current_version);
1835 } else {
1836 if (verbose)
1837 krb5_warnx(context,
1838 "Got a signal, but no update in log version %lu",
1839 (unsigned long)current_version);
1843 for (p = slaves; p != NULL; p = p->next) {
1844 if (!(p->flags & SLAVE_F_DEAD) &&
1845 FD_ISSET(p->fd, &writeset) &&
1846 ((have_tail(p) && send_tail(context, p) == 0) ||
1847 (!have_tail(p) && more_diffs(p)))) {
1848 send_diffs(server_context, p, log_fd, database,
1849 current_version);
1853 for(p = slaves; p != NULL; p = p->next) {
1854 if (p->flags & SLAVE_F_DEAD)
1855 continue;
1856 if (ret && FD_ISSET(p->fd, &readset)) {
1857 --ret;
1858 assert(ret >= 0);
1859 ret = process_msg(server_context, p, log_fd, database,
1860 current_version);
1861 if (ret && ret != EWOULDBLOCK)
1862 slave_dead(context, p);
1863 } else if (slave_gone_p (p))
1864 slave_dead(context, p);
1865 else if (slave_missing_p (p))
1866 send_are_you_there (context, p);
1869 if (ret && FD_ISSET(listen_fd, &readset)) {
1870 add_slave (context, keytab, &slaves, listen_fd);
1871 --ret;
1872 assert(ret >= 0);
1874 write_stats(context, slaves, current_version);
1877 if(exit_flag == SIGINT || exit_flag == SIGTERM)
1878 krb5_warnx(context, "%s terminated", getprogname());
1879 #ifdef SIGXCPU
1880 else if(exit_flag == SIGXCPU)
1881 krb5_warnx(context, "%s CPU time limit exceeded", getprogname());
1882 #endif
1883 else
1884 krb5_warnx(context, "%s unexpected exit reason: %ld",
1885 getprogname(), (long)exit_flag);
1887 write_master_down(context);
1889 return 0;